Josep Portella

Criptograma inesperado

Julio de 2011
Revisado: Septiembre de 2015

© 2011, 2015 Josep Portella Florit
Esta obra está bajo una licencia de
Atribución-SinDerivadas 3.0 Creative Commons.

Llega un mensaje cifrado

—Menelao, te ha llegado un email de un tal Paris.
—¿Paris? Es un viejo amigo mío.
—Mira el contenido del email.

-----BEGIN BLAISE MESSAGE-----
B8 C0 CB B2 9F C9 B6 CC B2 BB B6 CA A6 90 C5 C4
91 C4 CF B4 D0 B6 B1 C4 9B CA B1 C3 C0 91 C3 C1
C4 CE B2 B3 BE CD C7 B5 9F 9F B6 D1 C1 C4 9E B9
B0 95 C8 BF BA C0 D1 91 CF C1 C3 D1 BC BD B6 9B
CB C5 B6 9F B9 C4 9C B4 CD BB BE B8 C4 BE BF 9F
9F CA C0 9C BF CD 8D BB C4 9B CA C5 B6 C3 C0 9F
BD B8 D3 AE BD C9 BC CC 90 BE C0 C4 AD
-----END BLAISE MESSAGE-----

—Parece que se trata de un mensaje cifrado, ¿pero por qué me lo envía? Hace unos días me encontré con él después de años sin verle y estuvimos charlando sobre… Ah, claro, recuerdo que le conté que últimamente no estoy muy motivado; que no entran trabajos que supongan un reto intelectual. Creo que debe haber enviado esto para que tenga algo interesante en lo que pensar. ¡Muy amable de su parte!
—¿Vas a intentar descifrarlo?
—Los demás trabajos que tenemos en marcha no son especialmente urgentes, así que no veo por qué no tendría que hacerlo. Empecemos. ¿Qué es lo que podemos decir de este mensaje a primera vista?
—Lo más obvio es que tiene una marca de donde empieza y otra de donde acaba.
—Sí, y además parece ser que se ha generado con un sistema de cifrado llamado BLAISE.
—No lo conozco.
—Yo tampoco. Busquemos en Internet a ver si podemos encontrar el programa.

El sistema de cifrado “BLAISE”

Cifrar Descifrar

—Ya veo, escribes un texto y una contraseña y te permite cifrarlo o descifrarlo. Vamos a ver qué pasa si introducimos el mensaje cifrado de Paris e intentamos descifrarlo con una contraseña cualquiera… Por supuesto, se convierte en basura. Sigamos estudiando el mensaje cifrado. ¿Qué podemos decir sobre lo que hay entre las marcas de inicio y final del mensaje?
—Parecen octetos en notación hexadecimal.
—Exacto. Pongamos en marcha el REPL de Racket para hacer pruebas.

menelao@esparta:~$ racket
Welcome to Racket v5.3.6.
>

—Guardemos el contenido del mensaje en una variable.

