Ir al contenido principal

Criptografía (CCXII): Solución Reto 404 CTF "Enigma"

En este post la solución a un reto de criptografía que he visto en una competición CTF (del inglés, 'Capture the Flag'), modalidad 'on-line' y formato 'Jeopardy' en la que me entretuve participando.

La competición es 404 CTF y el reto es sobre enigma, la máquina electromecánica de cifrado utilizada por el ejercito alemán durante la Segunda Guerra Mundial y la más famosa de la historia, y a la que, como sabe cualquier lector de este blog, soy muy aficionado.

Además de que el reto no parece fácil, en mi opinión presenta un nivel de dificultad alto (), el hándicap que tuve para resolverlo fue el idioma en el que se desarrolló la competición y, en consecuencia, en el que estaba escrito éste y todos los demás retos, el francés.

Enunciado:

La traducción, con la ayuda de 'Google Translate', sería más o menos la siguiente:

“¡La siguiente misión es de una confidencialidad absoluta! Interceptamos un mensaje enviado por un miembro de Hallebarde, y encontramos la máquina utilizada, una máquina Enigma M3. ¡Descifre este mensaje, encuentre el nombre de su contacto y frustre los planes de nuestros enemigos!

Durante su investigación, descubre el concepto de 'índice de coincidencia', que le intriga particularmente...

Tiene dos réplicas de máquinas Enigma M3 a su disposición, una en Python, la otra en C ++.

La bandera es el nombre del contacto, no olvide agregar 404ctf {} alrededor de su nombre".

Solución: la primera pista que me dan para resolver el reto es que parece que voy a tener que utilizar el 'índice de coincidencia' (IC), es decir, utilizar la fuerza bruta para ir probando configuraciones de la máquina y, tras calcular el índice de coincidencia del resultado de descifrar el criptograma, quedarme con aquella que presente un valor mayor de éste.

Esto me suena al reto que ya puse en este blog, "Reto 43", en el que expliqué el método 'Ciphertext-only cryptanalysis of Enigma' propuesto por  James Gillogly. En resumen, este método consiste en tres pasos, los siguientes:

1.- Obtener el IC mayor del descifrado del criptograma para todos los posibles órdenes de los rotores y, dentro de cada uno de ellos, todas las posiciones iniciales de cada rotor, fijando el ajuste de los anillos en A - A - A y con el tablero de conexiones sin ningún conector puesto.

2.- Para el orden de los rotores y a partir de la posición inicial de cada uno de ellos obtenidos en el paso anteriorobtener el IC mayor del descifrado del criptograma para cada ajuste del anillo posible del rotor derecho o rápido (de 'A' a 'Z'), fijando el ajuste de los anillos izquierdo y central (velocidad de giro lenta y media, respectivamente) en A - A y con el tablero de conexiones sin ningún conector puesto.

Este paso se repitea partir de la mejor configuración del rotor derecho o rápido hallada, para el rotor central o medio, es decir, para el orden de los rotores y a partir de la posición inicial del rotor izquierdo o lento obtenidos en el Paso 1, y con el ajuste del anillo y posición inicial del rotor derecho o rápido hallados en el Paso 2, obtener el IC mayor del descifrado del criptograma para cada ajuste del anillo posible del rotor central o medio (de 'A' a 'Z'), fijando el ajuste del anillo izquierdo o lento en 1 (A) y con el tablero de conexiones sin ningún conector puesto.

