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

Вче­ра мы пока­за­ли вам невзла­мы­ва­е­мый шифр Вер­на­ма. Теперь напи­шем соб­ствен­ную реа­ли­за­цию это­го шиф­ра на 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);

Текст и код:
Миша Поля­нин

Редак­тор:
Мак­сим Илья­хов

Кор­рек­тор:
Ира Михе­е­ва

Иллю­стра­тор:
Даня Бер­ков­ский

Вёрст­ка:
Маша Дро­но­ва

Достав­ка:
Олег Веш­кур­цев