Enigma

Enigma bola prenosným elektromechanickým šifrovacím strojom pracujúcim na princípe otáčajúcich sa rotorov. Používala sa v niekoľkých modifikovaných verziách najmä nemeckými ozbrojenými silami pred vypuknutím a počas druhej svetovej vojny.

Šifra Enigmy bola Nemcami považovaná za neprelomiteľnú a absolútne bezpečnú, prečo tomu tak bolo si ukážeme na nasledvnom príklade.

Caesarova šifra (50 p. n. l.) Pri tejto šifre ide o klasický substitučný systém, kde sa jeden znak nahrádza iným. Povedzme, že znak bude nahradený písmenom z abecedy, ktoré sa nachádza o tri pozície vpravo.


In [ ]:
ALPHABET =   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
SUBSTITUTE = 'DEFGHIJKLMNOPQRSTUVWXYZABC'

secret_message = 'Tento text je zasifrovany substitucnou sifrou avsak rozlustit ho nie je vobec zlozite'

Zašifrovanie tejto spravy je jednoduché a taktiež jej rozšifrovanie. Na jednom mieste mohlo byť 26 písmen, pri útoku hrubou silou teda neobstojí dlho. Ďalšia slabosť tejto šifry a jej podobných boli lingvistické znaky. Analýza sa robila na základe výskytu znakov v šifre.


In [ ]:
secret_message = secret_message.strip().replace(' ', '')

encrypted_message = ''.join([SUBSTITUTE[ALPHABET.find(i.upper())] for i in secret_message])
    
print('Šifrovaný text: %s' % encrypted_message)

Obdobné substitučné šifry, spolu s tranaspozičnými šiframi a ich kombináciami sa využívali až do konca 19 storočia, kedy prichádzajú prvé šifrovacie stroje. Hlavný rozdiel u Enigmy je že, pri stlačení jedného písmena dva krát po sebe, operátor dostal dva rôzne znaky.

Tak ako iné, na rotoroch založené šifrovacie stroje, je Enigma kombináciou elektrického a mechanického systému. Mechanický systém je zložený z klávesnice, sady rotačných valcov (alebo rotorov) zoradených na jednej osi a krokového mechanizmu, ktorý otáča postupne jedným alebo niekoľkými rotormi pri každom stlačení.

Šifrovanie prebieha takto: po stlačení klávesy sa uzavrie elektrický obvod. Prúd preteká rôznymi súčasťami, až sa nakoniec rozsvieti jedna z mnohých žiaroviek na paneli, čím indikuje výsledné zašifrované písmeno.

Rotory

Rotory (tiež rotačné valce) sú srdcom šifrovacieho stroja. Sú uložené vedľa seba, kolíky jedného z nich sa dotýkajú s plochými elektrickými kontaktmi susedného, uzatvárajúc elektrický obvod. Vnútri rotora sa nachádza 26 prevodov spájajúcich zhodne so zvolenou kombináciou kolíky z jednej strany s kontaktmi z druhej. Spôsob káblového prepojenia je iný pre každý typ rotora.

Enigmy boli vybavené niekoľkými typmi rotorov, no v čase zavedenia ich mali iba tri, krátko po vypuknutí vojny bola súprava rotorov rozšírená na 5, z ktorých sa do použitia v stroji vyberali tri. Kvôli odlíšeniu boli rotory označené rímskymi číslicami I, II, III, IV a V.

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
I E K M F L G D Q V Z N T O W Y H X U S P A I B R C J
II A J D K S I R U X B L H W T M C Q G Z N P Y F V O E
III B D F H J L C P R T X V Z N Y E I W G A K M U S Q O
IV E S O V P Z J A Y Q U I R H X L N F T G K D C M W B
V V Z B R G I T Y U P S D N H L X A W M J Q O F E C K

Každý z nich mal jeden zárez umiestnený v rôznych miestach abecedného krúžku, slúžiaci na jeho otáčanie, vďaka čomu značne narastala zložitosť šifry. Viera konštruktérov stroja, že zavedenie zárezu prakticky znemožní dekódovanie správ sa ukázala mylná, pretože vďaka zavedeniu poľskej „hodinovej metódy“ Jerzyho Różyckiho a neskôr britskej metódy nazvanej „banburismus“ bola nakoniec šifra Enigmy prelomená.

Rotor Zárez Následok
I Q Ak sa rotor otočí z Q na R, nasledujúci rotor je otočený
II E Ak sa rotor otočí z E na F, nasledujúci rotor je otočený
III V Ak sa rotor otočí z V na W, nasledujúci rotor je otočený
IV J Ak sa rotor otočí z J na K, nasledujúci rotor je otočený
V Z Ak sa rotor otočí z Z na A, nasledujúci rotor je otočený

