Шифр Вернама на JavaScript
medium

Шифр Вернама на JavaScript

Невзламываемый шифр за 4 строчки кода.

Вчера мы показали вам невзламываемый шифр Вернама. Теперь напишем собственную реализацию этого шифра на JavaScript.

Принцип работы шифра Вернама

  1. Нам нужно исходное сообщение и ключ шифрования. Ключ должен быть таким же по размеру, как исходное сообщение, или больше. 
  2. Каждый бит исходного сообщения сопоставляется с битом ключа шифрования. Производится специальная операция XOR, которая отвечает за кодирование
  3. На выходе получается зашифрованный текст, расшифровать который можно только имея под рукой секретный ключ. 

Алгоритм

Сделаем классическую реализацию шифра Вернама:

  1. Спрашиваем текст исходного сообщения и ключ.
  2. Если ключ короче сообщения — говорим об этом и сразу генерируем новый ключ нужной длины.
  3. Шифруем сообщение и выводим результат в консоль.

Единственное отличие нашего алгоритма от классического — принудительная проверка длины ключа. Чтобы шифр нельзя было взломать, ключ должен быть как минимум той же длины, что и исходное сообщение.

Подготовка

Заведём нужные переменные:

// переменные для исходного сообщения, зашифрованного и ключа шифрования
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 — исключающее ИЛИ. Его особенность в том, что если его применить к зашифрованному тексту снова с тем же ключом, то мы получим расшифрованное исходное сообщение. Это значит, что этим алгоритмом мы можем и шифровать, и расшифровывать.

Логика работы алгоритма такая:

  1. Берём очередной символ сообщения и ключа.
  2. Получаем их числовой код.
  3. Применяем к ним XOR (в JavaScript эта операция обозначается крышечкой: ^) и получаем новое число.
  4. Это число переводим в символ и прибавляем к строке с зашифрованным текстом.

// шифруем сообщение
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);

Текст и код

Миша Полянин


Редактор

Максим Ильяхов


Корректор

Ира Михеева


Иллюстратор

Даня Берковский


Вёрстка

Маша Дронова


Доставка

Олег Вешкурцев

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
medium