Шифровальная машина Энигма была разработана еще до Второй мировой войны и использовалась как в коммерческих целях, так и в военных целях в армиях разных стран. Однако, именно нацистская Германия произвела большое число модификаций этой машины и массово использовала её для передачи секретных донесений и приказов. Особенное распространение Энигма получила во флоте на атлантике, однако, и на восточном фронте она использовалась. О самой шифровальной машине сложилось много мифов и легенд и про нее писали книги и снимали фильмы. И это понятно, ведь использование шифров при передаче оперативной информации напрямую влияло на исход военных, морских сражений, а значит на судьбы многих тысяч людей.
Подробнее об Энигме можно почитать, например, в Википедии. Я же в свою очередь так же постараюсь кратко рассказать о ее устройстве и о моей реализации шифроалгоритма в FPGA Intel MAX10 нашей платы M02mini. Я буду реализовывать конкретно одну модель - Enigma M3 использовавшуюся в основном военно-морскими силами Германии (Kriegsmarine).
Я честно скажу, что некоторые иллюстрации возьму из Википедии. Там есть совершенно замечательная схема, которая вполне объясняет принцип действия шифровальной машины:
Машина является электромеханической. Тут есть и чисто механические узлы, клавиатура, вращающиеся роторы, так и электрические компоненты: лампочки, коммутирующая панель, батарея. На схеме выше цифрами обозначены справа на лево: 4 - статор (ETW), 5 - роторы (R-I, R-II, R-III), 6 - рефлектор (UKW-B).
Особую хитрость представляют собой роторы. До войны в первых машинках было всего два ротора, но уже в военных экземплярах их стало три, а потом и четыре и пять и до восьми. Я буду делать шифромашинку с тремя роторами:
Каждый ротор представляет собой диск с 26ю контактами по обе стороны диска. 26 контактов соответствуют 26 буквам латинского алфавита. Внутри диска контакты соединены проводами по секретному принципу и все диски имеют разную схему соединений:
Схема соединения внутри каждого из этих дисков именно для машины Enigma M3 следующая:
|
Каждый диск, кроме статора по сути производил перестановку букв местами. Сигнал поступал с клавиатуры справа на один из контактов статора и передавался самому правому ротору. Правый ротор выдавал этот электрический сигнал уже в другом контакте с левой стороны выполняя замену буквы. Следующие два ротора так же выполняли свою подмену исходной буквы на другую и так сигнал попадал в отражатель, рефлектор. Рефлектор это тот же ротор, но не вращающийся. Он так же выполняет замену буквы выдавая сигнал назад уже на самый левый ротор. Возвращаясь назад слева направо происходит дополнительная замена букв одних на другие.
Нужно заметить, что каждое нажатие на клавишу шифромашины вызывает вращение самого правого ротора. Однако не нужно думать, что средний ротор обернется только через 26 нажатий, через полный цикл вращения правого ротора. Это же не одометр. На роторах были специальные выемки (notch), которые зацеплялись за выступы ротора, расположенного правее. Таким образом, вращение ротора слева происходило не через полный оборот правого ротора, а именно в момент зацепления выемки. В более поздних моделях Энигмы некоторые диски могли иметь их по две или даже три. Другой неочевидный факт о роторах проявляется в том, что когда поворачивается ротор R-II или R-I, то это вызывает дополнительный поворот ротора справа от поворачивающегося. Этот эффект - double stepping. Для центрального ротора это не так заметно, ведь правый ротор и так вращается с каждым нажатием, а вот поворот первого самого левого ротора действительно вызывает поворот центрального. Таким образом, система роторов производила многократную замену исходной буквы на другую букву. При этом, многократное нажатие одной и той же клавиши естественно порождало последовательность разных букв.
Таким образом, часть ключа шифра это первоначальное положение трех роторов, задаваемое тремя исходными буквами, например, "BSQ".
Дополнительная сложность ключа обеспечивалась возможностью сдвига диска подстановок, Ring Setting (Ringstellung).
Каждый из роторов можно было предварительно сдвинуть, так, что сдвигалась и таблица подстановок.
Например, для ротора R-I при Ringstellung положении "A" таблица заменн была следующей:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
EKMFLGDQVZNTOWYHXUSPAIBRCJ, но при положении "B" таблица замен была уже такой:
ZABCDEFGHIJKLMNOPQRSTUVWXY
EKMFLGDQVZNTOWYHXUSPAIBRCJ.
То есть ключ шифра был как минимум шестибуквенным: Начальное положение и начальный сдвиг.
Ну и наконец, дополнительная сложность ключа обеспечивалась через штекера. К клавиатуре была подключена панель штекеров, plugboard (Steckerverbindungen):
С помощью специальных кабелей можно было менять любые две буквы местами. Букв 26, значит пар букв, которые можно было поменять местами было 13. Порядок соединения штекеров это так же часть ключа. На схеме выше обозначены гнезда коммутаторов 3, 7 и коммутирующий кабель 8.
Ну и конечно, по схеме выше можно проследить как нажатие клавиши обеспечивало прохождение электрического сигнала через plugboard, статор, роторы, рефлектор, назад через роторы, через plugboard и на лампочку, которая загоралась обозначая закодированную букву..
Было ли такое шифрование простым и надежным?
Как видно из этого военного фото шифровальщики вероятно работали парами для надежности. Один нажимал клавиши и диктовал код второму, который записывал. Потом это отдавали радисту. Если хоть одна буква в середине сообщения будет искажена, то все, после этого сообщение будет нечитаемым.
Ну и ладно.
Давайте посмотрим, как этот алгоритм Энигмы можно реализовать в FPGA. Изначально мне хотелось сделать модель Энигмы на Verilog максимально похожей по логике на реальную машину. К сожалению, это не совсем удалось. Например, если посмотреть на ротор, то у него 26 контактов справа и 26 слева, при этом, через ротор одновременно двигаются два сигнала: один слева направо и другой справа налево. Каждый контакт может быть либо выходом либо входом. На verilog бывают такие выводы у модулей, inout. По логике тогда у модуля verilog должно быть всего 26 inout для сигналов слева и 26 inout для сигналов справа. Но это будет как-то громоздко при описании.
Вот код на verilog HDL, который сделал я:
module ring( input wire [25:0]f_in, output reg [25:0]f_out, input wire [25:0]b_in, output reg [25:0]b_out ); parameter TRANSLATION = "BDFHJLCPRTXVZNYEIWGAKMUSQO"; always @* begin f_out[ ((TRANSLATION>>(25*8) )&8'hFF) - 8'h41 ]= f_in[0 ]; f_out[ ((TRANSLATION>>(24*8) )&8'hFF) - 8'h41 ]= f_in[1 ]; f_out[ ((TRANSLATION>>(23*8) )&8'hFF) - 8'h41 ]= f_in[2 ]; f_out[ ((TRANSLATION>>(22*8) )&8'hFF) - 8'h41 ]= f_in[3 ]; f_out[ ((TRANSLATION>>(21*8) )&8'hFF) - 8'h41 ]= f_in[4 ]; f_out[ ((TRANSLATION>>(20*8) )&8'hFF) - 8'h41 ]= f_in[5 ]; f_out[ ((TRANSLATION>>(19*8) )&8'hFF) - 8'h41 ]= f_in[6 ]; f_out[ ((TRANSLATION>>(18*8) )&8'hFF) - 8'h41 ]= f_in[7 ]; f_out[ ((TRANSLATION>>(17*8) )&8'hFF) - 8'h41 ]= f_in[8 ]; f_out[ ((TRANSLATION>>(16*8) )&8'hFF) - 8'h41 ]= f_in[9 ]; f_out[ ((TRANSLATION>>(15*8) )&8'hFF) - 8'h41 ]= f_in[10]; f_out[ ((TRANSLATION>>(14*8) )&8'hFF) - 8'h41 ]= f_in[11]; f_out[ ((TRANSLATION>>(13*8) )&8'hFF) - 8'h41 ]= f_in[12]; f_out[ ((TRANSLATION>>(12*8) )&8'hFF) - 8'h41 ]= f_in[13]; f_out[ ((TRANSLATION>>(11*8) )&8'hFF) - 8'h41 ]= f_in[14]; f_out[ ((TRANSLATION>>(10*8) )&8'hFF) - 8'h41 ]= f_in[15]; f_out[ ((TRANSLATION>>( 9*8) )&8'hFF) - 8'h41 ]= f_in[16]; f_out[ ((TRANSLATION>>( 8*8) )&8'hFF) - 8'h41 ]= f_in[17]; f_out[ ((TRANSLATION>>( 7*8) )&8'hFF) - 8'h41 ]= f_in[18]; f_out[ ((TRANSLATION>>( 6*8) )&8'hFF) - 8'h41 ]= f_in[19]; f_out[ ((TRANSLATION>>( 5*8) )&8'hFF) - 8'h41 ]= f_in[20]; f_out[ ((TRANSLATION>>( 4*8) )&8'hFF) - 8'h41 ]= f_in[21]; f_out[ ((TRANSLATION>>( 3*8) )&8'hFF) - 8'h41 ]= f_in[22]; f_out[ ((TRANSLATION>>( 2*8) )&8'hFF) - 8'h41 ]= f_in[23]; f_out[ ((TRANSLATION>>( 1*8) )&8'hFF) - 8'h41 ]= f_in[24]; f_out[ ((TRANSLATION>>( 0*8) )&8'hFF) - 8'h41 ]= f_in[25]; end always @* begin b_out[ 0 ]= b_in[ ((TRANSLATION>>(25*8) )&8'hFF) - 8'h41 ]; b_out[ 1 ]= b_in[ ((TRANSLATION>>(24*8) )&8'hFF) - 8'h41 ]; b_out[ 2 ]= b_in[ ((TRANSLATION>>(23*8) )&8'hFF) - 8'h41 ]; b_out[ 3 ]= b_in[ ((TRANSLATION>>(22*8) )&8'hFF) - 8'h41 ]; b_out[ 4 ]= b_in[ ((TRANSLATION>>(21*8) )&8'hFF) - 8'h41 ]; b_out[ 5 ]= b_in[ ((TRANSLATION>>(20*8) )&8'hFF) - 8'h41 ]; b_out[ 6 ]= b_in[ ((TRANSLATION>>(19*8) )&8'hFF) - 8'h41 ]; b_out[ 7 ]= b_in[ ((TRANSLATION>>(18*8) )&8'hFF) - 8'h41 ]; b_out[ 8 ]= b_in[ ((TRANSLATION>>(17*8) )&8'hFF) - 8'h41 ]; b_out[ 9 ]= b_in[ ((TRANSLATION>>(16*8) )&8'hFF) - 8'h41 ]; b_out[ 10 ]= b_in[ ((TRANSLATION>>(15*8) )&8'hFF) - 8'h41 ]; b_out[ 11 ]= b_in[ ((TRANSLATION>>(14*8) )&8'hFF) - 8'h41 ]; b_out[ 12 ]= b_in[ ((TRANSLATION>>(13*8) )&8'hFF) - 8'h41 ]; b_out[ 13 ]= b_in[ ((TRANSLATION>>(12*8) )&8'hFF) - 8'h41 ]; b_out[ 14 ]= b_in[ ((TRANSLATION>>(11*8) )&8'hFF) - 8'h41 ]; b_out[ 15 ]= b_in[ ((TRANSLATION>>(10*8) )&8'hFF) - 8'h41 ]; b_out[ 16 ]= b_in[ ((TRANSLATION>>( 9*8) )&8'hFF) - 8'h41 ]; b_out[ 17 ]= b_in[ ((TRANSLATION>>( 8*8) )&8'hFF) - 8'h41 ]; b_out[ 18 ]= b_in[ ((TRANSLATION>>( 7*8) )&8'hFF) - 8'h41 ]; b_out[ 19 ]= b_in[ ((TRANSLATION>>( 6*8) )&8'hFF) - 8'h41 ]; b_out[ 20 ]= b_in[ ((TRANSLATION>>( 5*8) )&8'hFF) - 8'h41 ]; b_out[ 21 ]= b_in[ ((TRANSLATION>>( 4*8) )&8'hFF) - 8'h41 ]; b_out[ 22 ]= b_in[ ((TRANSLATION>>( 3*8) )&8'hFF) - 8'h41 ]; b_out[ 23 ]= b_in[ ((TRANSLATION>>( 2*8) )&8'hFF) - 8'h41 ]; b_out[ 24 ]= b_in[ ((TRANSLATION>>( 1*8) )&8'hFF) - 8'h41 ]; b_out[ 25 ]= b_in[ ((TRANSLATION>>( 0*8) )&8'hFF) - 8'h41 ]; end endmodule
Я сделал две группы 26 входов и 26 выходов. Одна группа "вперед" (forward): f_in/f_out, вторая группа "назад" (backward) b_in/b_out. Выводы делают замену букв в двух направлениях. Таблица трансляции задается через параметр строку из 26 букв. Чтобы взять N-нную букву из таблицы трансляции мне приходится делать сдвиг строки на N*8 бит. Я думаю это абсолютно не страшно, я уверен, что компилятор не будет устанавливать логику сдвигателя в чип, но сам вычислит итоговую константу и вместо всего этого нагромождения кода просто оставит провода-соединения. Вычитание шестнадцатеричного 8'h41 нужно для того, чтобы получить индекс буквы. Буква "A" имеет код 8'h41 и ее индекс - ноль.
Аналогично выглядит и рефлектор, но поскольку у него только одно направление передачи, то и код трансляции короче:
module reflector( input wire [25:0]in, output reg [25:0]out ); parameter TRANSLATION = "BDFHJLCPRTXVZNYEIWGAKMUSQO"; always @* begin out[ ((TRANSLATION>>(25*8) )&8'hFF) - 8'h41 ]= in[0 ]; out[ ((TRANSLATION>>(24*8) )&8'hFF) - 8'h41 ]= in[1 ]; out[ ((TRANSLATION>>(23*8) )&8'hFF) - 8'h41 ]= in[2 ]; out[ ((TRANSLATION>>(22*8) )&8'hFF) - 8'h41 ]= in[3 ]; out[ ((TRANSLATION>>(21*8) )&8'hFF) - 8'h41 ]= in[4 ]; out[ ((TRANSLATION>>(20*8) )&8'hFF) - 8'h41 ]= in[5 ]; out[ ((TRANSLATION>>(19*8) )&8'hFF) - 8'h41 ]= in[6 ]; out[ ((TRANSLATION>>(18*8) )&8'hFF) - 8'h41 ]= in[7 ]; out[ ((TRANSLATION>>(17*8) )&8'hFF) - 8'h41 ]= in[8 ]; out[ ((TRANSLATION>>(16*8) )&8'hFF) - 8'h41 ]= in[9 ]; out[ ((TRANSLATION>>(15*8) )&8'hFF) - 8'h41 ]= in[10]; out[ ((TRANSLATION>>(14*8) )&8'hFF) - 8'h41 ]= in[11]; out[ ((TRANSLATION>>(13*8) )&8'hFF) - 8'h41 ]= in[12]; out[ ((TRANSLATION>>(12*8) )&8'hFF) - 8'h41 ]= in[13]; out[ ((TRANSLATION>>(11*8) )&8'hFF) - 8'h41 ]= in[14]; out[ ((TRANSLATION>>(10*8) )&8'hFF) - 8'h41 ]= in[15]; out[ ((TRANSLATION>>( 9*8) )&8'hFF) - 8'h41 ]= in[16]; out[ ((TRANSLATION>>( 8*8) )&8'hFF) - 8'h41 ]= in[17]; out[ ((TRANSLATION>>( 7*8) )&8'hFF) - 8'h41 ]= in[18]; out[ ((TRANSLATION>>( 6*8) )&8'hFF) - 8'h41 ]= in[19]; out[ ((TRANSLATION>>( 5*8) )&8'hFF) - 8'h41 ]= in[20]; out[ ((TRANSLATION>>( 4*8) )&8'hFF) - 8'h41 ]= in[21]; out[ ((TRANSLATION>>( 3*8) )&8'hFF) - 8'h41 ]= in[22]; out[ ((TRANSLATION>>( 2*8) )&8'hFF) - 8'h41 ]= in[23]; out[ ((TRANSLATION>>( 1*8) )&8'hFF) - 8'h41 ]= in[24]; out[ ((TRANSLATION>>( 0*8) )&8'hFF) - 8'h41 ]= in[25]; end endmodule
Код на Verilog для одного ротора уже сложнее и включает в себя дополнительную обработку первоначальных настроек смещения offset и ringstellung, выполнение шага ротора и все такое.. Я пожалуй не буду приводить здесь весь код ротора, чтобы не загромождать статью. Приведу только описание модуля ротора:
module rotor( input wire clk, input wire set, //pulse used for offset/rengstellung initial settings input wire step0, //main pulse used for rotating input wire step, //pulse used for rotating input wire [4:0]offset, //initial settings input wire [4:0]ringstellung, //initial settings input wire [25:0]f_in, //forward translation output wire[25:0]f_out, input wire [25:0]b_in, //backward translation output wire[25:0]b_out, output wire advance ); parameter TRANSLATION = "BDFHJLCPRTXVZNYEIWGAKMUSQO"; parameter NOTCH = "A";
Сигналы step, offset, ringstellung используются для первоначальной установки ротора, то есть для задания ключа шифра.
Сигналы step0, step, нужны для осуществления шага ротора, advance передает шаговый импульс следующему ротору. Таблица трансляции и положение нотча передаются в модуль через параметр модуля.
Весь код проекта будет на github, там вы сможете его полностью посмотреть.
Могу здесь в статье привести код на Verilog для plugboard. Он довольно компактный и его не трудно понять.
module plugboard( input wire[4:0] char, input wire[25*5+4:0] tbl, output reg [4:0] out); reg [4:0] outp [11:0]; always @* begin outp[0]= (char==tbl[ 0*5+4: 0*5]) ? tbl[ 1*5+4: 1*5] : (char==tbl[ 1*5+4: 1*5]) ? tbl[ 0*5+4: 0*5] : char; outp[1]= (char==tbl[ 2*5+4: 2*5]) ? tbl[ 3*5+4: 3*5] : (char==tbl[ 3*5+4: 3*5]) ? tbl[ 2*5+4: 2*5] : outp[0]; outp[2]= (char==tbl[ 4*5+4: 4*5]) ? tbl[ 5*5+4: 5*5] : (char==tbl[ 5*5+4: 5*5]) ? tbl[ 4*5+4: 4*5] : outp[1]; outp[3]= (char==tbl[ 6*5+4: 6*5]) ? tbl[ 7*5+4: 7*5] : (char==tbl[ 7*5+4: 7*5]) ? tbl[ 6*5+4: 6*5] : outp[2]; outp[4]= (char==tbl[ 8*5+4: 8*5]) ? tbl[ 9*5+4: 9*5] : (char==tbl[ 9*5+4: 9*5]) ? tbl[ 8*5+4: 8*5] : outp[3]; outp[5]= (char==tbl[10*5+4:10*5]) ? tbl[11*5+4:11*5] : (char==tbl[11*5+4:11*5]) ? tbl[10*5+4:10*5] : outp[4]; outp[6]= (char==tbl[12*5+4:12*5]) ? tbl[13*5+4:13*5] : (char==tbl[13*5+4:13*5]) ? tbl[12*5+4:12*5] : outp[5]; outp[7]= (char==tbl[14*5+4:14*5]) ? tbl[15*5+4:15*5] : (char==tbl[15*5+4:15*5]) ? tbl[14*5+4:14*5] : outp[6]; outp[8]= (char==tbl[16*5+4:16*5]) ? tbl[17*5+4:17*5] : (char==tbl[17*5+4:17*5]) ? tbl[16*5+4:16*5] : outp[7]; outp[9]= (char==tbl[18*5+4:18*5]) ? tbl[19*5+4:19*5] : (char==tbl[19*5+4:19*5]) ? tbl[18*5+4:18*5] : outp[8]; outp[10]=(char==tbl[20*5+4:20*5]) ? tbl[21*5+4:21*5] : (char==tbl[21*5+4:21*5]) ? tbl[20*5+4:20*5] : outp[9]; outp[11]=(char==tbl[22*5+4:22*5]) ? tbl[23*5+4:23*5] : (char==tbl[23*5+4:23*5]) ? tbl[22*5+4:22*5] : outp[10]; out= (char==tbl[24*5+4:24*5]) ? tbl[25*5+4:25*5] : (char==tbl[25*5+4:25*5]) ? tbl[24*5+4:24*5] : outp[11]; end endmodule
Как я уже писал, plugboard делает замену одной буквы на другую. Здесь это делается по таблице. В таблицу должны быть помещены буквы парами, всего 13 пар. Если входной символ равен первому из пары, значит ответ будет второй из пары. А если входной символ равен второму из пары, то ответ будет первый символ из пары. Ну а если входной символ не равен ни первому ни второму, то ответ это исходный символ. Так производится 13 пар сравнений и выбирается ответ.
Исходный код на verilog машины Enigma у меня выглядит так:
`timescale 1ns / 1ns module enigma( input wire clk, input wire rset, input wire [14:0]offset_init, //3 ring * 5 bits input wire [14:0]ringst_init, input wire [25*5+4:0] plug_tbl, input wire [7:0]in_char, input wire in_char_write, output wire [7:0]out_char, output reg out_char_ready ); `include "lib.v" wire [4:0]plg1_in; assign plg1_in = (in_char-"A"); wire [4:0]plg1_out; plugboard plg1( .char(plg1_in ), .tbl( plug_tbl ), .out( plg1_out ) ); reg [25:0]f_in; always @* f_in <= (1<<plg1_out); wire [25:0]f_out3; wire [25:0]b_out3; wire [25:0]f_out2; wire [25:0]b_out2; wire [25:0]f_out1; wire [25:0]b_out1; wire [25:0]ukw_out; wire adv1; wire adv2; wire adv3; rotor #( .TRANSLATION("BDFHJLCPRTXVZNYEIWGAKMUSQO"), .NOTCH("V") ) rot3( .clk( clk ), .set( rset ), .step0(in_char_write), .step(in_char_write), .offset( offset_init[4:0] ), .ringstellung( ringst_init[4:0] ), .f_in(f_in), .f_out(f_out3), .b_in(b_out2), .b_out(b_out3), .advance( adv3 ) ); wire [4:0]plg2_in; assign plg2_in = pos2char( b_out3 ); wire [4:0]plg2_out; plugboard plg2( .char( plg2_in ), .tbl( plug_tbl ), .out( plg2_out ) ); assign out_char = plg2_out+"A"; //out char ready one clock later then input char always @(posedge clk) out_char_ready <= in_char_write;// && allowed_in; rotor #( .TRANSLATION("AJDKSIRUXBLHWTMCQGZNPYFVOE"), .NOTCH("E") ) rot2( .clk( clk ), .set( rset ), .step0(in_char_write), .step( adv3 ), .offset( offset_init[9:5] ), .ringstellung( ringst_init[9:5] ), .f_in(f_out3), .f_out(f_out2), .b_in(b_out1), .b_out(b_out2), .advance( adv2 ) ); rotor #( .TRANSLATION("EKMFLGDQVZNTOWYHXUSPAIBRCJ"), .NOTCH("Q") ) rot1( .clk( clk ), .set( rset ), .step0(in_char_write), .step(adv2 ), .offset( offset_init[14:10] ), .ringstellung( ringst_init[14:10] ), .f_in(f_out2), .f_out(f_out1), .b_in(ukw_out), .b_out(b_out1), .advance( adv1 ) ); reflector #( .TRANSLATION("YRUHQSLDPXNGOKMIEBFZCWVJAT") ) ukw_b( .in( f_out1 ), .out( ukw_out ) ); wire [7:0]chr_ukw_out; assign chr_ukw_out = pos2letter(ukw_out); endmodule
Видите три разных ротора с разными параметрами трансляции? Еще есть рефлектор и plugboard на входе и выходе.
Все это на самом деле занимает довольно много логики в FPGA, но в Intel MAX10 2000LE нашей платы M02mini помещается.
Хочу еще обратить ваше внимание, что поскольку я изначально пытался реализовать машину Энигма максимально похожей на настоящую, то это все у меня выливается в огромную логическую функцию. Она у меня никак не выписывается в конвейерную обработку. Просто большая логическая функция. Из-за этого такая логическая функция может работать только на очень низкой частоте. Но в этом проекте мне это не принципиально.
На самом деле, поскольку я хочу запустить этот проект в реальной плате M02mini, то модуль самого верхнего уровня это даже не enigma, а модуль max10_02:
module max10_02( input wire CLK100MHZ, input wire KEY0, input wire KEY1, input wire SERIAL_RX, output wire SERIAL_TX, output wire [3:0]LED, inout [13:0]IO );
Внутри этого модуля установлены модули приемника и передатчика для последовательного порта. Работать к шифровальной машиной можно будет из программы терминала, Putty.
По нажатию на кнопку платы KEY0 происходит сброс машины в исходное состояние и в терминал выводится символ ">" - начало работы. После этого оператор шифровальщик должен в терминале набрать ключ шифрования. Это должны быть как минимум 6 букв латинского алфавита устанавливающие offset и rinstellung для трех роторов. Дальше можно набрать еще до 13ти пар букв замены для plugboard. После этого можно нажать в терминале Enter и наша шифровальная машина на FPGA пришлет код ":". После этого можно набирать кодируемый или раскодируемый текст.
Надо сказать, что в интернете существует довольно много онлайн эмуляторов Энигмы. В своей работе я пользовался вот этим симпатичным сайтом.
С помощью этого эмулятора я например могу зашифровать сообщение, а расшифровать его с помощью моей шифромашины в плате FPGA M02mini.
Посмотрите, если в эмуляторе сделать настройки ключа "ABC", ringstellung "CDE" и plugboard пары "E-O", "H-L", то фраза "HELLOWORLD" будет закодирована в "QCNHCSZTQS". Потом я использую тот же самый ключ "ABCCDEEOHL", ввожу его в консоли терминала к плате M02mini и тогда фраза "QCNHCSZTQS" расшифровывается в "HELLOWORLD". Это все видно в коротком видео ролике-демонстрации, который я записал:
Весь код проекта для платы M02mini можно взять на github в папке enigma https://github.com/marsohod4you/M02mini.git
Кстати говоря, в этом же проекте есть и тестбенч, даже несколько тестбенчей.
С помощью симулятора icarus verilog довольно просто можно просимулировать работу моей шифровальной Verilog машины. Если у вас установлен icarus, то просто наберите в консоли команды:
>iverilog -o qqq -DICARUS=1 max10_02.v rotor.v ring.v reflector.v plugboard.v serial.v enigma.v tb2.v left.v
>vvp qqq
>gtkwave out.vcd
После этого можно просмотреть временные диаграммы всех внутренних сигналов проекта. Там будет видно, что после сброса в энигму посылаются через последовательный порт сперва ключ ADTBCDCDEF - это начальное положение ADT, сдвиг ringstellung BCD и положение на plugboard: CDEF. После этого посылается код 0xD означающий начало работы и собственно шифруемое сообщение "BCDEFGHI". На выходе шифромашины Энигма получается "TDKWACGR"..
А теперь посмотрим, что дают другие эмуляторы энигмы. Например, вот на сайте 101computing.net/enigma-machine-emulator:
А результат такой же! Так же сообщение "BCDEFGHI" шифруется в сообщение "TDKWACGR"! Значит моя шифровальная машина Энигма в плате M02mini работает правильно!
Подробнее...