From b7a25e33de39e8cb6e9bbe39a0d88d83b7a8eb5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:15:15 +0000 Subject: [PATCH 1/5] Initial plan From 15be3ae443167e8fc96354cd23f5cdc595dc2390 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:25:17 +0000 Subject: [PATCH 2/5] Add cryptographically secure random number generator option Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com> --- CHANGELOG.md | 8 +++++ README.md | 24 +++++++++++++- randstr/char-classes.rkt | 11 ++++--- randstr/cli/main.rkt | 18 +++++++++++ randstr/config.rkt | 52 +++++++++++++++++++++++++++++-- randstr/generator.rkt | 14 ++++----- randstr/main.rkt | 1 + randstr/scribblings/randstr.scrbl | 21 +++++++++++++ randstr/tests/test.rkt | 21 +++++++++++++ 9 files changed, 155 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f81bc5a..b5d584f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Added +- ✨ New: Cryptographically secure random number generator option + - Library parameter `randstr-secure-random?` to enable secure random mode + - CLI option `-s/--secure` to use cryptographically secure random + - Environment variable `RANDSTR_SECURE` to enable secure random from environment + ## 0.2.0 (2026-01-09) ### Added diff --git a/README.md b/README.md index 87f2dee..820d03d 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ racket -l randstr/cli "[a-z]{5}" (randstr "[a-z]{5}") ; Generate a random 5-letter lowercase string (randstr "(abc|def)+") ; Generate a string with repeated "abc" or "def" (randstr* "[0-9]{3}" 10) ; Generate 10 random 3-digit numbers + +;; Use cryptographically secure random number generator +(parameterize ([randstr-secure-random? #t]) + (randstr "[A-Za-z0-9]{32}")) ; Generate a secure random string ``` ### As a Command-Line Tool @@ -39,11 +43,13 @@ racket -l randstr/cli "[a-z]{5}" randstr "[a-z]{5}" # Generate one random string randstr -n 10 "[0-9]{3}" # Generate 10 random 3-digit numbers randstr -m 20 "a+" # '+' and '*' max repetition cap +randstr -s "[A-Z]{10}" # Use cryptographically secure random ``` -You can also set an environment variable: +You can also set environment variables: - `RANDSTR_MAX_REPEAT`: positive integer; default cap for `*` and `+` (CLI only). +- `RANDSTR_SECURE`: set to `1`, `true`, `yes`, or `on` to enable cryptographically secure random (CLI only). ## Pattern Syntax @@ -130,6 +136,22 @@ Capture generated content and reuse it later in the pattern: (randstr "(?[A-Z]{2})(?\\d{2})-\\k\\k") ; => "XY42-XY42" ``` +### Cryptographically Secure Random + +For security-sensitive applications (e.g., generating tokens, passwords, or secrets), enable cryptographically secure random number generation: + +```racket +;; Using parameterize (recommended for scoped usage) +(parameterize ([randstr-secure-random? #t]) + (randstr "[A-Za-z0-9]{32}")) ; => Secure random 32-character string + +;; Or set globally +(randstr-secure-random? #t) +(randstr "[0-9]{6}") ; => Secure random 6-digit code +``` + +**Note**: Cryptographically secure random uses `crypto-random-bytes` from Racket, which is backed by the operating system's cryptographic random number generator (e.g., `/dev/urandom` on Unix systems). This is slower than the default pseudo-random number generator but provides unpredictable output suitable for security purposes. + ## Examples ```racket diff --git a/randstr/char-classes.rkt b/randstr/char-classes.rkt index b983803..52b9768 100644 --- a/randstr/char-classes.rkt +++ b/randstr/char-classes.rkt @@ -4,7 +4,8 @@ racket/string racket/list racket/random - racket/set) + racket/set + "config.rkt") (provide (contract-out @@ -57,12 +58,12 @@ ;; Generate a random character (define (random-character) (let ([chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]) - (string-ref chars (random (string-length chars))))) + (string-ref chars (randstr-random (string-length chars))))) ;; Generate a random word character (alphanumeric + underscore) (define (random-word-char) (let ([chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"]) - (string-ref chars (random (string-length chars))))) + (string-ref chars (randstr-random (string-length chars))))) ;; Generate a random whitespace character (define (random-whitespace-char) @@ -86,11 +87,11 @@ ;; Get a random element from a list (define (random-ref lst) - (list-ref lst (random (length lst)))) + (list-ref lst (randstr-random (length lst)))) ;; Get a random element from a vector (define (vector-random-ref vec) - (vector-ref vec (random (vector-length vec)))) + (vector-ref vec (randstr-random (vector-length vec)))) ;; Generate list of alphanumeric characters (define (alphanumeric-chars) diff --git a/randstr/cli/main.rkt b/randstr/cli/main.rkt index a4ba05e..ee21543 100644 --- a/randstr/cli/main.rkt +++ b/randstr/cli/main.rkt @@ -8,6 +8,7 @@ (define pattern (make-parameter "")) (define count (make-parameter 1)) (define max-repeat (make-parameter #f)) +(define secure-random (make-parameter #f)) (define (env->positive-integer name) (define v (getenv name)) @@ -17,6 +18,13 @@ (define n (string->number v)) (and (exact-integer? n) (positive? n) n)])) +(define (env->boolean name) + (define v (getenv name)) + (cond + [(or (not v) (string=? v "")) #f] + [else + (member (string-downcase v) '("1" "true" "yes" "on"))])) + (define help-text "Usage: randstr [options] Generate random strings based on regex-like patterns. @@ -24,6 +32,7 @@ Generate random strings based on regex-like patterns. Options: -n, --count N Generate N strings (default: 1) -m, --max-repeat N Maximum repetition for * and + (default: env RANDSTR_MAX_REPEAT or 5) + -s, --secure Use cryptographically secure random number generator (default: env RANDSTR_SECURE or false) -h, --help Show this help message ") @@ -36,6 +45,10 @@ Options: (define env-max (env->positive-integer "RANDSTR_MAX_REPEAT")) (when env-max (randstr-max-repeat env-max)) + + ;; Apply env default for secure random (if present) + (when (env->boolean "RANDSTR_SECURE") + (secure-random #t)) (command-line #:program "randstr" @@ -44,6 +57,8 @@ Options: (count (string->number N))] [("-m" "--max-repeat") N "Maximum repetition for * and +" (max-repeat (string->number N))] + [("-s" "--secure") "Use cryptographically secure random number generator" + (secure-random #t)] ;;; [("-h" "--help") "Show help" ;;; (show-help)] #:args (pattern-arg) @@ -52,6 +67,9 @@ Options: (when (max-repeat) (randstr-max-repeat (max-repeat))) + (when (secure-random) + (randstr-secure-random? #t)) + (if (= (count) 1) (displayln (randstr (pattern))) (for ([str (in-list (randstr* (pattern) (count)))]) diff --git a/randstr/config.rkt b/randstr/config.rkt index 076e580..2779739 100644 --- a/randstr/config.rkt +++ b/randstr/config.rkt @@ -1,12 +1,22 @@ #lang racket/base -(require racket/contract) +(require racket/contract + racket/random) (provide (contract-out ;; Maximum repetition for '*' and '+' quantifiers. ;; '*' picks an integer in [0, max], '+' picks an integer in [1, max]. - [randstr-max-repeat (parameter/c exact-positive-integer?)])) + [randstr-max-repeat (parameter/c exact-positive-integer?)] + ;; When #t, use cryptographically secure random number generation. + ;; Default is #f (use standard PRNG for better performance). + [randstr-secure-random? (parameter/c boolean?)] + ;; Generate a random non-negative integer less than n. + ;; Uses crypto-random-bytes when randstr-secure-random? is #t. + [randstr-random (exact-positive-integer? . -> . exact-nonnegative-integer?)] + ;; Generate a random floating point number in [0, 1). + ;; Uses crypto-random-bytes when randstr-secure-random? is #t. + [randstr-random-real (-> (and/c real? (>=/c 0) ( . (listof string?))] [parse-and-generate (string? . -> . string?)] [randstr-max-repeat (parameter/c exact-positive-integer?)] + [randstr-secure-random? (parameter/c boolean?)] [tokenize-pattern (string? . -> . (listof (struct/c token any/c any/c any/c)))] [parse-character-class (list? . -> . (values vector? list?))] [parse-quantifier (list? . -> . (values diff --git a/randstr/scribblings/randstr.scrbl b/randstr/scribblings/randstr.scrbl index fc54d5f..aa6b709 100644 --- a/randstr/scribblings/randstr.scrbl +++ b/randstr/scribblings/randstr.scrbl @@ -20,6 +20,7 @@ A library for generating random strings based on regex-like patterns. @item{Named groups and backreferences for pattern reuse} @item{Command-line interface for quick generation} @item{Fair distribution: duplicate characters in classes are deduplicated} + @item{Cryptographically secure random number generation option} ] @section{Functions} @@ -44,6 +45,26 @@ A library for generating random strings based on regex-like patterns. ] } +@defparam[randstr-secure-random? secure? boolean?]{ + When set to @racket[#t], all random number generation uses cryptographically secure + random numbers (via @racket[crypto-random-bytes]). Default is @racket[#f]. + + Use this for security-sensitive applications like generating tokens, passwords, or secrets. + + @racketblock[ + ;; Using parameterize for scoped secure mode + (parameterize ([randstr-secure-random? #t]) + (randstr "[A-Za-z0-9]{32}")) ; => Secure random 32-character string + + ;; Or set globally + (randstr-secure-random? #t) + (randstr "[0-9]{6}") ; => Secure random 6-digit code + ] + + @bold{Note}: Cryptographically secure random is slower than the default pseudo-random + number generator, but provides unpredictable output suitable for security purposes. +} + @section{Pattern Syntax} The following pattern syntax is supported: diff --git a/randstr/tests/test.rkt b/randstr/tests/test.rkt index 32b70fd..9e39a55 100644 --- a/randstr/tests/test.rkt +++ b/randstr/tests/test.rkt @@ -103,4 +103,25 @@ ;; Named group with quantifier on backreference (check-true (string? (randstr "(?[A-Z]{2})\\k{2}"))) +;; Tests for cryptographically secure random mode +;; Default should be non-secure +(check-false (randstr-secure-random?)) + +;; Test that secure mode works with parameterize +(parameterize ([randstr-secure-random? #t]) + (check-true (randstr-secure-random?)) + (check-true (string? (randstr "[a-z]{10}"))) + (check-true (string? (randstr "\\w{5}"))) + (check-true (string? (randstr "[[:alpha:]]{8}"))) + (check-true (string? (randstr "(abc|def){3}")))) + +;; Verify it returns to normal mode after parameterize +(check-false (randstr-secure-random?)) + +;; Test that randstr* works in secure mode +(parameterize ([randstr-secure-random? #t]) + (let ([results (randstr* "[0-9]{6}" 5)]) + (check-equal? (length results) 5) + (check-true (andmap string? results)))) + (printf "All tests passed!\n") From 57375def4fe70713a2bd22d250736958cffb45fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:26:56 +0000 Subject: [PATCH 3/5] Address code review: use rejection sampling for unbiased crypto random Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com> --- randstr/config.rkt | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/randstr/config.rkt b/randstr/config.rkt index 2779739..be9e2a9 100644 --- a/randstr/config.rkt +++ b/randstr/config.rkt @@ -30,16 +30,31 @@ (define randstr-secure-random? (make-parameter #f)) -;; Generate a random integer in [0, n) using crypto-random-bytes +;; 2^64 - used for converting 8 bytes to a floating point number in [0, 1) +(define 2^64 (expt 2 64)) + +;; Calculate the number of bytes needed to represent values up to n +;; Uses ceiling to ensure we have enough bytes for the full range +(define (bytes-needed-for n) + (max 4 (ceiling (/ (+ 1 (integer-length n)) 8)))) + +;; Generate a random integer in [0, n) using crypto-random-bytes with rejection sampling +;; This ensures unbiased results by rejecting values that would cause modulo bias (define (crypto-random-integer n) - ;; Use enough bytes to cover the range with minimal bias - ;; For values up to 2^32, 4 bytes is sufficient - ;; For larger values, use more bytes - (let* ([byte-count (max 4 (ceiling (/ (+ 1 (integer-length n)) 8)))] - [bytes (crypto-random-bytes byte-count)] - [val (for/fold ([acc 0]) ([i (in-range byte-count)]) - (+ (bytes-ref bytes i) (* acc 256)))]) - (modulo val n))) + (let* ([byte-count (bytes-needed-for n)] + ;; Maximum value that can be represented with byte-count bytes + [max-val (expt 256 byte-count)] + ;; Largest multiple of n that fits in max-val + ;; We reject values >= limit to avoid modulo bias + [limit (* n (quotient max-val n))]) + (let loop () + (let* ([bytes (crypto-random-bytes byte-count)] + [val (for/fold ([acc 0]) ([i (in-range byte-count)]) + (+ (bytes-ref bytes i) (* acc 256)))]) + (if (< val limit) + (modulo val n) + ;; Reject and retry to avoid bias + (loop)))))) ;; Generate a random floating-point number in [0, 1) using crypto-random-bytes (define (crypto-random-real) @@ -48,7 +63,7 @@ [val (for/fold ([acc 0]) ([i (in-range 8)]) (+ (bytes-ref bytes i) (* acc 256)))]) ;; Divide by 2^64 to get a value in [0, 1) - (/ val 18446744073709551616))) + (/ val 2^64))) ;; Generate a random non-negative integer less than n. ;; Uses cryptographically secure random when randstr-secure-random? is #t. From 78b295b8953859833ba6b4e589a455ea589d7d1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:28:45 +0000 Subject: [PATCH 4/5] Replace magic numbers with named constants for clarity Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com> --- randstr/config.rkt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/randstr/config.rkt b/randstr/config.rkt index be9e2a9..4eec57a 100644 --- a/randstr/config.rkt +++ b/randstr/config.rkt @@ -30,27 +30,30 @@ (define randstr-secure-random? (make-parameter #f)) -;; 2^64 - used for converting 8 bytes to a floating point number in [0, 1) -(define 2^64 (expt 2 64)) +;; Constants for byte manipulation +(define 2^64 (expt 2 64)) ; Used for converting 8 bytes to [0, 1) float +(define BITS-PER-BYTE 8) ; Number of bits per byte +(define VALUES-PER-BYTE 256) ; Number of distinct values per byte (2^8) +(define MIN-BYTES 4) ; Minimum bytes for reasonable randomness (32 bits) ;; Calculate the number of bytes needed to represent values up to n ;; Uses ceiling to ensure we have enough bytes for the full range (define (bytes-needed-for n) - (max 4 (ceiling (/ (+ 1 (integer-length n)) 8)))) + (max MIN-BYTES (ceiling (/ (+ 1 (integer-length n)) BITS-PER-BYTE)))) ;; Generate a random integer in [0, n) using crypto-random-bytes with rejection sampling ;; This ensures unbiased results by rejecting values that would cause modulo bias (define (crypto-random-integer n) (let* ([byte-count (bytes-needed-for n)] ;; Maximum value that can be represented with byte-count bytes - [max-val (expt 256 byte-count)] + [max-val (expt VALUES-PER-BYTE byte-count)] ;; Largest multiple of n that fits in max-val ;; We reject values >= limit to avoid modulo bias [limit (* n (quotient max-val n))]) (let loop () (let* ([bytes (crypto-random-bytes byte-count)] [val (for/fold ([acc 0]) ([i (in-range byte-count)]) - (+ (bytes-ref bytes i) (* acc 256)))]) + (+ (bytes-ref bytes i) (* acc VALUES-PER-BYTE)))]) (if (< val limit) (modulo val n) ;; Reject and retry to avoid bias @@ -59,9 +62,9 @@ ;; Generate a random floating-point number in [0, 1) using crypto-random-bytes (define (crypto-random-real) ;; Use 8 bytes (64 bits) for good precision - (let* ([bytes (crypto-random-bytes 8)] - [val (for/fold ([acc 0]) ([i (in-range 8)]) - (+ (bytes-ref bytes i) (* acc 256)))]) + (let* ([bytes (crypto-random-bytes BITS-PER-BYTE)] + [val (for/fold ([acc 0]) ([i (in-range BITS-PER-BYTE)]) + (+ (bytes-ref bytes i) (* acc VALUES-PER-BYTE)))]) ;; Divide by 2^64 to get a value in [0, 1) (/ val 2^64))) From 64b605a1de2007b60d1e5f43fdb468b6fd13f399 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:42:11 +0000 Subject: [PATCH 5/5] Apply code review feedback: add inexact->exact and byte order comment Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com> --- randstr/config.rkt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/randstr/config.rkt b/randstr/config.rkt index 4eec57a..ffccfad 100644 --- a/randstr/config.rkt +++ b/randstr/config.rkt @@ -39,7 +39,7 @@ ;; Calculate the number of bytes needed to represent values up to n ;; Uses ceiling to ensure we have enough bytes for the full range (define (bytes-needed-for n) - (max MIN-BYTES (ceiling (/ (+ 1 (integer-length n)) BITS-PER-BYTE)))) + (max MIN-BYTES (inexact->exact (ceiling (/ (+ 1 (integer-length n)) BITS-PER-BYTE))))) ;; Generate a random integer in [0, n) using crypto-random-bytes with rejection sampling ;; This ensures unbiased results by rejecting values that would cause modulo bias @@ -52,6 +52,7 @@ [limit (* n (quotient max-val n))]) (let loop () (let* ([bytes (crypto-random-bytes byte-count)] + ;; Convert bytes to integer in big-endian order (most significant byte first) [val (for/fold ([acc 0]) ([i (in-range byte-count)]) (+ (bytes-ref bytes i) (* acc VALUES-PER-BYTE)))]) (if (< val limit)