4 de febrero de 2020

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.