(define msg "
B8 C0 CB B2 9F C9 B6 CC B2 BB B6 CA A6 90 C5 C4
91 C4 CF B4 D0 B6 B1 C4 9B CA B1 C3 C0 91 C3 C1
C4 CE B2 B3 BE CD C7 B5 9F 9F B6 D1 C1 C4 9E B9
B0 95 C8 BF BA C0 D1 91 CF C1 C3 D1 BC BD B6 9B
CB C5 B6 9F B9 C4 9C B4 CD BB BE B8 C4 BE BF 9F
9F CA C0 9C BF CD 8D BB C4 9B CA C5 B6 C3 C0 9F
BD B8 D3 AE BD C9 BC CC 90 BE C0 C4 AD
")

—Definamos un procedimiento para obtener el mensaje cifrado en crudo: tendrá que dividir la cadena por donde haya espacios o nuevas líneas y convertir cada número hexadecimal resultante en un entero.

(define (unarmor str)
  (sequence-map (curryr string->number 16)
                (string-split str)))

—Escribamos también un procedimiento para obtener la frecuencia de cada valor de forma ordenada.

(define (sorted-frequencies nums)
  (sort (hash->list
         (sequence-fold
          (curryr hash-update add1 0)
          (hash) nums))
        < #:key cdr))

—Y obtengamos el resultado utilizando ambos procedimientos.

> (sorted-frequencies (unarmor msg))
'((141 . 1) (149 . 1) (158 . 1) (166 . 1)
  (174 . 1) (173 . 1) (179 . 1) (176 . 1)
  (181 . 1) (186 . 1) (199 . 1) (200 . 1)
  (206 . 1) (211 . 1) (208 . 1) (144 . 2)
  (156 . 2) (177 . 2) (180 . 2) (185 . 2)
  (188 . 2) (203 . 2) (201 . 2) (207 . 2)
  (204 . 2) (145 . 3) (155 . 3) (178 . 3)
  (187 . 3) (184 . 3) (191 . 3) (189 . 3)
  (193 . 3) (197 . 3) (205 . 3) (209 . 3)
  (190 . 4) (195 . 4) (202 . 4) (192 . 6)
  (159 . 7) (182 . 7) (196 . 9))

—Podemos ver que la frecuencia de los valores no está distribuida de forma regular.
—¿Y qué significa eso?
—Que no se trata de un sistema criptográfico fuerte; seguramente lo ha diseñado un aficionado.

Ingeniería inversa

—Cifrando el texto AAAA con la contraseña AB nos da el texto cifrado AE AF AE AF. Si ciframos BBBB con la misma contraseña tenemos AF B0 AF B0, es decir lo mismo que antes pero con cada valor incrementado en una unidad.
—¿Qué se deduce de esto?
—Que cada octeto del texto cifrado se obtiene aplicando una función a un carácter del texto en claro y un carácter de la contraseña. El primer carácter del texto con el primero de la contraseña, el segundo con el segundo y así hasta que se acaba la contraseña. Entonces se vuelve a empezar con el primer carácter de la contraseña. Sólo falta descubrir una cosa: el número mágico.
—¿El número mágico?
—Un número fijo que también se usa en la función, que vamos a averiguar restando al octeto del texto cifrado el valor de sus correspondientes caracteres del texto en claro y de la contraseña.

> (- #xAE (char->integer #\A) (char->integer #\A))
44
> (- #xAF (char->integer #\A) (char->integer #\B))
44

—¿Lo ves? En ambos casos el número mágico es 44. Ahora ya podemos escribir el procedimiento de descifrado.

(define (decrypt nums keys)
  (sequence-map
   (lambda (n k)
     (integer->char (modulo (- n k 44) 128)))
   (in-parallel
    nums (in-cycle (sequence-map char->integer
                                 keys)))))

Ataque de diccionario

—Vamos a probar un ataque de diccionario. Como el programa sólo acepta palabras en mayúsculas y sin tildes, necesitaremos un procedimiento para adaptar las palabras del diccionario así como se vayan leyendo.

(define normalize-word
  (compose (curryr string-replace #px"\\W+" "")
           string-upcase string-normalize-nfkd))

—Y por último, un procedimiento que intenta descifrar el texto cifrado con cada una de las palabras del diccionario. En cada resultado buscará un prefijo para determinar si es la palabra correcta, y si coincide devolverá la palabra.

(define (attack nums word-list prefix)
  (with-input-from-file word-list
    (lambda ()
      (for/first
          ((w (sequence-map normalize-word
                            (in-lines)))
           #:when
           (sequence-andmap
            eqv? (in-parallel (decrypt nums w)
                              prefix)))
        w))))

—Busquemos un archivo con un listado de palabras en español en Internet… Bien, este servirá. Ahora sólo falta decidir qué prefijo ha de buscar.
—¿Qué tal si busca HOLA MENELAO?
—Puede que funcione. Probemos.

> (attack (unarmor msg) "espanol.txt"
          "HOLA MENELAO")

Texto en claro

—Vaya, me pregunto por qué Paris habrá escogido esta palabra como contraseña. Bueno, sólo queda utilizarla para descifrar el mensaje.

Menelao leyó el mensaje descifrado y durante unos segundos se quedó pensativo. De repente palideció, se levantó bruscamente y cogió el móvil.