Шифр Виженера
Шифр Виженера триста лет считали невзламываемым. Его секрет — переменный сдвиг, заданный ключевым словом.
Современники называли его le chiffre indéchiffrable — «нераскрываемый шифр».
Идея: много сдвигов вместо одного
Слабость Цезаря в том, что сдвиг одинаков для всех букв. Виженер исправляет это: сдвиг меняется от буквы к букве по ключевому слову. Например, ключ KEY задаёт сдвиги K(10), E(4), Y(24), которые повторяются по кругу для всего сообщения.
Как это работает на примере
| Текст | H | E | L | L | O |
| Ключ | K | E | Y | K | E |
| Сдвиг | 10 | 4 | 24 | 10 | 4 |
Одна и та же буква (две L) теперь шифруется по-разному, потому что для них разные сдвиги. Это разрушает простой частотный анализ.
Шифруем по Виженеру
import string
alphabet = string.ascii_uppercase
def vigenere_encrypt(text, key):
result = ""
key = key.upper()
j = 0
for ch in text.upper():
if ch in alphabet:
shift = alphabet.index(key[j % len(key)])
result += alphabet[(alphabet.index(ch) + shift) % 26]
j += 1
else:
result += ch
return result
print(vigenere_encrypt("HELLO WORLD", "KEY"))Вывод:
RIJVS UYVJN
Расшифровка
Чтобы расшифровать, вычитаем сдвиг ключа вместо прибавления:
import string
alphabet = string.ascii_uppercase
def vigenere(text, key, mode=1):
result, j, key = "", 0, key.upper()
for ch in text.upper():
if ch in alphabet:
shift = alphabet.index(key[j % len(key)]) * mode
result += alphabet[(alphabet.index(ch) + shift) % 26]
j += 1
else:
result += ch
return result
secret = vigenere("ATTACK AT DAWN", "LEMON", 1)
print("Зашифровано:", secret)
print("Расшифровано:", vigenere(secret, "LEMON", -1))Вывод:
Зашифровано: LXFOPV EF RNHR Расшифровано: ATTACK AT DAWN
В чём всё-таки слабость
Виженер держался долго, но в XIX веке его взломали. Хитрость в том, что ключ повторяется. Если угадать длину ключа (например, 3), то можно разбить шифр-текст на 3 группы, в каждой из которых сдвиг постоянный — а значит, к каждой группе снова применим частотный анализ! Метод поиска длины ключа называется тестом Касиски.
cipher = "RIJVSUYVJN"
key_len = 3
for start in range(key_len):
group = cipher[start::key_len]
print("Группа", start, ":", group)Вывод:
Группа 0 : RVYN Группа 1 : IS V Группа 2 : JUJ
Каждая группа зашифрована одним сдвигом — значит, это снова обычный Цезарь, который мы уже умеем ломать. Вывод урока: даже умный классический шифр в итоге сдаётся статистике. Настоящую стойкость дают только современные алгоритмы.