Criptograma inesperado
Julio de 2011
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 descargar el programa.
El sistema de cifrado “BLAISE”
—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 bytes en notación hexadecimal.
—Exacto. Pongamos en marcha el REPL de Clojure para hacer pruebas.
$ java -Dfile.encoding=UTF8 -cp clojure.jar clojure.main
Clojure 1.2.1
user=>
—Guardemos el contenido del mensaje en una variable.
(def x
"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 una función 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.
(defn unarmor [s]
(map #(Integer/parseInt % 16) (.split s "\\s+")))
—Escribamos también una función para obtener la frecuencia de cada valor de forma ordenada.
(defn sorted-frequencies [xs]
(sort #(compare (second %1) (second %2))
(frequencies xs)))
—Y obtengamos el resultado utilizando ambas funciones.
user=> (sorted-frequencies (unarmor x))
([166 1] [199 1] [200 1] [141 1] [173 1] [174 1]
[206 1] [176 1] [208 1] [179 1] [211 1] [149 1]
[181 1] [186 1] [158 1] [201 2] [203 2] [204 2]
[207 2] [144 2] [177 2] [180 2] [185 2] [156 2]
[188 2] [193 3] [197 3] [205 3] [145 3] [209 3]
[178 3] [184 3] [155 3] [187 3] [189 3] [191 3]
[195 4] [202 4] [190 4] [192 6] [182 7] [159 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 byte 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 byte del texto cifrado el valor de sus correspondientes caracteres del texto en claro y de la contraseña.
user=> (- 0xAE (int \A) (int \A))
44
user=> (- 0xAF (int \A) (int \B))
44
—¿Lo ves? En ambos casos el número mágico es 44. Ahora ya podemos escribir la función de cifrado y descifrado.
(defn blaise [xs ks encrypt?]
(apply str
(map (fn [x k]
(char (mod ((if encrypt? + -)
(int x) (int k) 44)
256)))
xs (apply concat (repeat ks)))))
Ataque de diccionario
—Vamos a probar un ataque de diccionario. Como el programa sólo acepta palabras en mayúsculas y sin tildes, necesitaremos una función para adaptar las palabras del diccionario.
(defn normalize [s]
(.. s
(toUpperCase)
(replaceAll "Á" "A")
(replaceAll "É" "E")
(replaceAll "Í" "I")
(replaceAll "Ó" "O")
(replaceAll "Ú" "U")
(replaceAll "Ü" "U")
(replaceAll "Ñ" "N")))
—Definamos otra función que lea un archivo que contenga una palabra en cada línea y devuelva una secuencia con las palabras transformadas por la función anterior.
(defn words [url]
(map normalize (.split (slurp url) "\n")))
—Y por último, una función que intenta descifrar el texto cifrado con cada una de las palabras del diccionario. En cada resultado buscará un patrón para determinar si es la palabra correcta, y si coincide devolverá la palabra.
(defn attack [xs url pattern]
(some (fn [word]
(if (re-find pattern (blaise xs word false))
word))
(words url)))
—Busquemos un archivo con un listado de palabras en español en Internet… Bien, este servirá. Ahora sólo falta decidir qué patrón ha de buscar.
—¿Qué tal si busca HOLA MENELAO al principio del mensaje?
—Puede funcionar. Probemos.
(attack (unarmor x)
"http://bit.ly/x9zxH4"
#"^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.