Jak działa ten kod
Pętla czyta tekst znak po znaku. Funkcja ord() zamienia literę na liczbę: ord("A") to 65, a ord("a") to 97. Po odjęciu tej podstawy dostajesz miejsce litery w alfabecie, od 0 do 25.
Po dodaniu przesunięcia % 26 zawija wynik, więc przy przesunięciu 3 litera Z trafia na C. Na koniec chr() robi z liczby z powrotem literę. Jeśli sam szyfr jest dla ciebie nowy, zajrzyj do przewodnika, w którym nie ma kodu.
Spacje, znaki interpunkcyjne i cyfry idą gałęzią else i zostają bez zmian. To samo dotyczy polskich liter, dlatego „ś” w przykładzie się nie zmienia. Odpowiada za to warunek isascii(): bez niego Python uzna ś za zwykłą małą literę, wyliczy z niej przypadkową literę A–Z i tekstu nie da się już odszyfrować.
Deszyfrowanie w Pythonie
Druga funkcja nie jest potrzebna. Odszyfrowanie to takie samo przesunięcie w drugą stronę, więc wystarczy podać liczbę ujemną:
print(caesar("Zlwdm śzlhflh!", -3))# Witaj świecie!Działa to dzięki temu, że % w Pythonie nie zwróci tu liczby ujemnej: (0 - 3) % 26 daje 23, czyli A wraca na X. W C, Javie i JavaScripcie wyszłoby -3, dlatego wersja w JavaScripcie potrzebuje dodatkowego kroku.
Program pobierający dane od użytkownika
Zadania domowe często wymagają programu, który wczytuje tekst i przesunięcie z klawiatury. Dopisz to pod funkcją:
text = input("Tekst: ")shift = int(input("Przesunięcie: "))mode = input("Szyfrować czy odszyfrować? (s/o): ") if mode == "o": shift = -shift print(caesar(text, shift))input() zawsze zwraca napis, więc przesunięcie trzeba przepuścić przez int(). Jeśli ktoś wpisze słowo zamiast liczby, Python zatrzyma się z błędem ValueError.
Łamanie szyfru metodą brute force
Przy 26 literach do sprawdzenia jest tylko 25 przesunięć, więc krótka pętla odczyta wiadomość bez klucza. Wystarczy znaleźć w wynikach linijkę, która ma sens:
def crack(text): for shift in range(1, 26): print(shift, caesar(text, -shift)) crack("Zlwdm śzlhflh!")# 1 Ykvcl śykgekg!# 2 Xjubk śxjfdjf!# 3 Witaj świecie!# ...Więcej o tej metodzie przeczytasz w artykule o ataku brute force, a łamacz online zrobi to samo w przeglądarce.
Krótsza wersja z str.translate
Gdy rozumiesz już pętlę, możesz zastąpić ją tabelą zamiany. str.maketrans() łączy każdą literę z jej przesuniętą wersją, a translate() stosuje tę tabelę do całego tekstu. Polskie litery także tutaj zostają bez zmian.
from string import ascii_lowercase as lower, ascii_uppercase as upper def caesar(text, shift): shift %= 26 table = str.maketrans( lower + upper, lower[shift:] + lower[:shift] + upper[shift:] + upper[:shift], ) return text.translate(table)Wyniki są takie same, a przy długich tekstach zwykle szybciej, bo tabela powstaje tylko raz. W pierwszym zadaniu łatwiej jednak wytłumaczyć pętlę.
Szyfr Cezara z polskimi znakami
Żeby przesuwać także ą, ć czy ż, zapisz alfabet w napisie i szukaj każdego znaku przez index(). To ten sam alfabet, 35 znaków, którego używa nasze narzędzie:
ALPHABET = "aąbcćdeęfghijklłmnńoópqrsśtuvwxyzźż" def caesar_alphabet(text, shift): result = "" for char in text: letter = char.lower() if letter in ALPHABET: index = (ALPHABET.index(letter) + shift) % len(ALPHABET) new = ALPHABET[index] result += new.upper() if char.isupper() else new else: result += char return result print(caesar_alphabet("Zażółć gęślą jaźń", 3))# Acbrńę jhvnć łcąpZawijanie korzysta teraz z len(ALPHABET), więc nie musisz nigdzie wpisywać 35. Wynik możesz porównać w narzędziu szyfru Cezara po wybraniu polskiego alfabetu.
Szyfrowanie pliku tekstowego
Wczytaj plik, przekaż jego zawartość do funkcji i zapisz wynik w nowym pliku. Ustawienie encoding="utf-8" sprawia, że polskie znaki nie zepsują się w żadnym systemie.
with open("message.txt", encoding="utf-8") as source: text = source.read() with open("encrypted.txt", "w", encoding="utf-8") as target: target.write(caesar(text, 3))Ten sam szyfr jako klasa
Klasa przydaje się, gdy jednego klucza używasz wiele razy albo gdy prowadzący wymaga kodu obiektowego. Przechowuje przesunięcie i korzysta z funkcji z góry strony:
class CaesarCipher: def __init__(self, shift=3): self.shift = shift def encrypt(self, text): return caesar(text, self.shift) def decrypt(self, text): return caesar(text, -self.shift) cipher = CaesarCipher(5)print(cipher.encrypt("Witaj świecie!"))# Bnyfo śbnjhnj!Testowanie kodu
Kilka linijek z assert wyłapie większość błędów: przejście poza Z, zachowanie wielkości liter, pozostawienie interpunkcji i powrót do tekstu wyjściowego. Jeśli wszystko gra, program wypisze OK.
assert caesar("ABC", 3) == "DEF"assert caesar("xyz", 3) == "abc"assert caesar("Hello, World!", 5) == "Mjqqt, Btwqi!"assert caesar(caesar("Python", 7), -7) == "Python"assert caesar("A", 26) == "A"assert caesar("é", 3) == "é"print("OK")Najczęstsze błędy
Klasyczny błąd to chr(ord(char) + shift) bez modulo: przy przesunięciu 3 litera Z zamienia się wtedy w symbol ]. Inny to użycie podstawy 65 dla wszystkich liter, co psuje małe litery. Początkujący zapominają też o int() przy input(), a wtedy Python zatrzymuje się z błędem TypeError przy dodawaniu przesunięcia.
Najczęstsze pytania
Jakiej wersji Pythona potrzebuję?
Pythona 3.7 lub nowszego, bo wtedy pojawiło się isascii(). Kod z tej strony uruchomiliśmy na Pythonie 3.9. Nie wymaga dodatkowych pakietów.
Czy mogę tym chronić prawdziwe dane?
Nie. Pętla pokazana wyżej łamie ten szyfr w kilka sekund. Używaj go do nauki i łamigłówek, a hasła i prywatne dane chroń współczesnym szyfrowaniem z utrzymywanej biblioteki.
Jak sprawdzić, czy wynik jest poprawny?
Wpisz ten sam tekst i przesunięcie w narzędziu szyfru Cezara i porównaj. Ze swoich testów możesz też wywołać darmowe REST API.