Hello! So, as some of you know, I'm working on a federation implementation in Guile. This needs a few things:
- Random tokens which won't collide, for various purposes - The ability to generate a solid random key, which is used for... - The ability to generate an HMAC (for signed cooke based sessions) To start out with, I've wondered how reliable Guile's RNG is. From the Guile docs: -- Scheme Procedure: random-state-from-platform -- C Function: scm_random_state_from_platform () Construct a new random state seeded from a platform-specific source of entropy, appropriate for use in non-security-critical applications. Currently ‘/dev/urandom’ is tried first, or else the seed is based on the time, date, process ID, an address from a freshly allocated heap cell, an address from the local stack frame, and a high-resolution timer if available. Well okay, if I could be sure that an application is using urandom, then it seems fine, but that doesn't seem clear. But here's a question: even if urandom is used for the initial seed, will that be enough? Depending on your RNG, you might be likely to see repeats. See this horror story: https://medium.com/@betable/tifu-by-using-math-random-f1c308c4fd9d#.lybr6y64d Is Guile's RNG ever good enough for a security sensitive application, even if a reliably random and secure initial seed is provided? Or should we only trust it for things like games? As for what I'm doing, I've figured, I have libgcrypt, which I'm using for the HMAC, and so for the random tokens, I could use libgcrypt, which is probably good-enough. But getting tokens via gcrypt, while not super slow, isn't really super fast. So here's some code I have, GPLv3 or later, yadda yadda: ================================================== (use-modules (system foreign) (rnrs bytevectors) (guix gcrypt) (guix base64)) (define %gcry-weak-random 0) (define %gcry-strong-random 1) (define %gcry-very-strong-random 2) (define* (gen-random-bv #:optional (bv-length 50) (level %gcry-strong-random)) (let* ((ptr (libgcrypt-func "gcry_randomize")) (proc (pointer->procedure void ptr `(* ,size_t ,int))) ; buffer, length, level (bv (make-bytevector bv-length)) (bv-ptr (bytevector->pointer bv))) (proc bv-ptr bv-length %gcry-strong-random) bv)) (define* (gen-random-token #:optional (bv-length 50) (level %gcry-strong-random)) (base64-encode (gen-random-bv bv-length level) 0 bv-length #f #t base64url-alphabet)) ================================================== So you can use gen-random-token like so: scheme@(guile-user)> (gen-random-token) $27 = "XRtMvTmfnQGRCWgA8BqVGFPUB3pOvFn5Us9Qc0JhecL4uxFZkwvb_jeFk8CpPC7TWbw" Horray, a random token! It's probably secure, as in, it probably won't collide with anything, because it's quite large and I suspect that libgcrypt knows what it's doing about reasonably-secureness in its RNG. (But what do I know?) It's not very fast though. I can generate ~5k tokens per second, which isn't so bad. scheme@(guile-user)> ,time (do ((i 1 (1+ i))) ((> i 10000)) (gen-random-token)) ;; 1.890826s real time, 1.959617s run time. 0.293538s spent in GC. Much of that is taken up by the base64 encoding... if we tear that out: scheme@(guile-user)> ,time (do ((i 1 (1+ i))) ((> i 10000)) (gen-random-bv)) ;; 0.440007s real time, 0.430297s run time. 0.000000s spent in GC. So that's a bit better. But it could be a lot faster. Here's something a lot faster, but it depends on our RNG being reliable: https://github.com/cwebber/pubstrate/blob/master/pubstrate/webapp/auth.scm#L53 (Sorry for the GitHub link, it will be moved shortly.) That's a lot faster: scheme@(guile-user)> ,time (do ((i 1 (1+ i))) ((> i 10000)) (gen-bearer-token)) ;; 0.226487s real time, 0.235521s run time. 0.024290s spent in GC. ... and probably could be even faster if I weren't making a string by appending a list together. So I've thought, maybe I could generate an initial seed like so: ================================================== (define* (big-random-number #:optional (bytes 50)) "Generate a quite large random number, probably suitable for seeding an RNG" ;; @@: Is * a sensible way to combine this? (apply * (bytevector->uint-list (gen-random-bv bytes %gcry-very-strong-random) (native-endianness) 8))) (set! *random-state* (seed->random-state (big-random-number))) ================================================== Ie, rely on libgcrypt to provide the initial seed. I'd assume libgcrypt would provide reliably pretty good random information, even in the absence of urandom. So! Does someone much better informed than I am have any insights? :) - Chris