4 de febrero de 2020

Quantumly Generated Random Bytes in Racket

Let’s get the bytes

Last week I read about the project Quantum RNG for OpenQu that is a “quantum random numbers as a service”. The idea is to use it to get some random numbers in Racket, using the net/hhp-client and json modules.

This function creates a bytestring of length n using the service.

#lang racket

(require net/http-client
         json)

(define (quantum-random-bytes n)
  (define-values (status headers content)
    (http-sendrecv "random.openqu.org"        
                   (string-append "/api/randint"
                                  "?size=" (number->string n)
                                  "&min=0"
                                  "&max=255")))
  (list->bytes (hash-ref (read-json content) 'result)))

(quantum-random-bytes 5) ; ==> #"\2\353<\223\346"
(quantum-random-bytes 5) ; ==> #"\240\200\242\364\25"
(quantum-random-bytes 5) ; ==> #"\201\370\367\32\25"

Other random-bytes functions

For comparison, we can use the crypto-random-bytes function and we also can write our own random-bytes function that uses the build-in pseudorandom number generator.

(require racket/random) ; for crypto-random-bytes
(define (random-bytes n)
  (list->bytes
   (for/list ([i (in-range n)])
    (random 256))))

(random-bytes 5)         ; ==> #"E\31\366\4\333"
(crypto-random-bytes 5)  ; ==> #"b\345\207\315\""
(quantum-random-bytes 5) ; ==> #"\30`\325\3377"

(require file/sha1) ; for bytes->hex-string

(bytes->hex-string (quantum-random-bytes 5)) ; ==> "00b7c4d6db"
(bytes->hex-string (crypto-random-bytes 5))  ; ==> "662b108fd2"
(bytes->hex-string (random-bytes 5))         ; ==> "da25419554"

As expected, the result of all of them look like similar random nonsense. But it looks nicer as hexadecimal random nonsense.

The first is deterministic, but it uses the initial time of the program as a seed, so you will get different results in each run. Its’s good enough to for a game or simulation, and you can use random-seed to get the same sequence.

The second use the operative system random number generator and is cryptographically secure. The operative system mix many sources of entropy, but all (most?) of them are classic source. (It’s like rolling a dice, it’s difficult to predict but it’s not truly random.)

The third uses a quantum system to produce the random numbers. These numbers are truly random (if quantum mechanics is correct). But remember that you can’t use them for a password and other security applications because the server owner may have a copy (or be lying and sending the digits ofpi). Or someone could be wiretapping your internet connection (this use http, not https!).

(Note that this are not qbits. The “q” part is killed in the generation, long before they are sent through the wire.)

I guess you can use it for a nice truly random quantum choice of numbers, like the starting position of a solitaire game. It’s “better” that just shuffling the cards, but probably undistinguishable for the player. For the secure version, you should buy the hardware version (and verify that it works as intended).

Testing the numbers

In a previous article, I used Random Sanity that is a service that does a minimal check of the random numbers. It may have false positives, and it may not detect fake random numbers, so do it’s only useful to detect very bad implementations of (pseudo) random numbers generators.

Using the functions defined in that article to contact the service, we get:

(test-random-bytes (random-bytes 64))         ; ==> #t ;probably
(test-random-bytes (crypto-random-bytes 64))  ; ==> #t
(test-random-bytes (quantum-random-bytes 64)) ; ==> #t
(with-random-seed 1234567890
  (test-random-bytes (random-bytes 64)))      ; ==> #f

The first is almost always true, but it may be false if we all keep trying and sending the numbers created by the pseudorandom number generator. Using the birthday paradox, with 38 persons starting the program in the same second, we have a 50% chance to get a collision. (Or melt the server, whatever happens first.) The last one is false because it is using a fixed seed, and I already tested it and the server detects the collision now.

Note that now that the bytes have been tested, they are even less secure because yet another person has a copy.

Some ideas to try

  • There is an API call to get some bytes in the base64 format. I tried to use it, but I get an error message. It may work later. It may be useful base64-decode.
  • Without any good reason I’m generating a random byte string instead of a single random number. With small modifications, it’s simple to make function that is a replacement of (random n). Note that the max value of the interval in the service may be included in the results, so remember to subtract 1 before sending the request. I don’t know how big can be max.
  • There is another API call to get floating point numbers, which can be used to write a function that is a replacement of (random). Just note that the roundtrip on the network may take longer than tolerable for an interactive program.

Bonus: Another similar service

While looking for more info I found a few similar services, but most of them require some kind of registration. One that is easy to use is the ANU Quantum Random Numbers Server. Let’s use it too:

(define (other-quantum-random-bytes n)
  (define-values (status headers content)
    (http-sendrecv "qrng.anu.edu.au"        
                   (string-append "/API/jsonI.php"
                                  "?length=" (number->string n)
                                  "&type=uint8")))
  (list->bytes (hash-ref (read-json content) 'data)))

(other-quantum-random-bytes 5) ; ==> #"\25\n1\236K"
(other-quantum-random-bytes 5) ; ==> #"\271\375'\272\205"
(other-quantum-random-bytes 5) ; ==> #"\23\362\324\271\\"
(bytes->hex-string (other-quantum-random-bytes 5)) ; ==> "9366b3f8d2"
(test-random-bytes (other-quantum-random-bytes 64)) ; ==> #t

The link to the API is difficult to find, but this server only support int8, int16 and bytes strings in some hexadecimal format. On the other hand, it supports https, but that part is left as an exercise for the reader.

Bytes aleatorios generados cuánticamente en Racket

Busquemos los bytes

La semana pasada leí sobre el proyecto QuantumRNG for OpenQu que se autodefine como “números cuánticos aleatorios como servicio”. La idea es usarlo para obtener algunos números aleatorios en Racket, usando los módulos net/http-client y json

Esta función crea una cadena de bytes de longitud n usando el servicio.

#lang racket

(require net/http-client
         json)

(define (quantum-random-bytes n)
  (define-values (status headers content)
    (http-sendrecv "random.openqu.org"        
                   (string-append "/api/randint"
                                  "?size=" (number->string n)
                                  "&min=0"
                                  "&max=255")))
  (list->bytes (hash-ref (read-json content) 'result)))

(quantum-random-bytes 5) ; ==> #"\2\353<\223\346"
(quantum-random-bytes 5) ; ==> #"\240\200\242\364\25"
(quantum-random-bytes 5) ; ==> #"\201\370\367\32\25"

Otras funciones como random-bytes

A modo de comparación, podemos usar la función crypto-random-bytes y también podemos escribir nuestra propia función random-bytes que usa el generador de números pseudoaleatorios incorporado.

(require racket/random) ; for crypto-random-bytes
(define (random-bytes n)
  (list->bytes
   (for/list ([i (in-range n)])
    (random 256))))

(random-bytes 5)         ; ==> #"E\31\366\4\333"
(crypto-random-bytes 5)  ; ==> #"b\345\207\315\""
(quantum-random-bytes 5) ; ==> #"\30`\325\3377"

(require file/sha1) ; for bytes->hex-string

(bytes->hex-string (quantum-random-bytes 5)) ; ==> "00b7c4d6db"
(bytes->hex-string (crypto-random-bytes 5))  ; ==> "662b108fd2"
(bytes->hex-string (random-bytes 5))         ; ==> "da25419554"

Como era de esperar, los resultados de todos ellos parecen cosas aleatorias similares sin sentido. Pero se ve más lindo verlo como cosas hexadecimales aleatorias sin sentido.

La primera es determinista, pero utiliza el tiempo de inicio del programa como semilla, por lo que se obtienen resultados diferentes en cada ejecución. Es lo suficientemente bueno como para un juego o simulación, y se puede usar random-seed para obtener la misma secuencia.

La segunda utiliza el generador de números aleatorios del sistema operativo y es criptográficamente segura. El sistema operativo combina muchas fuentes de entropía, pero todas (¿la mayoría?) son fuentes clásicas. (Es como tirar un dado, es difícil de predecir, pero no es realmente aleatorio.)

El tercero usa un sistema cuántico para producir los números aleatorios. Estos números son verdaderamente aleatorios (si la mecánica cuántica es correcta). Pero no es buena idea usarlos para una contraseña y otras aplicaciones de seguridad porque el dueño del servidor puede tener una copia (o estar mintiendo y enviando los dígitos de pi). O alguien podrían estar interceptando tu conexión a Internet (¡esto usa http, no https!).

(Notar que esto no son qbits. La “q” se elimina en la generación mucho antes de que se envíen a través del cable.)

Supongo que se puede usar para una buena elección de números aleatorios en forma cuántica, como la posición inicial de un juego de solitario. Es "mejor" que simplemente mezclar las cartas, pero probablemente sea indistinguible para el jugador. Para una versión segura, es necesario comprar la versión en hardware (y verificar que funciona según lo declarado).

Testeando los números

En un artículo anterior, usé Random Sanity que es un servicio que realiza una verificación mínima de los números aleatorios. Puede tener falsos positivos y puede que no detecte números aleatorios falsos, por lo que solo es útil para detectar implementaciones muy malas de generadores de números (pseudo) aleatorios.

Usando las funciones definidas en ese artículo para contactar este servicio, obtenemos:


(test-random-bytes (random-bytes 64))         ; ==> #t ;probably
(test-random-bytes (crypto-random-bytes 64))  ; ==> #t
(test-random-bytes (quantum-random-bytes 64)) ; ==> #t
(with-random-seed 1234567890
  (test-random-bytes (random-bytes 64)))      ; ==> #f

El primero en general es verdadero, pero puede ser falso si todos generamos y enviamos los números creados por el generador de números pseudoaleatorios. Usando la paradoja del cumpleaños, con 38 personas que comienzan el programa en el mismo segundo, tenemos un 50% de posibilidades de tener una colisión. (O que se derrita el servidor, lo que pase primero.) El último es falso porque está usando una semilla fija, y yo ya lo testeé y el servidor ahora detecta la colisión.

Notar que ahora que los bytes han sido testeados, son aún menos seguros porque otra persona más tiene una copia.

Algunas ideas para probar

  • Hay una llamada API para obtener algunos bytes en el formato base64. Probé usarla, pero recibo un mensaje de error. Es posible que funcione más adelante. Puede ser útil base64-decode.
  • Sin ninguna buena razón, estoy generando una cadena de bytes aleatoria en lugar de un único número aleatorio. Con algunas modificaciones es fácil hacer una función que reemplace a (random n). Notar que el valor max del intervalo en el servicio está incluido en los resultados, así que hay que restar 1 antes de enviar la solicitud. No se que tan grande puede ser max.
  • Hay otra llamada API para obtener números de coma flotante, que se puede usar para escribir una función que reemplace a (random). Pero es importante recordar que el viaje de ida y vuelta en la red puede llevar más tiempo del tolerable para un programa interactivo.

Bonus: Otro servicio similar en español

Mientras buscaba más información encontré varios servicios similares, pero la mayoría de ellos pedían algún tipo de registración. Uno que es fácil de usar es el ANU Quantum Random Numbers Server. Probémoslo también:

(define (other-quantum-random-bytes n)
  (define-values (status headers content)
    (http-sendrecv "qrng.anu.edu.au"        
                   (string-append "/API/jsonI.php"
                                  "?length=" (number->string n)
                                  "&type=uint8")))
  (list->bytes (hash-ref (read-json content) 'data)))

(other-quantum-random-bytes 5) ; ==> #"\25\n1\236K"
(other-quantum-random-bytes 5) ; ==> #"\271\375'\272\205"
(other-quantum-random-bytes 5) ; ==> #"\23\362\324\271\\"
(bytes->hex-string (other-quantum-random-bytes 5)) ; ==> "9366b3f8d2"
(test-random-bytes (other-quantum-random-bytes 64)) ; ==> #t

El link a la API es difícil de encontrar, pero este servidos solo soporta int8, int16 y cadenas de bits en algún tipo de formato hexadecimal. Por otro lado, soporta https, pero esta parte queda como ejercicio para el lector.