True Random Numbers

The Random.Org folks [L1 ] have some nice cgi-bin scripts that return truly random numbers based on atmospheric noise. Below is a TCL interface to that site.

Possible uses:

  • games - use ::RandomOrg::getIntList 1 6 100 to get 100 dice rolls
  • lotto - use ::RandomOrg::getSequence 1 40 to get numbers jumbled up
  • if the range of your random number queries isn't the same each time, use ::RandomOrg::getNumber min max as that uses an internal cache of random bytes to get the number so avoids HTTP calls for each request

Enjoy! BBH

 # -----------------------------------------------------------------
 #
 # Tcl interface to random.org random number generator
 #
 # -----------------------------------------------------------------
 #
 # author: Bruce Hartweg
 #
 # -----------------------------------------------------------------
 #
 # This creates a namespace called RandomOrg which exports 6 procs
 #
 #   configProxy - allows configuration of HTTP proxy data
 #   configCache - allows setting parameters for internal caching
 #
 #   These 3 procs provide direct access to cgi scripts at random.org
 #    they will block until data is complete
 #
 #   getIntList min max howMany - return a list of random numbers in range
 #   getSequence min max - return a random order list of non-duplicating numbers
 #   getBytes format numBytes - get random bytes in specified format
 #            formats:
 #                raw - binary data
 #                hexadecimal, decimal, octal, binary - list of ascii format numbers in each base
 #
 #  getNumber min max - uses internal buffer of random bytes to calculate number in range
 #            each call doesn't need to re-query random.org site useful if requested ranges
 #            are not the same each time
 #
 # -----------------------------------------------------------------
 
 namespace eval RandomOrg {
     variable baseURL "http://www.random.org/"
     variable limits
     array set limits {
         maxNum           10000
         intMin     -1000000000
         intMax      1000000000
         maxBytes         16384
         seqRange         10000
         seqMin     -1000000000
         seqMax      1000000000
     }
     variable proxy
     array set proxy {
         host ""
         port 8080
         user ""
         pw   ""
         haveBase64 1
     }
     variable formats
     array set formats {
         raw         f
         hexadecimal h
         decimal     d
         octal       o
         binary      b
     }
     variable cache
     array set cache {
         maxSize 1024
         minSize 128
         data ""
         timeout 120000
         token ""
     }
     namespace export configProxy
     namespace export configCache
     namespace export getIntList
     namespace export getSequence
     namespace export getBytes
     namespace export getNumber
 }
 
 package require http
 if {[catch {package require base64}]} {
     set ::RandomOrg::proxy(haveBase64) 0
 }
 
 proc ::RandomOrg::configProxy {{host ""} {port 8080} {user ""} {pw ""}} {
     variable proxy
     foreach v {host port user pw} {
         set proxy($v) [set $v]
     }
     if {[string length $user] > 0 && ! $proxy(haveBase64)} {
         set proxy(user) ""
         error "Cannot use proxy authentication - base64 package not found!" 
     }
 }
 
 proc ::RandomOrg::configCache {{minVal 128} {maxVal 1024} {timeout 120}} {
     variable cache
     # check args
     if {![string is int -strict $minVal]} {
         error "minVal must be an integer"
     }
     if {![string is int -strict $maxVal]} {
         error "maxVal must be an integer"
     }
     if {$maxVal <= $minVal} {
         error "maxVal must be greater than minVal"
     }
     if {$minVal < 0} {
         errpr "minVal cannot be negative"
     }
     if {![string is int -strict $timeout] || $timeout < 1} {
         error "timeout must be a positive integer"
     }
     set cache(minSize) $minVal
     set cache(maxSize) $maxVal
     set cahce(timeout) [expr {$timeout * 1000}]
     if {[string length $cache(data)] < $minVal} {
         _extendCache
     }
     return ""
 }
 
 proc ::RandomOrg::getIntList {minVal maxVal numItems} {
     # check args
     variable limits
     if {![string is int -strict $minVal] ||
         $minVal < $limits(intMin)} {
         error "minVal must be an integer no less than $limits(intMin)"
     }
     if {![string is int -strict $maxVal] ||
         $maxVal > $limits(intMax)} {
         error "maxVal must be an integer no greater than $limits(intMax)"
     }
     if {![string is int -strict $numItems] ||
         $numItems < 1 || $numItems > $limits(maxNum)} {
         error "numItems must be an integer from1 to $limits(maxNum)"
     }
     if {$maxVal <= $minVal} {
         error "maxVal must be greater than minVal"
     }
     # build URL string
     set url "integers/?num=$numItems&min=$minVal&max=$maxVal&col=1&format=plain&base=10"
     if {[catch { _getData $url } ret]} {
         error "Failed to get random numbers: $ret"
     }
     return [string map {"\n" " "} $ret]
 }
 
 proc ::RandomOrg::getSequence {minVal maxVal} {
     # check args
     variable limits
     if {![string is int -strict $minVal] ||
         $minVal < $limits(seqMin)} {
         error "minVal must be an integer no less than $limits(seqMin)"
     }
     if {![string is int -strict $maxVal] ||
         $maxVal > $limits(seqMax)} {
         error "maxVal must be an integer no greater than $limits(seqMax)"
     }
     if {$maxVal <= $minVal} {
         error "maxVal must be greater than minVal"
     }
     if {($maxVal - $minVal) > $limits(seqRange)} {
         error "sequence range must not exceed $limits(seqRange)"
     }
     # build URL string
     set url "sequences/?min=$minVal&max=$maxVal&format=plain&col=1"
     if {[catch { _getData $url } ret]} {
         error "Failed to get random sequence: $ret"
     }
     return [string map {"\n" " "} $ret]
 }
 
 proc ::RandomOrg::getBytes {format numBytes} {
     variable limits
     variable formats
     # check args
     if {![string is int -strict $numBytes] ||
         $numBytes < 1 || $numBytes > $limits(maxBytes)} {
         error "numBytes must be an integer from1 to $limits(maxBytes)"
     }
     set key [array names formats $format*]
     if {[llength $key] != 1} {
         error "format must be one of [array names formats]"
     }
     set fmt $formats($key)
     # build URL string
     set url "randbyte?nbytes=$numBytes&format=$fmt"
     if {[catch { _getData $url } ret]} {
         error "Failed to get random sequence: $ret"
     }
     if {[string compare $fmt f]} {
         regsub -all {\s+} [string trim $ret] " " ret
     }
     return $ret
 }
 
 proc ::RandomOrg::getNumber {minVal maxVal} {
     variable cache
     # check args
     if {![string is int -strict $minVal]} {
         error "minVal must be an integer"
     }
     if {![string is int -strict $maxVal]} {
         error "maxVal must be an integer"
     }
     if {$maxVal <= $minVal} {
         error "maxVal must be greater than minVal"
     }
     if {[string length $cache(data)] < 4} {
         _extendCache
         set id [after $cache(timeout) set cache(data) Timeout]
         vwait [namespace which -variable cache](data)
         after cancel $id
         if {[string equal $cache(data) Timeout]} {
             set cache(data) ""
             error "Timeout while trying to get data"
         }
     }
     set chunk [string range $cache(data) 0 3]
     set cache(data) [string range $cache(data) 4 end]
     if {[string length $cache(data)] < $cache(minSize)} {
         _extendCache
     }
     # convert to int
     binary scan $chunk I num
     # clear sign bit
     set val [expr {$num & 0x7FFFFFFF}]
     # return number in specified range 
     return [expr {round((($maxVal-$minVal)*($val / double(0x7FFFFFFF))) + $minVal)}]
 }
 
 
 # internal helper procs -----------------------------------------------------
 
 proc ::RandomOrg::_getData {url} {
     variable baseURL
     variable proxy
 
     set cmd [list ::http::geturl $baseURL$url]
     # if proxy is set - configure it
     if {[string length $proxy(host)] > 0} {
         ::http::config -proxyhost $proxy(host) -proxyport $proxy(port)
         # see if proxy needs authentication
         if {[string length $proxy(user)] > 0} {
             set enc [base64::encode $proxy(user):$Proxy(pw)]
             set auth [list "Proxy-Authorization" [concat "Basic" $enc]]
             lappend cmd -headers $auth
         }
     }
     if {[catch $cmd tok]} {
         error "HTTP Request failed: $tok"
     }
     if {[::http::ncode $tok] != 200} {
         set msg "[::http::status $tok] : [::http::error $tok]"
         ::http::cleanup $tok
         error $msg
     }
     set ret [::http::data $tok]
     ::http::cleanup $tok
     return $ret
 }
 
 proc ::RandomOrg::_extendCache {} {
     variable baseURL
     variable proxy
     variable cache
 
     if {[string length $cache(token)] > 0} {
         # current request pending - don't send another
         return
     }
     
     set cmd [list ::http::geturl ${baseURL}randbyte?nbytes=$cache(maxSize)&format=f]
     # if proxy is set - configure it
     if {[string length $proxy(host)] > 0} {
         ::http::config -proxyhost $proxy(host) -proxyport $proxy(port)
         # see if proxy needs authentication
         if {[string length $proxy(user)] > 0} {
             set enc [base64::encode $proxy(user):$Proxy(pw)]
             set auth [list "Proxy-Authorization" [concat "Basic" $enc]]
             lappend cmd -headers $auth
         }
     }
     lappend cmd -handler [namespace which -command _addToCache]
     lappend cmd -command [namespace which -command _cacheComplete]
     if {[catch $cmd tok]} {
         error "Failed HTTP call : $tok"
     }
     set cache(token) $tok
 }
 
 proc ::RandomOrg::_addToCache {sock tok} {
     variable cache
     set chunk [read $sock]
     if {[set sz [string length $chunk]] > 0} {
         append cache(data) $chunk
     }
     return $sz
 }
 
 proc ::RandomOrg::_cacheComplete {tok} {
     variable cache
     ::http::cleanup $tok
     set cache(token) ""
     if {[string length $cache(data)] < $cache(minSize)} {
         _extendCache
     }
 }