3.- Y finalmenteasumiendo que son correctos los parámetros de configuración de la máquina hallados hasta el momento, obtener los mejores resultados (IC's mayores) para el descifrado del criptograma objeto de análisis, de forma separada, con cada uno de los pares de letras posibles que pueden ser conectadas entre sí en el tablero de conexiones. Es decir, primero con: A - B, después con A - C,..., y finalmente con Y - Z.

Decía yo en el reto que puse en este blog y al que he hecho referencia antes, "Reto 43", que los dos factores críticos para que el criptoanálisis de un criptograma mediante este método tenga éxito son, por una parte, que su tamaño sea suficientemente grande para que el IC sea significativo (el del reto tiene una longitud de 3.131 caracteres, algo totalmente inusual entre los enviados por los alemanes en la WWII, en los que la norma imponía un máximo de 300 caracteres) y, por otra parte, que los pares de letras conectados en el tablero de conexiones no sean demasiados.

Inicialmente, consideré que se cumplía la segunda de las condiciones o factores críticos de éxito mencionados en el párrafo anterior, ya que si hay bastantes o muchas conexiones establecidas en el tablero de clavijas (justo antes de la WWII los alemanes disponían de 6 cables con los que conectar entre sí pares de letras y durante la guerra ese número aumentó a 10) me temo que este método no será eficaz.

Dicho lo anterior, aunque desde el punto de vista de las probabilidades de éxito nos viene muy bien que el criptograma sea tan largo, esta circunstancia incrementa sustancialmente la potencia de cálculo necesaria para dar respuesta al primero de los pasos indicados, por lo que para acortar el tiempo de ejecución del primer script de python que voy a emplear limito la longitud del criptograma a 501 caracteres (ver script más adelante).

Bueno, pues todo ello fue la estrategia que pensé para resolver este reto, y digo "fue" porque antes de empezar a programar los scripts necesarios para realizar el criptoanálisis eché un vistazo a las réplicas de máquinas Enigma M3 que se proporcionan: una en Python y la otra en C ++, y vi que se facilita el ajuste de los anillos de cada uno de los 5 rotores que se pueden emplear en la configuración de la máquina:

Con lo que se simplifica un poco el número de pasos a dar y cambio la estrategia de criptoanálisis por la siguiente:

1.- Obtener el IC mayor del descifrado del criptograma (recordar que limité su longitud a 501 caracteres para acortar el tiempo de ejecución de este primer paso) para todos los posibles órdenes de los rotores y, dentro de cada uno de ellos, todas las posiciones iniciales de cada rotor, fijando el ajuste de los anillos que se proporciona en el reto para cada rotor a utilizar en el descifrado y con el tablero de conexiones sin ningún conector puesto.

2.- Con los parámetros de configuración de la máquina hallados en el punto anterior, obtener los mejores resultados (IC's mayores) para el descifrado del criptograma objeto de análisis, de forma separada, con cada uno de los pares de letras posibles que pueden ser conectadas entre sí en el tablero de conexiones. Es decir, primero con: A - B, después con A - C,..., y finalmente con Y - Z.

Para implementar esta estrategia utilizo el mismo excelente simulador de la máquina enigma que usé en el "Reto 43" de este blog:

1.- Script para el primer paso (los ficheros "rotores_izquierdos.txt", "rotores_centrales.txt" y "rotores_derechos.txt" contienen juntando las mismas filas de cada uno de ellos las variaciones sin repetición de los 5 rotores tomados de 3 en 3 o lo que es lo mismo todas las posibilidades de alojar 3 de los 5 rotores en las 3 ranuras dispuestas en la máquina para ello, es decir, 5!/(5-3)! = 60, mientras que el fichero "posiciones_iniciales.txt" contiene todas las variaciones con repetición de las 26 letras del alfabeto tomados de 3 en 3 o lo que es lo mismo todas las posibles posiciones iniciales de los 3 rotores que se utilicen en el cifrado, es decir, 263 = 17.576):

import enigmaM3

alfabeto=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']

reflectores=['B','C']

rotores=['I','II','III','IV','V']

ajustes_anillos=['Q','E','V','J','Z']

tablero_conexiones=''

DEBUG = 0

criptograma=open('chiffre.txt').read()[:501]

print('[+] Criptograma:',criptograma)

print('*** FUERZA BRUTA ROTORES UTILIZADOS, SU ORDEN Y POSICIÓN INICIAL DE LOS MISMOS')

for reflector in reflectores:

   print('[+] Reflector:',reflector)

   f_rotor_izquierdo=open("rotores_izquierdos.txt")

   f_rotor_central=open("rotores_centrales.txt")

   f_rotor_derecho= open("rotores_derechos.txt")

   for rotor_izquierdo in f_rotor_izquierdo:

      rotor_central=f_rotor_central.readline()

      rotor_derecho=f_rotor_derecho.readline()

      print('[+] Rotor izquierdo o lento:',rotor_izquierdo.rstrip('\n'),'; rotor central o medio:',rotor_central.rstrip('\n'),'; rotor derecho o rápido:',rotor_derecho.rstrip('\n'))

      ajustes_anillos_rotores=ajustes_anillos[rotores.index(rotor_izquierdo.rstrip('\n'))] + ajustes_anillos[rotores.index(rotor_central.rstrip('\n'))] + ajustes_anillos[rotores.index(rotor_derecho.rstrip('\n'))]

      print('[+] Ajustes de los anillos rotores:',ajustes_anillos_rotores)

      f_posiciones_iniciales=open("posiciones_iniciales.txt")

      for posiciones_iniciales_rotores in f_posiciones_iniciales:

         c=enigmaM3.Enigma(reflector,rotor_izquierdo.rstrip('\n'), rotor_central.rstrip('\n'), rotor_derecho.rstrip('\n'), tablero_conexiones, ajustes_anillos_rotores, posiciones_iniciales_rotores.rstrip('\n'), DEBUG)

         texto_claro=''

         for i in range(len(criptograma)):

            t = c.encode(criptograma[i])

            texto_claro=texto_claro+t

         IC=0

         pares_letras_posibles=len(texto_claro)*(len(texto_claro)-1)/2

         for i in alfabeto:

            IC+=texto_claro.count(i)*(texto_claro.count(i)-1)/2/pares_letras_posibles

         if IC>0.05:

            print('[+] Posiciones iniciales de los rotores:',posiciones_iniciales_rotores.rstrip('\n'))

            print('[+] IC:',IC)

            print('[+] Texto_claro=',texto_claro)

      f_posiciones_iniciales.close()

   f_rotor_derecho.close()

   f_rotor_central.close()

   f_rotor_izquierdo.close()

Tras ejecutarlo se obtiene:

Con lo que se ve que el mayor IC se corresponde con:

- Reflector: C

- Rotores: V, II, IV

- Ajuste de los anillos de los rotores: Z, E, J

- Posiciones iniciales de los rotores: P, A, X

Y, por tanto, asumo que estos parámetros forma parte de la configuración de la máquina empleada para cifrar el mensaje. 

2.- Script para el segundo paso (el fichero "tablero_conexiones.txt" contiene las 325 posibles conexiones de 2 letras en el tablero de clavijas):

import enigmaM3

alfabeto=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']

DEBUG = 0

criptograma=open('chiffre.txt').read().strip('\n')

print('[+] Criptograma:',criptograma)

print('[+] Reflector:','C')

print('[+] Rotor izquierdo o lento: V; rotor central o medio: II; rotor derecho o rápido: IV')

print('[+] Ajuste de los anillos los rotores: Z, E, J')

print('[+] Posición inicial de los rotores: P, A, X')

print('*** FUERZA BRUTA TABLERO DE CONEXIONES')

f_tablero_conexiones=open("tablero_conexiones.txt")

for tablero_conexiones in f_tablero_conexiones:

   c=enigmaM3.Enigma('C', 'V', 'II', 'IV', tablero_conexiones.rstrip('\n'), 'ZEJ', 'PAX', DEBUG)

   texto_claro=''

   for i in range(len(criptograma)):

      t = c.encode(criptograma[i])

      texto_claro=texto_claro+t

   IC=0

   pares_letras_posibles=len(texto_claro)*(len(texto_claro)-1)/2

   for i in alfabeto:

      IC+=texto_claro.count(i)*(texto_claro.count(i)-1)/2/pares_letras_posibles

   if IC>0.058:

      print('[+] Tablero conexiones:',tablero_conexiones.rstrip('\n'))

      print('[+] IC =',IC)

      print('[+] Texto_claro=',texto_claro)

f_tablero_conexiones.close()

Tras ejecutarlo se obtiene:

 

Con lo que se ve que el mayor IC se corresponde con los parámetros hallados en el paso anterior más la conexión de las letras "R" y "V" en el tablero de clavijas. Además, si nos fijamos en el texto en claro ya se pueden ver ciertas palabras en francés e incluso ir adivinando otras.

Lamentablemente, con este método no conseguí obtener más conexiones en el tablero de clavijas, por lo que me tuve que conformar con el texto en claro parcialmente descifrado para conseguir la 'flag', y tras la inestimable ayuda de 'Google Translate' y gran cantidad de imaginación llegué a la conclusión de que ésta era: 404CTF{OISEAUDEMALHEUR}, lo que resultó ser correcto.

Comentarios

Entradas populares de este blog

Criptografía (I): cifrado Vigenère y criptoanálisis Kasiski

Hace unos días mi amigo Iñaki Regidor ( @Inaki_Regidor ), a quien dedico esta entrada :), compartió en las redes sociales un post titulado "Criptografía: el arte de esconder mensajes"  publicado en uno de los blogs de EiTB . En ese post se explican ciertos métodos clásicos para cifrar mensajes , entre ellos el cifrado de Vigenère , y , al final del mismo, se propone un reto consistente en descifrar un mensaje , lo que me ha animado a escribir este post sobre el método Kasiski  para atacar un cifrado polialfabético ( conociendo la clave descifrar el mensaje es muy fácil, pero lo que contaré en este post es la forma de hacerlo sin saberla ). El mensaje a descifrar es el siguiente: LNUDVMUYRMUDVLLPXAFZUEFAIOVWVMUOVMUEVMUEZCUDVSYWCIVCFGUCUNYCGALLGRCYTIJTRNNPJQOPJEMZITYLIAYYKRYEFDUDCAMAVRMZEAMBLEXPJCCQIEHPJTYXVNMLAEZTIMUOFRUFC Como ya he dicho el método de Vigenère es un sistema de sustitución polialfabético , lo que significa que, al contrario que en un sistema de

Criptografía (XXIII): cifrado de Hill (I)

En este post me propongo explicar de forma comprensible lo que he entendido sobre el cifrado de Hill , propuesto por el matemático Lester S. Hill , en 1929, y que se basa en emplear una matriz como clave  para cifrar un texto en claro y su inversa para descifrar el criptograma correspondiente . Hay tres cosas que me gustan de la criptografía clásica, además de que considero que ésta es muy didáctica a la hora de comprender los sistemas criptográficos modernos: la primera de ellas es que me "obliga" a repasar conceptos de matemáticas aprendidos hace mucho tiempo y, desgraciadamente, olvidados también hace demasiado tiempo, y, por consiguiente, que, como dice  Dani , amigo y coautor de este blog, me "obliga" a hacer "gimnasia mental"; la segunda es que, en la mayoría de las ocasiones, pueden cifrarse y descifrase los mensajes, e incluso realizarse el criptoanálisis de los criptogramas, sin más que un simple lápiz y papel, es decir, para mi es como un pasat

¿Qué significa el emblema de la profesión informática? (I)

Todas o muchas profesiones tienen un emblema que las representa simbólicamente y en el caso de la  informática: " es el establecido en la resolución de 11 de noviembre de 1977  para las titulaciones universitarias superiores de informática, y  está constituido por una figura representando en su parte central  un  núcleo toroidal de ferrita , atravesado por  hilos de lectura,  escritura e inhibición . El núcleo está rodeado por  dos ramas : una  de  laurel , como símbolo de recompensa, y la otra, de  olivo , como  símbolo de sabiduría. La  corona  será la  de la casa real  española,  y bajo el escudo se inscribirá el acrónimo de la organización. ". Veamos los diferentes elementos tomando como ejemplo el emblema del COIIE/EIIEO (Colegio Oficial de Ingenieros en Informática del País Vasco/ Euskadiko Informatikako Ingeniarien Elkargo Ofiziala ) . Pero no sólo el COIIE/EIIEO adopta el emblema establecido en dicha resolución, sino que éste se adopta también como im