Вчера мы показали вам невзламываемый шифр Вернама. Теперь напишем собственную реализацию этого шифра на JavaScript.
Принцип работы шифра Вернама
- Нам нужно исходное сообщение и ключ шифрования. Ключ должен быть таким же по размеру, как исходное сообщение, или больше.
- Каждый бит исходного сообщения сопоставляется с битом ключа шифрования. Производится специальная операция XOR, которая отвечает за кодирование
- На выходе получается зашифрованный текст, расшифровать который можно только имея под рукой секретный ключ.
Алгоритм
Сделаем классическую реализацию шифра Вернама:
- Спрашиваем текст исходного сообщения и ключ.
- Если ключ короче сообщения — говорим об этом и сразу генерируем новый ключ нужной длины.
- Шифруем сообщение и выводим результат в консоль.
Единственное отличие нашего алгоритма от классического — принудительная проверка длины ключа. Чтобы шифр нельзя было взломать, ключ должен быть как минимум той же длины, что и исходное сообщение.
Подготовка
Заведём нужные переменные:
// переменные для исходного сообщения, зашифрованного и ключа шифрования
var input, output,key;
// служебные переменные для шифра
var inp, k;
Ещё нам понадобится функция, которая возвращает случайное число в указанном диапазоне. Её мы будем использовать для создания нового ключа, если ключ пользователя будет слишком короткий.
// генератор случайных чисел в заданном диапазоне
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
Последнее, что нам осталось сделать, — запросить у пользователя сообщение, которое будем шифровать, и ключ:
// запрашиваем текст, который будем шифровать
input = prompt("Введите исходный текст");
// запрашиваем ключ шифрования
key = prompt("Введите ключ");
Проверяем ключ
Теперь, когда у нас есть сообщение и ключ, проверим, хватит ли длины ключа или нет. Если не хватит — выведем сообщение и сгенерируем новый ключ.
👉 Обратите внимание на второй параметр в функции, которая возвращает случайное значение. В нашем примере мы взяли число 66535, потому что в первом регистре кодировки Unicode 66536 символов — от 0 до 66535, а JavaScript возвращает коды символов строки как раз в Unicode. Попробуйте поставить число поменьше, например 9999, и посмотрите, как меняются символы в ключе и в готовой шифровке.
Само генерирование ключа очень простое: мы берём случайное число, переводим его в строку функцией String.fromCharCode() и добавляем результат к строке с ключом.
// если длина ключа меньше длины сообщения — говорим пользователю и генерируем свой ключ
if ((key.length) < (input.length)) {
alert("Ключ короче сообщения, это небезопасно. Скопируйте новый сгенерированный ключ из консоли браузера.");
// в самом начале ключ будет пустой
key = "";
// генерируем новый ключ такой же длины, как и сообщение
for (var i = 0; i < input.length; i++) {
key += String.fromCharCode(getRandomInt(0,66535));
}
// выводим новый ключ шифрования
console.log("Скопируйте новый ключ ↓");
console.log(key);
}
Шифруем сообщение
В основе алгоритма Вернама лежит XOR — исключающее ИЛИ. Его особенность в том, что если его применить к зашифрованному тексту снова с тем же ключом, то мы получим расшифрованное исходное сообщение. Это значит, что этим алгоритмом мы можем и шифровать, и расшифровывать.
Логика работы алгоритма такая:
- Берём очередной символ сообщения и ключа.
- Получаем их числовой код.
- Применяем к ним XOR (в JavaScript эта операция обозначается крышечкой: ^) и получаем новое число.
- Это число переводим в символ и прибавляем к строке с зашифрованным текстом.
// шифруем сообщение
output = "";
for (i = 0; i < input.length; i++) {
// берём цифровое значение очередного символа в сообщении и ключе
inp = input.charCodeAt(i);
k = key.charCodeAt(i);
// и применяем к ним исключающее или — XOR
output += String.fromCharCode(inp ^ k);
}
// выводим результат шифрования
console.log("Результат работы алгоритма ↓");
console.log(output);
Если хотите проверить, как работает алгоритм, расшифруйте это сообщение
ඣߎಃ␗ᯄ᧕Ⴛඥ¢.ᵾ௧ᓐेࠬ௦ዐ⒦ਐ►༈ᕄ
вот этим ключом:
়Ύࢻ‥ᶗ႗අӯ࿓Аᵞ࿑႓ഇ࿖᛫⒆ชↄ଼ᕥ
Это может показаться удивительным, но в нашем примере длина ключа и сообщения совпадают. Это связано с особенностью кодировки Unicode — один код может одновременно кодировать несколько визуальных символов, поэтому длина кажется разной.
Что дальше
В нашей реализации шифра есть несколько минусов:
- в настоящей криптографии не используют функцию Math.random() в таком явном виде. Есть методы подбора и взлома этой псевдослучайной последовательности, по которой можно восстановить, казалось бы, случайные символы.
- Если мы хотим отправить длинное сообщение, то нам нужно его и ключ копировать и вставлять в поле ввода. Намного проще сделать чтение из файла — и сообщения, и ключа.
- Чтобы пользоваться этим алгоритмом, нужно постоянно передавать ключ, а это — проблема. Намного проще сделать так: сгенерировать очень длинную случайную последовательность и использовать её части при каждом шифровании или расшифровке. А использованные фрагменты сразу удалять, чтобы не снижать стойкость шифра.
- И да, у нас нет в явном виде функции расшифровки :-)
Когда-нибудь мы соберёмся с силами и исправим все три пункта.
// генератор случайных чисел в заданном диапазоне
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
// переменные для исходного сообщения, зашифрованного и ключа шифрования
var input, output,key;
// служебные переменные для шифра
var inp, k;
// запрашиваем текст, который будем шифровать
input = prompt("Введите исходный текст");
// запрашиваем ключ шифрования
key = prompt("Введите ключ");
// если длина ключа меньше длины сообщения — говорим пользователю и генерируем свой ключ
if ((key.length) < (input.length)) {
alert("Ключ короче сообщения, это небезопасно. Скопируйте новый сгенерированный ключ из консоли браузера.");
// в самом начале ключ будет пустой
key = "";
// генерируем новый ключ такой же длины, как и сообщение
for (var i = 0; i < input.length; i++) {
key += String.fromCharCode(getRandomInt(0,66535));
}
// выводим новый ключ шифрования
console.log("Скопируйте новый ключ ↓");
console.log(key);
}
// шифруем сообщение
output = "";
for (i = 0; i < input.length; i++) {
// берём цифровое значение очередного символа в сообщении и ключе
inp = input.charCodeAt(i);
k = key.charCodeAt(i);
// и применяем к ним исключающее или — XOR
output += String.fromCharCode(inp ^ k);
}
// выводим результат шифрования
console.log("Результат работы алгоритма ↓");
console.log(output);