In [ ]:
ROTOR_I = 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'
ROTOR_II = 'AJDKSIRUXBLHWTMCQGZNPYFVOE'
ROTOR_III = 'BDFHJLCPRTXVZNYEIWGAKMUSQO'
ROTOR_IV = 'ESOVPZJAYQUIRHXLNFTGKDCMWB'
ROTOR_V = 'VZBRGITYUPSDNHLXAWMJQOFECK'

ROTOR_I_NOTCH = 'Q'
ROTOR_II_NOTCH = 'E'
ROTOR_III_NOTCH = 'V'
ROTOR_IV_NOTCH = 'J'
ROTOR_V_NOTCH = 'Z'

Reflektor

Úlohou reflektora bolo spojenie elektrických kontaktov do párov a spätné presmerovanie signálu cez sústavu rotorov, ale inou cestou. Reflektor Enigmy je symetrický, čo značí, že zašifrovaná informácia je rozkódovávaná po jej vyslaní tou istou cestou (akoby znovu zakódovaná). Reflektor dáva Enigme ešte jednu vlastnosť, menovite, nikdy nijaké písmeno pred zašifrovaním nemôže mať tú istú hodnotu ako po zašifrovaní (čiže A nikdy nebude po zašifrovaní vystupovať ako A). Vyplýva to z konštrukcie reflektora, ktorý vždy zamieňa znaky pármi. Táto vlastnosť, aj keď mala byť výhodou, je v skutočnosti kryptografickou chybou a bola využitá na prelomenie kódu Enigmy.


In [ ]:
REFLECTOR = 'ABCDEFGDIJKGMKMIEBFTCVVJAT'

Rozvodná doska

Rozvodná doska umožňovala rôznorodé okáblovanie, ktoré mohlo byť menené operátorom. Zmena okáblovania rozvodnej dosky napriek svojej jednoduchosti značne zvyšovala komplikáciu šifry Enigmy, dokonca viac než dodatočný rotor. Enigma bez rozvodnej dosky mohla byť rozkódovaná relatívne jednoduchým spôsobom, dokonca aj ručne, ale zmena písmen pomocou nej prinútila dešifrantov k použitiu špeciálnych dekódovacích strojov.

Python Enigma

Na začiatok je potrebné enigmu nastaviť. Zadefinujeme si list rotorov v ktorom, budeme mat dictionary s nastaveniami rotora.


In [ ]:
ENIGMA = (
{
    'values': ROTOR_I,
    'position': 'M'
}, 
{    
    'values': ROTOR_II,
    'position': 'C'
},
{
    'values': ROTOR_III,
    'position': 'K'
})

Zadefinujeme si dve funkcie, ktoré predstavujú prúd prechádzajúci rotormi (z ľava do prava a následne obratený v reflektore z prava do ľava). Funkcia vráti substituované písmeno z abecedy v závislosti od umiestnenia na rotore:


In [ ]:
def rotor_right_to_left(letter, rotor):
    # Vrat poziciu pismena na rotore podla pozicie pismena v abecede
    return rotor[ALPHABET.index(letter)]

def rotor_left_to_right(letter, rotor):
    # Vrat poziciu pismena v abecede podla pozicie pismena na rotore
    return ALPHABET[rotor.index(letter)]

Ďalšia funkcia bude predstavovať reflektor. Vieme na ktorej poziíci vstupuje prúd do reflektora a potrebujeme vrátiť ďalšiu pozíciu písmena do ktorého vstupoval prúd, t.j. pozíciu odkiaľ bude prúd vystupovať späť do rotorov:


In [ ]:
def reflector(position):
    # Poloha vstupneho pismena na reflektore
    entry_letter = REFLECTOR[position]
    # Poloha vystupneho pismena na reflektore
    exit_letter = REFLECTOR.index(entry_letter)
    
    # Ak sa vystupna poloha rovna vstupnej, vrat druhu polohu pismena
    # Reflektor obsahuje dane pismeno vzdy dva krat
    if exit_letter == position:
        return REFLECTOR.rindex(entry_letter)
    return exit_letter

Vytvoríme si dve pomocné funkcie, ktoré nám uľahčia prácu s abecedou. Keďže rotory boli kruhové, po Z vždy nasleduje A. Prvá funkcia bude zisťovať či posun v abecede pretočí abecedu alebo nie, druhá robí v podstate to isté s rozdielom že na zisťovanie nepoužíva písmeno, ale jeho pozíciu na rotore:


In [ ]:
def alpha_rotate(letter, shift):
    # Hodnota pismena v ASCII
    ascii_letter = ord(letter)
    
    # Ak je hodnota ASCII aj s posunom vacsia ako 90, pretocili sme sa cez Z a musime pocitat od A
    if ascii_letter + shift > 90:
        # Vrat pismeno posunute o "shift" znakov, s pretocenim abecedy od zaciatku
        return chr(64 + (ascii_letter - 90 + shift))
    else:
        # Vrat pismeno posunute o "shift" znakov
        return chr(ascii_letter + shift)

