Cryptographically secure random numbers using /dev/urandom

Cryptographically secure random numbers are important any time your random numbers are being used in what is meant to be a secure application; for example when generating passwords and cryptographic keys. Tcl's rand is not cryptographically secure. However, Tcllib does offer rc4, a stream cipher that is at its core a pseudo-random number generator. There is also the Random package which uses the ISAAC algorithm; which is considered secure.

These aren't bad solutions, but if you are in a modern Linux based environment your kernel should have support for /dev/random and /dev/urandom. The /dev/random kernel module polls the running system to gather entropy used to generate what are considered true random numbers. The only other true random numbers available to most Tcl applications is random.org, however this service transmits your numbers in the clear which in most instances requiring security is unacceptable.

/dev/random is powerful and mature, and so it only makes sense to take advantage of when available. The counterpart to /dev/random is /dev/urandom, which stands for "unblocked" random. /dev/random is "blocked" meaning when the pool of available entropy is exhausted you must wait until additional entropy is gathered to get new random data. /dev/urandom does not block, and instead will reuse the entropy pool. While this decreases the quality of the randomness over time, it is still considered a cryptographically secure PRNG. What follows is the example of a function that can generate a 64 bit unsigned integer within a range, using /dev/urandom. Note that /dev/random and /dev/urandom are simply treated as files.

proc randInt { min max } {
    set randDev [open /dev/urandom rb]
    set random [read $randDev 8]
    binary scan $random H16 random
    set random [expr {([scan $random %x] % (($max-$min) + 1) + $min)}]
    close $randDev
    return $random
}

RLE - 2011-12-23 19:55:58 Changed open above to open /dev/urandom in BINARY mode. /dev/urandom returns random binary data and reading from the channel in cooked mode would slightly reduce the available entropy.


bodangly - 2011-12-23 19:55:58

Good catch RLE, I noticed a slight irregularity in the distribution previously and was trying to figure it out. BINARY told me it required permissions to be set so I changed the above code to use "rb" instead. Edit: Whoops. =\ Wiped out the page, led me to rewrite things a bit more succinctly and update the function to accept both a min and max.

nem 2011-12-24: Won't the use of % (mod) also skew the distribution?

RLE (2011-12-24): Try it with "[open /dev/urandom {RDONLY BINARY}]". I left out the "RDONLY" in my last edit. Note also that there is no need to brace quote the constant filename unless the filename contains spaces or characters that are special to the Tcl parser. The name "/dev/urandom" contains nothing special and no spaces.

DKF (2011-12-24): Rewrote to use scan to convert hex to a number, which is more efficient than reparsing an expression. (I don't know whether urandom is of good enough quality that the difference between / and % would matter; I've always used division for this sort of thing myself.)


DED - 2016-04-30 15:29:49

I think the above procedure has at least one significant problem in that the scan function severely truncates unless the format string is %llx (ll meaning don't truncate). As written, any random value above 2147483647 is truncated to 2147483647, and of course, 8 bytes ranges from 0 to 18446744073709551615---so, not so random. I'm also pretty unclear about the mapping of the resulting integer into the min-max range---I'm not confident that line of the procedure is correct. Here's a version I like much better.

##################################################
# Random number generator, crypto quality
#################################################    
# Returns a random floating point number between $min and $max, inclusive
# With the default arguments, this is almost the same as expr rand()
# Doesn't work on Windows, only on Unix-based OS such as MacOSX and Linux
proc getRandomNumber {{min 0} {max 1}} {
   global tcl_platform
   if {$tcl_platform(platform) == "unix"} {
        set f [open /dev/urandom rb] ; set eightRandomBytes [read $f 8] ; close $f
       binary scan $eightRandomBytes h16 eightRandomBytesHex
       # n is an integer from 0 to 18446744073709551615 inclusive... lossless conversion
       set n [scan $eightRandomBytesHex %llx]
       # map n to min-max inclusive... maybe we lose a little randomness here (precision)
       set randomNumber [expr (($n/18446744073709551615.0) * ($max - $min)) + $min]
       return $randomNumber
   } else {
       error "getRandomNumber: Only works with Unix-based platforms"
   } 
}