La máquina Enigma, como Hitler, empezó a popularizarse dos décadas antes de su apoteosis en la 2ª Guerra Mundial. En 1918, el ingeniero eléctrico alemán Arthur Scherbius patentaba su invento para cifrar texto por medio de rotores al que decidió llamar αἴνιγμα (Enigma en griego). El mismo año, un soldado volvía a Munich tras firmarse el armisticio que sellaba la derrota de su país en la Gran Guerra. Sólo un lustro más tarde, nuestros dos protagonistas cobraban fama: la Enigma era presentada al público y la prensa en congresos internacionales, y el soldado se ganaba la simpatía de parte del pueblo alemán tras ser juzgado y encarcelado por liderar un intento de golpe de estado. La continuación de esta historia es bien conocida, así que nos centraremos en la del invento de Scherbius.
1937: la Enigma ya cifra las comunicaciones de otros ejércitos además del alemán, como el italiano y el bando franquista en la Guerra Civil española. Los británicos, aún sin Turing en plantilla, logran descifrar las comunicaciones de los dos últimos porque sus Enigmas no incorporan plugboard (la mayor fuente de complejidad de la máquina, que describiremos más tarde), pero no tienen la cortesía de compartir sus hallazgos con el bando republicano, que perdió sin conseguir resolver el enigma.
Pero ya vale de historia, es hora de explicar en qué consistía el artilugio. Tenía pinta de máquina de escribir moderna a la que se le ha intercambiado el soporte para papel por un conjunto de letras que parece un segundo teclado, sólo que no se pueden pulsar y se iluminan (mostrando la letra cifrada) cuando escribimos en el teclado verdadero. También se le han añadido un montón de agujeros en la parte delantera (éste es el famoso plugboard) y detrás del falso teclado, tres grandes ruedas dentadas con letras o números escritos en el borde (conocidas como rotores). Oculto, está el tercer elemento necesario para comprenderla: el reflector. No formaba parte del diseño original de Enigma, y fue incorporado posteriormente para simplificar enormemente su uso, ya que permitía cifrar y descifrar textos con la misma máquina, sin realizar ningún ajuste. Su función, como sugiere el nombre, era "reflejar" la corriente procedente del rotor izquierdo y hacerla retroceder como vemos en este fabuloso esquema hecho por MesserWoland:
Esta disposición puede parecer suficientemente compleja, pero no lo es. Si la mantenemos constante, nuestro código será fácilmente descifrado mediante análisis de frecuencias. Pero afortunadamente, un vaso es un vaso y los rotores rotan. A un ritmo bastante regular, además: el derecho gira cada vez que pulsamos una tecla; el de enmedio cada 26 vueltas del derecho; y el izquierdo, cada 26 del de enmedio (26 no es un número mágico, es la cantidad de letras del alfabeto internacional y no, no puedes cifrar la ñ con una Enigma). La rotación hace que cambie el recorrido de cada letra que ciframos, como puedes observar en la parte inferior del esquema que acabamos de comentar.
Ya sólo nos queda hablar del plugboard. Se trata sencillamente de un conjunto de agujeros asociados a cada letra que se encuentran al final del circuito. Si conectas mediante un cable los agujeros A y B, cuando el resultado del cifrado sea una A, se iluminará la B y viceversa.
![]() | |
Observa cómo giran los rotores y se iluminan las letras delanteras en este gif de origen desconocido. Fíjate en que, a pesar de pulsar siempre la misma tecla, se van iluminando distintas letras. |
A estas alturas de la entrada puedo confesar que el título era un pelín clickbait, pero no del todo. He "construido" (programado en Python) un modelo históricamente preciso de la más mítica de las Enigmas, la Enigma I, arquetipo de la máquina por ser la más utilizada por los nazis en la guerra y el objetivo principal del equipo de Alan Turing. La inmensa mayoría de simulaciones de la máquina que encontrarás por internet son de la Enigma I. Tenía la peculiaridad de poderse elegir tres de entre cinco rotores disponibles, multiplicando la potencia de la máquina (número de estados posibles) por diez. Este número sale de calcular las posibles combinaciones de sólo tres rotores (3!=6) y compararlas con las posibles combinaciones de tres rotores escogidos entre cinco (5!/2=60). El plugboard permitía hacer diez cambios, lo que multiplicada la complejidad de la máquina (debido a un cálculo combinatorio en el que no voy a entrar) por 26!/((26-20)!·2¹⁰·10!)=150 738 274 937 250
P
Por último, los estados posibles de los tres rotores son 26³=17 576. Multiplicando estos tres números, obtenemos la cantidad de estados posibles de la Enigma I.
![]() |
Genial modelo gráfico para entender el funcionamiento interno de Enigma. Me ayudó mucho a entenderla. Pruébalo online aquí. |
Antes de abandonaros a vuestra suerte con mi código (aunque no es justo decir esto, porque lo he llenado de comentarios para que sea más legible), quiero animaros a proseguir en el impulso que os trajo aquí: construir una Enigma vosotros mismos. Aunque puede ser frustrante a ratos, acaba siendo súper satisfactorio y, lo más importante, permite comprenderla muchísimo mejor que cualquier descripción que leáis, por muy divulgativa que sea. Si queréis orientaros, hay documentación de Enigmas programadas en Ruby, Java, Matlab,... e incluso tutoriales para reconstruir una Enigma física, de las de verdad.
Aquí tenéis mi modelo, que se basa en convertir los rotores, el plugboard y el reflector en listas, y las letras en números que tendrán un doble papel según la fase del cifrado: antes de pasar por el reflector, la letra n será cifrada por cada rotor como el n-ésimo elemento de la lista asociada a dicho rotor. Tras pasar por el reflector, operaremos al revés, cifrando la letra n como la posición del elemento con valor n en esa lista. Todo este trabalenguas se entiende mucho mejor en el propio código.
El giro de los rotores está implementado como sumar uno a la letra y hacer módulo 26 (tras un giro completo, hemos de volver al mismo sitio). La función toma como variables lo mismo que necesitaríamos para operar con una Enigma: las claves (rotores utilizados y posición inicial de los mismos, junto con los cambios del plugboard) y el texto a cifrar. La función devuelve el texto cifrado. He añadido como ejemplo un fragmento de una canción de Labordeta encriptado con otro modelo de la Enigma I. Se llama Banderas rotas y trata sobre derrotas. Evoca sentimientos agridulces: el descifrado de Enigma fue clave para romper banderas con esvásticas y habría sido de gran ayuda para derrotar a sus aliados españoles.
Nunca sabremos si, de habernos dado los británicos la clave de la Enigma franquista, estaríamos hoy cifrando esta canción.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Modelo históricamente preciso de la máquina Enigma I, desarrollada en 1927 y usada por el ejército y fuerzas aéreas alemanas en la Segunda Guerra Mundial | |
# Al igual que la Enigma I, incluye un reflector fijo y tres rotores con posiciones intercambiables (I,II,III), junto con los dos adicionales (IV, V) que fueron desarrollados en 1938 para multiplicar por 10 la seguridad del cifrado (10 * 3! = 5! / 2) | |
# Los rotores, el reflector y el plugboard son representados por listas de números, y las letras por números. | |
# A pesar de que he hecho múltiples pruebas con otros modelos, es posible que el programa tenga fallos. Las críticas y sugerencias son bienvenidas en ferblasco7@gmail.com | |
# Esta es una funcioncita para amenizar la lectura de la función enigma, donde se usan constantemente rotores. | |
def rotor(letra, numero, inverso=False): # Introducimos la "letra" que queremos cifrar (realmente, introducimos el NÚMERO asociado a dicha letra); el número del rotor (1, 2, 3, 4 ó 5) y la posición del rotor (si vemos por la ventanilla una A o un 1 (según el modelo de Enigma), será 0; si vemos B, 1...) | |
# He aquí los circuitos internos de cada rotor, sacados de Wikipedia (recuerda, A=0, B=1,...) | |
I=[4, 10, 12, 5, 11, 6, 3, 16, 21, 25, 13, 19, 14, 22, 24, 7, 23, 20, 18, 15, 0, 8, 1, 17, 2, 9] | |
II=[0, 9, 3, 10, 18, 8, 17, 20, 23, 1, 11, 7, 22, 19, 12, 2, 16, 6, 25, 13, 15, 24, 5, 21, 14, 4] | |
III=[1, 3, 5, 7, 9, 11, 2, 15, 17, 19, 23, 21, 25, 13, 24, 4, 8, 22, 6, 0, 10, 12, 20, 18, 16, 14] | |
# Aunque la Enigma original no los llevaba, en la literatura criptográfica se suele modelizar la Enigma con 5 rotores, incluyendo el IV y V incorporados por el ejército Nazi (ya que eran estas las características de la Enigma estudiada por el equipo de Tuering en Bletchley Park) | |
IV=[4, 18, 14, 21, 15, 25, 9, 0, 24, 16, 20, 8, 17, 7, 23, 11, 13, 5, 19, 6, 10, 3, 2, 12, 22, 1] | |
V=[21, 25, 1, 17, 6, 8, 19, 24, 20, 15, 18, 3, 13, 7, 11, 23, 0, 22, 12, 9, 16, 14, 5, 4, 2, 10] | |
# Siempre es más fácil trabajar con listas (había escrito 'vectores' pero he decidido cambiar el término): | |
tipo=[I,II,III,IV,V] # Como véis, el rotor I es el 0, el rotor II es el 1 y el III es el 2. ¿Cuándo nos pondremos de acuerdo en cuál es el primer número natural? (Yo voto 0) | |
if inverso==False: | |
return tipo[numero-1][(letra)%26] # Devolvemos la letra cifrada con el rotor escogido. La sintaxis puede ser un poco liosa por concatenar listas, pero piensa que tan solo estamos tomando el rotor adecuado de nuestra lista de rotores, y luego tomando la letra adecuada del rotor elegido (teniendo en cuenta su posición) | |
# Otra cosita, ese %26 no es más que expresar el número entre paréntesis en módulo 26 | |
else: | |
return tipo[numero-1].index((letra)%26) | |
# Aquí empieza lo bueno | |
def enigma(texto, numeros, posiciones, cambios=list(range(26))): # Introducir: texto a cifrar (en mayúsculas y sin eñes, porfi); lista de los números de los rotores a utilizar en el orden deseado; vector con sus posiciones iniciales y lista de los cambios de letra del 'plugboard' (por defecto, se considera que no se realizan cambios) | |
# Antes que nada, declaramos las posiciones (letras) en las que cada rotor hace girar al siguiente (no, no giran todos en la Z... se las traen estos alemanes) | |
girador=[16, 4, 21, 9, 25] # Es decir, el rotor I hace girar al de su izquierda cuando pasa de la Q a la R; el rotor II, cuando pasa de la E a la F,... | |
lista=list(texto) # Convertimos el texto en una lista de caracteres. Ejemplo: 'hola' pasa a ser ('h','o','l','a') | |
listacif=[] # Inicializamos la lista donde almacenaremos las letras cifradas | |
letras=[ord(letra)-65 for letra in lista] # ord nos da un numero asociado al caracter. Que no te engañe el nombre, son números, no letras. | |
# Por idiosincrasias en las que no entraremos, a la letra A le corresponde el 65, a la B el 66,... y así en orden alfabético. Por eso, restamos 65 a todos los elementos de la lista. | |
for letra in letras: # Bucle para codificar letra por letra | |
# Lo primero que hace la letra es pasar por el plugboard o cambiador | |
letra=cambios[letra] | |
posiciones[2]=(posiciones[2]+1)%26 # Hacemos girar al rotor derecho, asegurándonos de que si sobrepasa 25 vuelva al 0 (pasar de la A a la Z | |
# Comprobamos si ha de girar algún otro rotor | |
if posiciones[2]==girador[numeros[2]-1]+1: # A ver si gira el de enmedio | |
posiciones[1]=(posiciones[1]+1)%26 | |
if posiciones[1]==girador[numeros[1]-1]: # A ver si gira el de la derecha | |
posiciones[0]=(posiciones[0]+1)%26 | |
posiciones[1]+=1 #El rotor de enmedio puede girar dos veces seguidas. Esta sutileza, que se aprecia claramente en el modelo gráfico recomendado, me dio algún dolor de cabeza. | |
# Hacemos pasar la letra por los tres rotores y el reflector | |
rotor1=rotor((letra+posiciones[2])%26, numeros[2]) # Vamos a hacer pasar el resultado de cada rotor al siguiente. Podríamos haber anidado tres funciones rotor, pero queda más legible así. | |
rotor2=rotor((rotor1-(posiciones[2]-posiciones[1]))%26, numeros[1]) | |
rotor3=rotor(rotor2-(posiciones[1]-posiciones[0])%26, numeros[0]) # (No confundir rotor2 con roto2) (xD) | |
# Ahora vamos con la reflexión. Técnicamente, hay varios reflectores de la enigma, que al fin y al cabo son rotores con la única modificación de que (además de no girar nunca) su entrada y salida de corriente dan al mismo rotor. Por no enmarañar demasiado la función, no he querido meter esa variable y doy por hecho que el reflector está fijo y es el conocido como Reflector B. Esta decisión, además, es compatible con las modelizaciones comunes de Enigma. | |
R=[24, 17, 20, 7, 16, 18, 11, 3, 15, 23, 13, 6, 14, 10, 12, 8, 4, 1, 5, 25, 2, 22, 21, 9, 0, 19] # Como veis, el reflector es como un rotor más | |
reflejado=R[rotor3-posiciones[0]] | |
# Aquí encriptamos al contrario que anteriormente. Antes, la letra a cifrar se tomaba como índice del elemento, siendo éste la letra cifrada. Ejemplo: queremos cifrar A (cuyo número asociado es 0) con el rotor I (que es una lista I=[4, 10, 12,..]), cogíamos el elemento 0 de I (cifrando 'A' como 'E'). | |
# Ahora, para cifrar 'A', cogemos la posición del elemento 0 en I (como 0 es el 21º elemento de I, ciframos la A como V) | |
rotor3=rotor(reflejado+posiciones[0], numeros[0], True) | |
rotor2=rotor(rotor3+(posiciones[1]-posiciones[0])%26, numeros[1], True) | |
rotor1=(rotor(rotor2+(posiciones[2]-posiciones[1])%26, numeros[2], True)-posiciones[2])%26 | |
# Hacemos pasar la letra por el plugboard, por si se le ha asociado un cambio: | |
letra=cambios[rotor1] | |
listacif.append(letra) # Añadimos la letra cifrada a la lista | |
# Cuando salga del bucle, habrá terminado de cifrar todas las letras. Devolvemos por fin la lista cifrada, unida como cadena: | |
listafin=[chr(letra+65) for letra in listacif] # chr es el inverso de ord, pasa de números a las correspondientes letras. | |
listafin=''.join(listafin) # Unimos los elementos de la lista en una cadena. | |
return listafin | |
# Et... voilà! | |
# Algunos ejemplos de cifrado y descifrado con Enigma (ALTAMENTE RECOMENDADO: http://enigmaco.de/enigma/enigma.html) | |
print enigma('EUREKA',[4,5,1],[5,4,17],[11, 1, 2, 20, 4, 5, 6, 7, 8, 9, 10, 0, 12, 13, 14, 15, 16, 17, 18, 19, 3, 21, 22, 23, 24, 25]) | |
print enigma('UFKNRUBGNMFIYEBUQLBNPTTTAQYCBRNUPEDUIXUDSRJOVGWUYTEVLVURHQPKUPVNUWQOFOUIDUQZJTPQFMVIYSBLBZWFHWLSZWFWILUPWXUCGTIALYFBXUVJHFVMQGMXOCNMJZYYEPJNBSIBBTJUZQIEOGLFNVNIOCRWQTGJASOLVWNIEZJNYQNACHRZRTGHCDYFZWSZFCYATXFDGRCESZUNSQDLFJWCNOEZMNSFDUMOBSFOMYAQIINRGPIUEBRHOVLDFOKIVVGTOJYMDGVHVYSEDAAZQMOPZJFYNMCHRHIQI',[1,2,3],[15,23,13],[11, 1, 2, 20, 4, 5, 6, 7, 8, 9, 10, 0, 12, 13, 14, 15, 16, 17, 18, 19, 3, 21, 22, 23, 24, 25]) | |
Si no tienes Python, puedes ejecutar y jugar con esta función online en repl.it, eliminando la primera línea de código (sí, la codificación de caracteres sigue dándonos la lata en pleno 2017).
Fuentes y lectura recomendada
Las imágenes sin pie de foto las saqué en el Science Museum de Londres y en Bletchley Park, dos visitas muy recomendadas.
Esquema de los rotores por MesserWoland - Own work based on Image:Enigma-action.pnj by Jeanot; original diagram by Matt Crypto, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=1794494
https://elpais.com/elpais/2008/10/11/actualidad/1223713021_850215.html
https://crypto.stackexchange.com/questions/33628/how-many-possible-enigma-machine-settings
http://www.cryptomuseum.com/crypto/enigma/i/index.htm#wheels
https://en.wikipedia.org/wiki/Enigma_rotor_details#Rotor_wiring_tables
http://enigmaco.de/enigma/enigma.html
https://en.wikipedia.org/wiki/Adolf_Hitler's_rise_to_power
https://de.wikipedia.org/wiki/Arthur_Scherbius
https://docs.python.org/2/tutorial/index.html
interesante
ResponderEliminarMe alegro de que te lo haya parecido, Luis
Eliminarno me gusta mucho la verdad
ResponderEliminarOhh, ¿qué es lo que no te ha gustado?
ResponderEliminarEnhorabuena. Puedes sentirte muy muy orgulloso de tu trabajo. Es absolutamente genial. Y muchísimas gracias por compartirlo con todos.
ResponderEliminar