def calculate_move(position, letter):
    # Hodnota pismena na ktorom sa nachadza rotor v ASCII
    ascii_position = ord(position)
    # Hodnota vystupneho pismena
    ascii_letter = ord(letter)
    
    # Ak je pismeno na rotore dalej ako vystupne pismeno, musime sa pretocit
    if ascii_position > ascii_letter:
        # Od poctu znakov v abecede odpocitame poziciu rotora a pripocitame vystupne pismeno
        shift = 26 - (ascii_position - 65) + (ascii_letter - 65)
    else:
        # Od vystupneho pismena, odpocitame poziciu rotora
        shift = (ascii_letter - 65) - (ascii_position - 65)
    
    # Vratime celo ciselny zvysok po deleni vysledneho posunu, ak by sa rotor pretocil
    return shift % 26

Potrebujeme ešte funkciu, ktorá bude otáčať rotorom, ktorej návratová hodnota bude boolean označujúci, či rotor prešiel cez zárez a je potrebné posunúť nasledujúci rotor.


In [ ]:
def rotate_rotor(rotor):
    # Posun pozicie rotora o jednu polohu
    rotor['position'] = chr(ord(rotor['position']) + 1)
    
    # Kontrola zarezu, ci treba posunut aj nasledujuci rotor
    if rotor['values'] == ROTOR_I and rotor['position'] == 'R':
        return True
    elif rotor['values'] == ROTOR_II and rotor['position'] == 'F':
        return True
    elif rotor['values'] == ROTOR_III and rotor['position'] == 'W':
        return True
    elif rotor['values'] == ROTOR_IV and rotor['position'] == 'K':
        return True
    elif rotor['values'] == ROTOR_V and rotor['position'] == 'A':
        return True
    
    return False

A nakoniec si všetky naše funkcie dáme dohromady a vytvoríme ešte jednu, ktorá bude schopná prekladať celý text.


In [ ]:
def convert(letter):
    # Vypocitame posun na prvom valci, od inicializacneho pismena
    shift = ord(letter) - 65
    # Pomocna konstanta, ktora oznacuje ci sa ma otocit aj nasledujuci valec
    rotate_next = False
    
    # Prechadzame rotormi v enigme od konca na zaciatok
    for i, rotor in enumerate(reversed(ENIGMA)):
        # Najskor sa posunie rotor o jednu poziciu pri stlaceni klavesy
        if i == 0 or rotate_next:
            rotate_next = rotate_rotor(rotor)            

        conv_letter = alpha_rotate(rotor['position'], shift)
        letter = rotor_right_to_left(conv_letter, rotor['values'])
        shift = calculate_move(rotor['position'], letter)

    shift = reflector(shift)

    # Reflektovany prud prechadza rotormi od zaciatku na koniec
    for rotor in ENIGMA:
        conv_letter = alpha_rotate(rotor['position'], shift)
        letter = rotor_left_to_right(conv_letter, rotor['values'])
        shift = calculate_move(rotor['position'], letter)

    return ALPHABET[shift]

def encrypt(text):
    # Text ktory prejde enigmou
    encrypted_text = []
    # Velmi slabe osetrenie vstupu
    text = text.strip().replace(' ', '')
    
    for i, letter in enumerate(text, 1):
        if not letter.isalpha():
            continue
        
        # Uloz pismeno, ktore preslo enigmou
        encrypted_text.append(convert(letter.upper()))
        
        # Enigma standardne, robila text bloky po 4 znakoch
        if i % 4 == 0:
            encrypted_text.append(' ')


            return ' '.join(encrypted_text)

In [ ]:
#################################################
# Operator, ktory chce zasifrovat spravu musi nastavit Enigmu zalozenim rotorov do vychodzich pozicii
####
ENIGMA = (
{
    'values': ROTOR_I,
    'position': 'M'
},
{
    'values': ROTOR_II,
    'position': 'C'
},
{
    'values': ROTOR_III,
    'position': 'K'
})

message_to_encrypt = 'Enigma Revealed'
encrypted_message = encrypt(message_to_encrypt)

print('Zasifrovana sprava:', encrypted_message)

#################################################
# Operator, ktory obdrzi spravu musi nastavit Enigmu do rovnakeho vychodzieho nastavenia ako pri sifrovani
####
ENIGMA = (
{
    'values': ROTOR_I,
    'position': 'M'
},
{
    'values': ROTOR_II,
    'position': 'C'
},
{
    'values': ROTOR_III,
    'position': 'K'
})

print('Desifrovana sprava:', encrypt(encrypted_message))