Przejdź do treści

Symulator bębenków

Oto interaktywny symulator bębenków (wirników) maszyny Enigma napisany w języku Python. Program wiernie odtwarza mechaniczne obracanie się bębenków (w tym efekt double stepping), przejście sygnału elektrycznego przez bębenki, odbicie od walca odwracającego (reflektora) oraz drogę powrotną.

Kod symulatora (Python 3)

Możesz skopiować poniższy kod i uruchomić go w dowolnym środowisku Python (np. IDLE, PyCharm, VS Code):

`Enigma_Rotor_Similation.py

Python
class EnigmaRotor:  
    def __init__(self, wiring, notch, name=""):  
        self.alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
        # Okablowanie wewnętrzne bębenka  
        self.wiring = wiring  
        # Litera, przy której następuje obrót kolejnego bębenka  
        self.notch = notch  
        self.name = name  
        # Aktualna pozycja bębenka (0 = A, 1 = B, itd.)  
        self.position = 0  

    def step(self):  
        """Obraca bębenek o jedną pozycję."""  
        self.position = (self.position + 1) % 26  
        return self.position == 0  

    def get_current_letter(self):  
        return self.alphabet[self.position]  

    def forward(self, letter_index):  
        """Przejście sygnału od prawej do lewej strony (wejście -> wyjście)."""  
        shift = self.position  
        # Uwzględnienie przesunięcia bębenka na wejściu  
        core_input = (letter_index + shift) % 26  
        # Szyfrowanie przez wewnętrzne okablowanie  
        core_output_letter = self.wiring[core_input]  
        core_output = self.alphabet.index(core_output_letter)  
        # Uwzględnienie przesunięcia bębenka na wyjściu  
        return (core_output - shift) % 26  

    def backward(self, letter_index):  
        """Przejście sygnału w drodze powrotnej (od lewej do prawej)."""  
        shift = self.position  
        core_input = (letter_index + shift) % 26  
        # Odwrócenie szyfrowania (szukanie litery w okablowaniu)  
        core_output_letter = self.alphabet[core_input]  
        core_output = self.wiring.index(core_output_letter)  
        return (core_output - shift) % 26  


class EnigmaMachine:  
    def __init__(self, rotor_left, rotor_middle, rotor_right, reflector):  
        self.r_left = rotor_left  
        self.r_middle = rotor_middle  
        self.r_right = rotor_right  
        self.reflector = reflector  # Walec odwracający (nie obraca się)  
        self.alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  

    def set_positions(self, pos_str):  
        """Ustawia początkowe litery bębenków (np. 'AAA')."""  
        self.r_left.position = self.alphabet.index(pos_str[0].upper())  
        self.r_middle.position = self.alphabet.index(pos_str[1].upper())  
        self.r_right.position = self.alphabet.index(pos_str[2].upper())  

    def _rotate_rotors(self):  
        """Mechanizm obrotu z uwzględnieniem podwójnego skoku (double stepping)."""  
        # Sprawdzenie, czy bębenki stoją na pozycjach zapadek (notches)  
        right_at_notch = self.r_right.get_current_letter() == self.r_right.notch  
        middle_at_notch = self.r_middle.get_current_letter() == self.r_middle.notch  

        # Efekt double stepping: jeśli środkowy jest na notch, obraca siebie i lewy  
        if middle_at_notch:  
            self.r_left.step()  
            self.r_middle.step()  
        # Jeśli prawy jest na notch, obraca środkowy  
        elif right_at_notch:  
            self.r_middle.step()  

        # Prawy bębenek obraca się zawsze przy każdym naciśnięciu klawisza  
        self.r_right.step()  

    def encrypt_letter(self, letter):  
        if letter.upper() not in self.alphabet:  
            return letter  # Ignoruj znaki spoza alfabetu (np. spacje)  

        # 1. Obrót mechaniczny bębenków przed przepływem prądu  
        self._rotate_rotors()  

        # 2. Sygnał wchodzi z klawiatury  
        signal = self.alphabet.index(letter.upper())  

        # 3. Przejście do przodu: prawy -> środkowy -> lewy  
        signal = self.r_right.forward(signal)  
        signal = self.r_middle.forward(signal)  
        signal = self.r_left.forward(signal)  

        # 4. Reflektor (Walec odwracający)  
        reflector_letter = self.reflector[signal]  
        signal = self.alphabet.index(reflector_letter)  

        # 5. Przejście powrotne: lewy -> środkowy -> prawy  
        signal = self.r_left.backward(signal)  
        signal = self.r_middle.backward(signal)  
        signal = self.r_right.backward(signal)  

        # 6. Podświetlenie litery na tablicy  
        return self.alphabet[signal]  

    def encrypt_text(self, text):  
        return "".join([self.encrypt_letter(c) for c in text])  


# --- KONFIGURACJA HISTORYCZNYCH BĘBENKÓW WOJSKOWYCH (Wehrmacht) ---  
# Oryginalne okablowanie i punkty obrotu (notches) dla bębenków I, II, III oraz reflektora B  
rotor_I = EnigmaRotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", "Q", "I")  
rotor_II = EnigmaRotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", "E", "II")  
rotor_III = EnigmaRotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", "V", "III")  
reflector_B = "YRUHQSLDPXNGOKMIEBFZCWVJAT"  

# Tworzenie maszyny z bębenkami w kolejności: I (lewy), II (środkowy), III (prawy)  
enigma = EnigmaMachine(rotor_I, rotor_II, rotor_III, reflector_B)  

# --- PRZYKŁAD DZIAŁANIA ---  
print("--- SYMULACJA ENIGMY ---")  
wiadomosc = "WIKIPEDIA"  

# Ustawienie klucza początkowego bębenków na AAA  
enigma.set_positions("AAA")  
zaszyfrowana = enigma.encrypt_text(wiadomosc)  
print(f"Tekst jawny:   {wiadomosc}")  
print(f"Zaszyfrowany:  {zaszyfrowana}")  

# Deszyfrowanie: Enigma była symetryczna. Przy identycznym ustawieniu początkowym  
# wpisanie szyfru dawało tekst jawny.  
enigma.set_positions("AAA")  
odszyfrowana = enigma.encrypt_text(zaszyfrowana)  
print(f"Odszyfrowany:  {odszyfrowana}")  

Jak działa ta symulacja?

  1. Historyczne okablowanie: Użyte w kodzie ciągi znaków (np. EKMFLGDQVZNTOWYHXUSPAIBRCJ dla bębenka I) odpowiadają rzeczywistym maszynom używanym przez niemiecki Wehrmacht.
  2. Krok przed szyfrem: Zgodnie z prawdą historyczną, bębenki wykonują obrót zanim prąd elektryczny przejdzie przez układ styków.
  3. Double Stepping: Kod uwzględnia specyficzną wadę mechaniczną Enigmy. Środkowy bębenek, znajdując się na swojej pozycji zwrotnej (notch), potrafi obrócić się dwa razy pod rząd przy kolejnych kliknięciach.
  4. Symetryczność: Maszyna nie posiadała trybu "szyfruj" i "deszyfruj". Dzięki walcowi odwracającemu (reflektorowi), po ustawieniu tych samych pozycji początkowych (np. AAA), wpisanie zaszyfrowanego tekstu automatycznie zwraca pierwotną wiadomość.

Jeśli chcesz dowiedzieć się więcej o matematycznych podstawach tego mechanizmu, możesz przeczytać o pracach Mariana Rejewskiego i teorii permutacji na stronach Instytutu Pamięci Narodowej.
Jeżeli chcesz rozbudować ten kod, daj mi znać. Mogę pomóc Ci dopisać:

  • Łącznicę wtyczkową (Steckerbrett), która zamieniała pary liter przed wejściem do bębenków.
  • Ustawienia pierścieni (Ringstellung), zmieniające przesunięcie wewnętrznego okablowania względem zewnętrznego alfabetu.
  • Wizualizację w konsoli, pokazującą stan bębenków krok po kroku.

Oto rozbudowana wersja symulatora Enigmy w Pythonie, wzbogacona o ** dynamiczną wizualizację przepływu sygnału w konsoli**.
Dodatkowo, z racji tego, że poszukujesz ilustracji do wykładu o Johnie von Neumannie, na samym początku umieściłem schemat blokowy jego przełomowej architektury. Choć von Neumann nie projektował Enigmy, to jego koncepcja komputera z programem przechowywanym w pamięci bezpośrednio wyrosła z wojennych wysiłków kryptologicznych (takich jak brytyjski projekt Colossus w Bletchley Park czy amerykański ENIAC), w których brał czynny udział jako konsultant.

Architektura Maszyny Von Neumanna (Ilustracja na Wykład)

Poniższy schemat logiczny idealnie nadaje się do slajdu prezentującego tzw. Architekturę EDVAC / IAS (1945 r.). Pokazuje ona fundamentalny podział komputera na procesor (rejestry, ALU, CU), pamięć operacyjną oraz urządzenia wejścia/wyjścia:


Kod Symulatora Enigmy z Wizualizacją Przepływu Sygnału

Poniższy skrypt po uruchomieniu w terminalu pokazuje dokładnie, jak litera z klawiatury wędruje przez poszczególne bębenki w prawo, odbija się od reflektora (Reflector) i wraca przez bębenki w lewo, aż podświetli końcową literę.

Python
class EnigmaRotor:  
    def __init__(self, wiring, notch, name=""):  
        self.alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
        self.wiring = wiring  
        self.notch = notch  
        self.name = name  
        self.position = 0  

    def step(self):  
        self.position = (self.position + 1) % 26  
        return self.position == 0  

    def get_current_letter(self):  
        return self.alphabet[self.position]  

    def forward(self, letter_index):  
        shift = self.position  
        core_input = (letter_index + shift) % 26  
        core_output_letter = self.wiring[core_input]  
        core_output = self.alphabet.index(core_output_letter)  
        return (core_output - shift) % 26  

    def backward(self, letter_index):  
        shift = self.position  
        core_input = (letter_index + shift) % 26  
        core_output_letter = self.alphabet[core_input]  
        core_output = self.wiring.index(core_output_letter)  
        return (core_output - shift) % 26  


class EnigmaMachineVisual:  
    def __init__(self, rotor_left, rotor_middle, rotor_right, reflector):  
        self.r_left = rotor_left  
        self.r_middle = rotor_middle  
        self.r_right = rotor_right  
        self.reflector = reflector  
        self.alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  

    def set_positions(self, pos_str):  
        self.r_left.position = self.alphabet.index(pos_str[0].upper())  
        self.r_middle.position = self.alphabet.index(pos_str[1].upper())  
        self.r_right.position = self.alphabet.index(pos_str[2].upper())  

    def _rotate_rotors(self):  
        right_at_notch = self.r_right.get_current_letter() == self.r_right.notch  
        middle_at_notch = self.r_middle.get_current_letter() == self.r_middle.notch  

        if middle_at_notch:  
            self.r_left.step()  
            self.r_middle.step()  
        elif right_at_notch:  
            self.r_middle.step()  

        self.r_right.step()  

    def encrypt_letter_with_visualization(self, letter):  
        if letter.upper() not in self.alphabet:  
            return letter  

        # 1. Odczyt pozycji przed obrotem do wizualizacji  
        pos_before = f"{self.r_left.get_current_letter()}{self.r_middle.get_current_letter()}{self.r_right.get_current_letter()}"  

        # 2. Obrót bębenków  
        self._rotate_rotors()  
        pos_after = f"{self.r_left.get_current_letter()}{self.r_middle.get_current_letter()}{self.r_right.get_current_letter()}"  

        # 3. Przepływ sygnału (indeksy)  
        idx_in = self.alphabet.index(letter.upper())  

        # Droga do przodu  
        idx_r_fwd = self.r_right.forward(idx_in)  
        idx_m_fwd = self.r_middle.forward(idx_r_fwd)  
        idx_l_fwd = self.r_left.forward(idx_m_fwd)  

        # Reflektor  
        ref_letter = self.reflector[idx_l_fwd]  
        idx_ref = self.alphabet.index(ref_letter)  

        # Droga powrotna  
        idx_l_bkw = self.r_left.backward(idx_ref)  
        idx_m_bkw = self.r_middle.backward(idx_l_bkw)  
        idx_r_bkw = self.r_right.backward(idx_m_bkw)  

        out_letter = self.alphabet[idx_r_bkw]  

        # --- DRUKOWANIE WIZUALIZACJI DROGI SYGNAŁU ---  
        print("=" * 65)  
        print(f" NACIŚNIĘTO: '{letter.upper()}'  |  Pozycja bębenków: {pos_before} -> {pos_after}")  
        print("=" * 65)  
        print(" ETAP SZYFROWANIA         | DROGA ELEKTRYCZNA SYGNAŁU")  
        print("-" * 65)  
        print(f" 1. Wejście z klawiatury  | {letter.upper()}")  
        print(f" 2. Bębenek Prawy ({self.r_right.name})    | {letter.upper()} ---> {self.alphabet[idx_r_fwd]}")  
        print(f" 3. Bębenek Środkowy ({self.r_middle.name}) | {self.alphabet[idx_r_fwd]} ---> {self.alphabet[idx_m_fwd]}")  
        print(f" 4. Bębenek Lewy ({self.r_left.name})   | {self.alphabet[idx_m_fwd]} ---> {self.alphabet[idx_l_fwd]}")  
        print(f" 5. REFLEKTOR (Zwrot)     | {self.alphabet[idx_l_fwd]} ===> {ref_letter}")  
        print(f" 6. Bębenek Lewy (Powrót) | {ref_letter} ---> {self.alphabet[idx_l_bkw]}")  
        print(f" 7. Bębenek Środ. (Powrót)| {self.alphabet[idx_l_bkw]} ---> {self.alphabet[idx_m_bkw]}")  
        print(f" 8. Bębenek Prawy (Powrót)| {self.alphabet[idx_m_bkw]} ---> {out_letter}")  
        print("-" * 65)  
        print(f" WYNIK (Tablica żarówek)  | ROZŚWIETLONA LITERA: [{out_letter}]")  
        print("=" * 65 + "\n")  

        return out_letter  

    def encrypt_text_visual(self, text):  
        output = []  
        for c in text:  
            if c.isalpha():  
                output.append(self.encrypt_letter_with_visualization(c))  
        return "".join(output)  


# Definicje bębenków historycznych  
r1 = EnigmaRotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", "Q", "I")  
r2 = EnigmaRotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", "E", "II")  
r3 = EnigmaRotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", "V", "III")  
ref_B = "YRUHQSLDPXNGOKMIEBFZCWVJAT"  

# Inicjalizacja maszyny  
enigma_vis = EnigmaMachineVisual(r1, r2, r3, ref_B)  

# Ustawienie bębenków w pozycji startowej (np. 'AAB')  
# Wybrałem AAB, aby szybko zaprezentować obrót środkowego bębenka, gdy prawy przejdzie z V na W.  
enigma_vis.set_positions("AAV")  

print("URUCHAMIANIE SYMULACJI DLA SŁOWA: 'KOD'\n")  
enigma_vis.encrypt_text_visual("KOD")  

Jak wygląda przykładowy efekt w konsoli?

Gdy uruchomisz program, dla każdej litery otrzymasz przejrzystą mapę drogi prądu w maszynie:

Text Only
=================================================================  
 NACIŚNIĘTO: 'K'  |  Pozycja bębenków: AAV -> AAW  
=================================================================  
 ETAP SZYFROWANIA         | DROGA ELEKTRYCZNA SYGNAŁU  
-----------------------------------------------------------------  
 1. Wejście z klawiatury  | K  
 2. Bębenek Prawy (III)    | K ---> B  
 3. Bębenek Środkowy (II) | B ---> X  
 4. Bębenek Lewy (I)   | X ---> U  
 5. REFLEKTOR (Zwrot)     | U ===> F  
 6. Bębenek Lewy (Powrót) | F ---> J  
 7. Bębenek Środ. (Powrót)| J ---> M  
 8. Bębenek Prawy (Powrót)| M ---> Z  
-----------------------------------------------------------------  
 WYNIK (Tablica żarówek)  | ROZŚWIETLONA LITERA: [Z]  
=================================================================  

Dlaczego to idealny kontekst pod wykład o von Neumannie?

  1. Automaty stanów: Enigma to klasyczny mechaniczny automat skończony. Von Neumann w swoich późniejszych pracach badał teorię automatów (tzw. Cellular Automata / automaty komórkowe).
  2. Ewolucja programowania: Enigma miała stały program zaszyty w okablowaniu (aby go zmienić, trzeba było fizycznie przełożyć bębenki lub kable w łącznicy). Von Neumann zrewolucjonizował świat pomysłem, że kod powinien być elastyczny i znajdować się w tej samej pamięci co dane, co eliminuje sztywne ograniczenia maszyn szyfrujących i wczesnych komputerów.

Jeśli potrzebujesz zmodyfikować schemat architektury von Neumanna lub dodać do symulatora konkretne elementy przydatne na Twój wykład (np. zliczanie permutacji), daj mi znać!