FPX: Here's a shameless plug for Combat, which allows you to pull random numbers from their server as simply as

 package require combat

 proc getRandomNumber {} {
   set obj [corba::string_to_object http://www.random.org/Random.ior]
   set rand [$obj lrand48]
   corba::release $obj
   return $rand
 }

Sorry, couldn't resist ... [yeah, I know that the above code does much more than these lines]


TV I'm sure I've mentioned it on some other page, but to prevent being dependent on the internet and the security hazard that someone else may read the data you pass through their network, you could hook up a fm radio to your sound card, record some in between station noise, and interpret the wav file you recorded as quite random data, absolutely unpredictable electronic noise. Recording CD full of it takes about an hour, and that is uncrackable, probably even by nasa, cia and kgb together, except for extracting characteristics of the noise..

DKF: On the other hand, that just brings you to the key distribution problem. (You'd also better tune the characteristics of your noise generator carefully so that it has the right spectral properties).

Lars H: RFC 4086 [L2 ] has a lot to say about this matter. One important point is that it doesn't really matter if the hardware produces biased random data, because one should do post-processing ("whitening") to improve the distribution etc. This cannot produce more bits of truly random information than what was there to begin with however, it can only concentrate them.

TV Well, if you let a gigahertz process gather well made electronics/physics noise (like a transistor makes and can be captured by a decent AD converter) then the correlations and the real randomness may be covered by the idea that such a source could be ´´recognizable´´ but that doesn´t change that the resulting noise is ´´´extremely´´´ random, and equally ´´´unpredictable´´´ and not self-correlated (in EE terminology which can be quantized and qualified). And can be generated at such high rates that not even a hundred Tesla node machine can real-time keep up with it to do all too much analysis, so it´s cool.


cmaj The baseURL changed to "www.random.org/cgi-bin/". I also added a little proc to check when the buffer is full on the server. The site advises that automated clients should wait until the buffer is above 20%. Also, requests when the buffer is at 0% will hang.

 proc ::RandomOrg::checkBuffer {} {
    # build URL string
    set url "checkbuf"
    if {[catch { _getData $url } ret]} {
        error "Failed to check buffer: $ret"
    }
    return [string map {"\n" " " "%" ""} $ret]
 }

LD Lots of things seemed to have changed. I fixed the getIntList and getSequence so they work. For getIntList, though, the base value is hardcoded to 10, evn though 2, 8 and 16 are available. Someone with more time may want to spend some to make it better...


JM 4/3/2014...Another option:

 console show

 package require struct
 package require htmlparse
 package require http

 proc parse {} {

   ::struct::tree t

   set url "http://www.random.org/sequences/?min=1&max=54&col=1&format=html&rnd=new"
   set http  [::http::geturl $url]
   set html  [::http::data $http]

   htmlparse::2tree $html t
   htmlparse::removeVisualFluff t
   htmlparse::removeFormDefs t

   set base [walk {1 4 1 0}]
   set listado [t get $base data]
   puts "type(tag): [t get $base type]\n"
   set ctr 0
   foreach card $listado {
    puts "[incr ctr]: $card"
   }
   t destroy
   return
 }

 proc walkf {n p} {
     foreach idx $p {
         if {$n == ""} {break}
         set n [lindex [t children $n] $idx]
     }
     return $n
 }

 proc walk {p} {
     return [walkf root $p]
 }

 parse