Шифр Вернама — это симметричный шифр, в котором каждый бит открытого текста складывается по модулю два (операцией XOR) с соответствующим битом ключа. Если ключ при этом случайный, равен по длине сообщению и используется только один раз, шифр называют одноразовым блокнотом (one-time pad) и он обладает абсолютной (совершенной) стойкостью, доказанной Клодом Шенноном.
Содержание
Ниже разберу, как устроено XOR-гаммирование и почему расшифровка тем же ключом возвращает исходный текст, при каких именно условиях достигается абсолютная стойкость и что она на самом деле означает, почему такой шифр почти не применяют на практике и чем он отличается от потоковых шифров. Примеры на Python можно скопировать и запустить.
Как работает шифр Вернама: XOR-гаммирование
В основе лежит побитовая операция «исключающее ИЛИ» (XOR, сложение по модулю два). Ее таблица истинности короткая: результат равен единице тогда и только тогда, когда биты различаются.
| a | b | a XOR b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Шифрование — это наложение ключа (гаммы) на открытый текст бит за битом:
- шифртекст:
C = M XOR K, гдеM— открытый текст,K— ключ; - расшифровка:
M = C XOR K, тем же самым ключом.
Расшифровка работает тем же ключом из-за ключевого свойства XOR: он сам себе обратен. Дважды наложить один и тот же бит — значит вернуться к исходному значению, потому что x XOR y XOR y = x (второе наложение отменяет первое). Подставив C = M XOR K, получаем C XOR K = M XOR K XOR K = M. Никакой отдельной процедуры расшифровки нет: и там, и там одна операция XOR с ключом.
Такое наложение потока ключа на поток данных называют гаммированием, а сам ключевой поток — гаммой. Вот минимальный рабочий пример на Python. Ключ здесь задан явными байтами, чтобы вывод был воспроизводимым:
def xor_bytes(a: bytes, b: bytes) -> bytes:
return bytes(x ^ y for x, y in zip(a, b))
message = b"HELLO"
key = bytes([0x13, 0x24, 0x35, 0x46, 0x57]) # 5 случайных байт, длина = сообщению
cipher = xor_bytes(message, key) # C = M XOR K
plain = xor_bytes(cipher, key) # M = C XOR K
print("шифртекст (hex):", cipher.hex())
print("расшифровано: ", plain.decode())
Вывод:
шифртекст (hex): 5b61790a18
расшифровано: HELLO
Одна и та же функция xor_bytes и шифрует, и расшифровывает — разница только в том, что ей подают на вход. Обратите внимание: длина ключа здесь равна длине сообщения, и это не случайность, а одно из условий стойкости, о которых ниже.
Важная оговорка про практику: для настоящего ключа нельзя брать random — этот генератор предсказуем. Криптографически стойкие случайные байты в Python дает модуль secrets (например, secrets.token_bytes(len(message))). В примере выше ключ фиксирован намеренно, только ради повторяемого вывода.
Условия абсолютной стойкости по Шеннону
Сам по себе XOR с ключом стойкости не гарантирует. Абсолютной (в терминах Шеннона — совершенной) стойкостью обладает только одноразовый блокнот, а для этого ключ должен удовлетворять трем условиям одновременно:
- Случайность. Ключ — это действительно случайная последовательность (каждый бит равновероятен и независим), а не результат работы обычного псевдослучайного генератора.
- Длина. Ключ не короче сообщения. Укорачивать и повторять ключ нельзя — именно повтор ломает всю схему (см. ниже).
- Одноразовость. Каждый ключ используется ровно один раз и после этого уничтожается. Отсюда и название — одноразовый блокнот.
Совершенную секретность этой схемы математически доказал Клод Шеннон (работа велась в 1940-х, опубликована в 1949 году). Формально она означает, что шифртекст не дает о сообщении никакой информации: вероятность конкретного открытого текста при известном шифртексте равна его вероятности без всякого шифртекста. Иначе говоря, шифртекст и открытый текст статистически независимы.
На интуитивном уровне это выглядит так: имея только шифртекст длины n, злоумышленник видит, что ему подходит абсолютно любой открытый текст той же длины — под каждый вариант найдется свой ключ, который дал бы ровно этот шифртекст. Поэтому перебор бесполезен: он выдаст все осмысленные сообщения сразу, без признака, какое из них настоящее. Стойкость тут не вычислительная (не «слишком долго считать»), а информационная — нужной информации в шифртексте просто нет.
Из доказательства Шеннона следует и цена такой стойкости: энтропия ключа должна быть не меньше энтропии сообщения, то есть секретного ключевого материала нужно столько же, сколько данных вы хотите защитить.
Чтобы не создавать мифов, назову и границы. Абсолютная стойкость — это про конфиденциальность, и только при выполнении всех трех условий:
- она не защищает от подмены. Перевернув бит в шифртексте, атакующий перевернет тот же бит в расшифрованном тексте (шифр «пластичен»), поэтому в реальных системах отдельно нужна проверка целостности;
- она не скрывает сам факт и длину сообщения;
- при нарушении любого условия стойкость рушится. Повторное использование ключа — разрушительно: для двух сообщений с одним ключом
C1 XOR C2 = (M1 XOR K) XOR (M2 XOR K) = M1 XOR M2, ключ сокращается, и атакующий работает уже с текстами напрямую.
Этот эффект повтора легко показать кодом — у атакующего нет ключа, но XOR двух шифртекстов дает XOR двух открытых текстов:
def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b))
key = bytes([0x13, 0x24, 0x35, 0x46, 0x57]) # один и тот же ключ на два сообщения - ошибка
c1 = xor_bytes(b"HELLO", key)
c2 = xor_bytes(b"WORLD", key)
leak = xor_bytes(c1, c2) # ключ сокращается
print("C1 XOR C2 == M1 XOR M2:", leak == xor_bytes(b"HELLO", b"WORLD"))
Вывод:
C1 XOR C2 == M1 XOR M2: True
Именно на повторном использовании ключей строились реальные вскрытия шифрпереписки в XX веке.
Почему шифр Вернама редко применяют на практике
Проблема не в стойкости, а в ключе. Условия, которые дают совершенную секретность, же делают шифр неудобным:
- Объем ключа. Ключа нужно ровно столько же, сколько данных. Чтобы защитить гигабайт трафика, нужен гигабайт случайного ключа, и это на каждое сообщение заново.
- Доставка ключа. Ключ надо заранее и безопасно передать обеим сторонам. Но безопасно передать длинный секрет — задача не проще, чем безопасно передать само сообщение. Это замкнутый круг распределения ключей.
- Хранение и уничтожение. Ключевой материал надо надежно хранить до использования и гарантированно уничтожать после, иначе теряется одноразовость.
- Настоящая случайность. Нужен источник действительно случайных данных, а не псевдослучайный генератор — иначе стойкость становится не абсолютной, а лишь вычислительной.
Поэтому одноразовый блокнот применяют там, где секретность критична, стороны заранее обмениваются ключами, а объем переписки невелик. Классические примеры — дипломатическая и агентурная связь (в том числе «числовые» одноразовые блокноты у разведчиков), защищенные правительственные линии. Современное развитие идеи — квантовое распределение ключей (QKD), которое как раз позволяет двум сторонам получить общий случайный ключ, пригодный для такой схемы.
Для массовых задач — переписки, сайтов, мессенджеров — вместо него используют шифры с коротким ключом, о которых дальше.
Чем шифр Вернама отличается от потоковых шифров
Потоковые шифры (например, ChaCha20 или устаревший RC4) используют ту же идею гаммирования: генерируют ключевой поток (гамму) и накладывают его на данные операцией XOR. Разница в природе гаммы.
| Признак | Шифр Вернама (одноразовый блокнот) | Потоковый шифр |
|---|---|---|
| Источник гаммы | Истинно случайный ключ | Псевдослучайный поток из короткого ключа |
| Длина ключа | Равна длине сообщения | Короткий (например, 256 бит) |
| Повторное использование | Запрещено (каждый ключ — один раз) | Ключ можно переиспользовать со сменой одноразового значения (nonce) |
| Тип стойкости | Абсолютная (информационная) | Вычислительная (взлом теоретически возможен, но непрактичен) |
| Практичность | Низкая (проблема ключей) | Высокая, основа реального шифрования |
Ключевая мысль: потоковый шифр — это попытка получить удобство ценой замены истинно случайной гаммы на псевдослучайную, порожденную из короткого ключа. За это он платит тем, что стойкость становится вычислительной, а не абсолютной: гамма предсказуема тому, кто знает короткий ключ или сумеет вскрыть генератор. Одноразовый блокнот такую предсказуемость исключает по построению, но за счет непрактичного ключа.
Смежные темы: двоичная система счисления и бинарный код, случайные числа в Python, взлом компьютерного устройства: виды атак и защита.
FAQ
Шифр Вернама и одноразовый блокнот — это одно и то же? Не совсем. Шифр Вернама — это сам механизм наложения ключа операцией XOR. Одноразовым блокнотом его называют, когда ключ дополнительно случаен, равен по длине сообщению и одноразов. Исходная схема Вернама с повторяющейся ключевой лентой этим условиям не отвечала и стойкой не была.
Можно ли взломать одноразовый блокнот перебором ключей? Формально перебор возможен, но бесполезен: он выдаст все осмысленные тексты нужной длины одновременно, и среди них нельзя отличить настоящее сообщение от подделки. Информации, которая позволила бы выбрать верный вариант, в шифртексте нет.
Устойчив ли он к квантовым компьютерам? Да. Его стойкость информационная, а не вычислительная, поэтому рост вычислительной мощности, в том числе квантовой, ее не подрывает. Практическое ограничение остается прежним — распределение длинных случайных ключей.
Выводы
Шифр Вернама сводится к одной операции: побитовому XOR открытого текста с ключом, где та же операция и расшифровывает. Абсолютную стойкость он дает не сам по себе, а только как одноразовый блокнот — при случайном, равном по длине сообщению и одноразовом ключе; это и доказал Шеннон. Нарушьте любое условие, особенно одноразовость, и стойкость исчезает. Именно неудобство ключей, а не слабость шифра, вытеснило его из массовой практики в пользу потоковых шифров с коротким ключом и вычислительной стойкостью.
Освойте тему на практике
Разобраться в симметричных и потоковых шифрах, режимах и управлении ключами системно можно на курсе по криптографической защите информации, а познакомиться с форматом и преподавателями — на открытых уроках и вебинарах.



