自己打造一台恩尼格码密码机

按:这个作品是2013年无线电单片机竞赛的亚军。感谢所有支持这个作品的你们!

在对称加密学当中,恩尼格码机绝对是承前启后的存在。它将密码学研究从以前的语言文字学中心完全转移到了数学身上。在这里牵涉的密码并不是我们平时邮箱、银行帐号那种狭义概念,那种顶多叫做口令。这里说的密码就是通过某种转换规律方式,把一篇文章变得面目全非,非常人能阅读,以达到保密效果。这篇文章适于电脑控、军事控、历史控、数学控阅读,请做好烧脑准备。

另:我们的“开源恩尼格玛计划”在Kickstarter已经结束众筹。如有兴趣请浏览http://www.stgeotronics.com。感谢大家支持!

这是我们的初号机。以下教程将手把手教你如何完美山寨史上著名的德国恩尼格玛密码机(以下称哑谜机,不清楚历史的可以到维基、百度等地方脑补一下)。这个基于Arduino的开源程序能够加解密任何哑谜机M4型(海军型)的信息。

这个第一台全功能开源完美哑谜机复制品是根据sketchsk3tch写的《Kid’s Game to Arduino Enigma Machine》(从儿童玩具到Arduino恩尼格玛机)所作。

采用多路复用LED电路,仅用38个针脚的115个发光二极管和4个针脚的36个按键所连接的整个电路,全靠在键盘回路里准确放置的电阻以及P型号晶体管得以实现。要不然,4个16段显示器,以及每个按键上的LED将大幅增加所需针脚总量,即使用了Arduino Mega板但如果没用上述两个方法也不能如此简洁。 面对电路的超额需求,我们在http://www.stgeotronics.com设计了专用的PCB板。直接跳到第10步和以后的步骤可以找到更多信息。同时,我们以测试过的完整电子组装套装发布。

第一步:面包板上的论证


在开始制作电子哑谜机之前,我们先要确保能驱动16段LED显示。如果能的话,我们就能做接下来的所有步骤,除了数学上的问题,一切都是浮云。

第二步:万事具备

你所需要的是:

  • 1个Arduino Mega 2560板
  • 26个字母按键
  • 26个1/4英寸单通道母接口
  • 10个1/4英寸单通道公接口
  • 36个机械按钮
  • 1个单刀三掷开关
  • 4个16段橙色LED显示
  • 4个注塑2升汽水瓶罩子
  • 1个胶合板盒子
  • 一个铰链
  • 一个半榫接锁
  • 一个接线盘
  • 38个470欧电阻
  • 40个1千欧电阻
  • 7个IRF9Z24N P型晶体管
  • 1块金属片
  • 以及喷漆。

可选项:

电池盒,充电电池,充电器/充电接头。

我们真要做的时候,是不会用1/4英寸接口的。它们太大的体积几乎要超过整个恩尼格玛。香蕉插头体积较小,而且比原版德国哑谜机结合得更紧密些。

第三步:布置零件






6*8寸无线电面包版是最合适放置所有元件的,既不多余也不拥挤,而且和哑谜机盒子内部完美吻合。

最初我们将面包等分三块区域,但很快意识到如此一来,电子版哑谜机将比原版机械哑谜机长。于是我们将所有零件缩放到正好够占用的空间。

每个元件位置就绪,下一步就是焊接。

第四步:我焊,我焊,我焊焊焊……






好吧,在单一作品身上,我从没焊接如此多次。16段显示的18个针脚,还有26个字母键乘以每个4个脚,外加26个键盘灯,一些其他LED,一个三掷开关,真乃“成吉思焊”。

当初我们的决定是使这些16段LED显示看起来像老式电子管的感觉,增加了不少焊点,“巨焊”!

Arduino Mega板上针脚的分配: 17段:

针脚 线色 Duino针脚
a 2 24
b 1 22
c 16 白蓝 25
d 13 绿 31
e 9 白棕 38
f 8 36
g 6 绿 32
h 5 白橙 30
k 4 28
m 3 白蓝 26
n 17 23
p 15 27
r 12 白橙 33
s 11 35
t 7 白绿 34
u 14 白橙 29
dp 10 白棕 37
阳极1 18 39
阳极2 18 41
阳极3 18 43
阳极4 18 45

LED:

1 40
2 42
3 44
4 46
5 48

灯:

QAP 10
WSY 9
EDX 8
RFC 7
TGV 6
ZHB 5
UJN 4
IKM 3
OL 2
阳极1 (第一排) 11
阳极2 (第二排) 12
阳极3 (第三排) 13

功能键: A0 键盘:

第一排 A1
第二排 A2
第三排 A3

第五步:门面工夫——做个盒子钻出面板




在原版M4型木盒内得到确定位置数据后,我们买了一块胶合板,将它切块,然后砌盒子。

我们从旧服务器机架上卸了一块钢板,厚度正合需要。将模具(上面早已画好每个按键和灯位,并切好了洞洞)盖在钢板上,然后用记号笔画出需要切出的洞洞。

接着,我们用喷漆把它涂黑,就像真的哑谜机那样。

第六步:组装测试







首先把金属板在面包版上永久固定,确保所有按键正常工作,所有LED都能发光。

接着就是把这一大坨东东装入木盒,确保没有空隙位置。

第七步:软件啊,日完软啊!


在组装硬件过程中,我们也写了个小型Arduino程序框架,用以测试特定几个需要关注的部分:

用来测试每个按键信号能准确读取,还有测试10个功能按键的代码。

Enigma_POST(上电自检)确保在每种模式下所有键盘等都能准确亮起,在每种模式下每个LED信号都能传送。我们对原本面包板上的代码做了修正,确保4个16段LED显示的每个部件无懈可击。

但,即使所有手上的程序片段都说明机器状态完好,重现M4海军型哑谜机加解密功能,数学方面居功至伟。

所有Arduino程序片段在我们刚刚建好的云端都能找到。

以下是Enigma_POST程序片段(上电自检):

/* Enigma Development Code to Test each of the 4 Nixies, the 5 LEDs,
   then Turn On each Lamp in sequence.
   Written by Marc Tessier & James Sanderson 9/8/13
*/

// Define the 16-Segments Pins
int segment[17] = {24,22,25,31,38,36,32,30,28,26,23,27,33,35,34,29,37};
int anode[4] = {39,41,43,45};

// Define the 9 lamps Pins
int lamp[9] = {10,9,8,7,6,5,4,3,2};
int lanode[3] = {11,12,13};

//              LTP587P Segments: A,B,C,D,E,F,G,H,K,M,N,P,R,S,T,U,dp              
boolean segmentvals[39][17] = { { 0,0,0,0,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = A
                                { 0,0,0,0,0,0,1,1,1,0,1,0,1,0,1,1,1 },  // = B
                                { 0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = C
                                { 0,0,0,0,0,0,1,1,1,0,1,1,1,0,1,1,1 },  // = D
                                { 0,0,1,1,0,0,0,0,1,1,1,0,1,1,1,0,1 },  // = E
                                { 0,0,1,1,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = F
                                { 0,0,1,0,0,0,0,0,1,1,1,0,1,1,1,1,1 },  // = G
                                { 1,1,0,0,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = H
                                { 0,0,1,1,0,0,1,1,1,0,1,1,1,0,1,1,1 },  // = I
                                { 1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1 },  // = J
                                { 1,1,1,1,1,1,0,0,1,1,0,1,0,1,1,0,1 },  // = K
                                { 1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = L
                                { 1,1,0,0,1,1,0,0,0,1,0,1,1,1,1,1,1 },  // = M
                                { 1,1,0,0,1,1,0,0,0,1,1,1,0,1,1,1,1 },  // = N
                                { 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = O
                                { 0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = P
                                { 0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1 },  // = Q
                                { 0,0,0,1,1,1,0,0,1,1,1,0,0,1,1,0,1 },  // = R
                                { 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0,1 },  // = S
                                { 0,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1 },  // = T
                                { 1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = U
                                { 1,1,1,1,1,1,0,0,1,1,0,1,1,1,0,1,1 },  // = V
                                { 1,1,0,0,1,1,0,0,1,1,1,1,0,1,0,1,1 },  // = W
                                { 1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1 },  // = X
                                { 1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1 },  // = Y
                                { 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,1 },  // = Z
                                { 0,0,0,0,0,0,0,0,1,1,0,1,1,1,0,1,1 },  // = 0
                                { 1,1,0,0,1,1,1,1,1,1,0,1,1,1,1,1,1 },  // = 1
                                { 0,0,0,1,0,0,0,1,1,1,1,0,1,1,1,0,1 },  // = 2
                                { 0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,1 },  // = 3
                                { 1,1,0,0,1,1,1,0,1,1,1,0,1,1,1,0,1 },  // = 4
                                { 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0,1 },  // = 5
                                { 0,0,1,0,0,0,0,0,1,1,1,0,1,1,1,0,1 },  // = 6
                                { 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1 },  // = 7
                                { 0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,1 },  // = 8
                                { 0,0,0,0,0,0,1,0,1,1,1,0,1,1,1,0,1 },  // = 9
                                { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },  // = Space
                                { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },  // = Full Lit
                                { 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1 }   // = SS
                              };
//              LTP587P Segments: A,B,C,D,E,F,G,H,K,M,N,P,R,S,T,U,dp
        
boolean lampvals[9][9] =   { { 0,1,1,1,1,1,1,1,1 },  // = Q or A or P
                             { 1,0,1,1,1,1,1,1,1 },  // = W or S or Y
                             { 1,1,0,1,1,1,1,1,1 },  // = E or D or X
                             { 1,1,1,0,1,1,1,1,1 },  // = R or F or C
                             { 1,1,1,1,0,1,1,1,1 },  // = T or G or V
                             { 1,1,1,1,1,0,1,1,1 },  // = Z or H or B
                             { 1,1,1,1,1,1,0,1,1 },  // = U or J or N
                             { 1,1,1,1,1,1,1,0,1 },  // = I or K or M
                             { 1,1,1,1,1,1,1,1,0 }   // = O or L
                            };

int value_row1 = 0;
int value_row2 = 0;
int value_row3 = 0;
char key = 91;

int led1 = 40;
int led2 = 42;
int led3 = 44;
int led4 = 46;
int led5 = 48;
int wait = 100;



void setup() {
  for (int index = 0 ; index <= 3; index  ) {
    pinMode (anode[index], OUTPUT);
    digitalWrite (anode[index], 1);
  }
  for (int index = 0 ; index <= 16; index  ) {
    pinMode (segment[index], OUTPUT);
    digitalWrite (segment[index], 1);
  } 
  // initialize the digital pins as an output.
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
  pinMode(led4, OUTPUT);
  pinMode(led5, OUTPUT);
  for (int index = 0 ; index <= 2; index  ) {
    pinMode (lanode[index], OUTPUT);
    digitalWrite (lanode[index], 1);
  }
  for (int index = 0 ; index <= 8; index  ) {
    pinMode (lamp[index], OUTPUT);
    digitalWrite (lamp[index], 1);
  }
}

void loop() {
   sixteenSegWrite(0, 38);
   sixteenSegWrite(1, 38);
   sixteenSegWrite(2, 38);
   sixteenSegWrite(3, 38);
  
  digitalWrite(led1, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(200);               // wait for a second
  digitalWrite(led1, LOW);    // turn the LED off by making the voltage LOW
  delay(wait);               // wait for a second
  digitalWrite(led2, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(200);               // wait for a second
  digitalWrite(led2, LOW);    // turn the LED off by making the voltage LOW
  delay(wait);               // wait for a second
  digitalWrite(led3, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(200);               // wait for a second
  digitalWrite(led3, LOW);    // turn the LED off by making the voltage LOW
  delay(wait);               // wait for a second
  digitalWrite(led4, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(200);               // wait for a second
  digitalWrite(led4, LOW);    // turn the LED off by making the voltage LOW
  delay(wait);               // wait for a second
  digitalWrite(led5, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(200);               // wait for a second
  digitalWrite(led5, LOW);    // turn the LED off by making the voltage LOW
  delay(wait);               // wait for a second

for (int index = 0 ; index <= 2; index  ) {
   digitalWrite (lanode[index], 0);
   for (int mychar = 0; mychar < 9; mychar  ) {
      for (int sindex = 0; sindex < 9; sindex  ) {
         digitalWrite(lamp[sindex], lampvals[mychar][sindex]);
         delay (30);
      }
  }
  digitalWrite (lanode[index], 1);
}
}

void sixteenSegWrite(int digit, int character) {
  digitalWrite(anode[digit],0);
  for (int index = 0; index < 17; index  ) {
  digitalWrite(segment[index], segmentvals[character][index]);
  }
}

第八步:再多一些软件!









首先,我们写了个函数,给每个哑谜机工作模式用。

在模式0、默认模式,哑谜机仅仅是一台普通打字机,以跑马灯方式显示它的型号。

模式1下,允许用户从八个转子中选取三个,两个反射器中选择一个进行使用。

模式2下,允许用户排列转子次序。

模式3用于自定义转子初始字母排列。

选择模式4,用户最多可以使用接线板上10对交换字母排列。

模式5是运行模式,此时哑谜机能加解密任何从键盘录入的信息。

以下是整个哑谜机工作流程完整程序片段:

/* S&T GeoTronics Enigma Code. This Arduino Mega custom shield is programmed to replicate
   exactly the behavior of a true German M4 Enigma machine.
   It uses 4 16-Segment units, 5 LEDs, 26 Lamps setup as keyboard, 26 keyboard buttons
   & 10 Function keys. The 115 light emitting diodes are charlie-plexed to minimize the
   amount of pins needed down to 38 and all 36 pushbuttons keys are sharing a total of 4 pins.
   Designed, assembled & programmed by Marc Tessier & James Sanderson 9/20/13
*/
// Define the variables
unsigned long time = millis();
unsigned long otime = time;
int inpin[4] = {A0, A1, A2, A3};
int inval[4] = {0, 0, 0, 0};
int keyval = 100;
boolean windex = 0;
boolean windex1 = 0;
boolean windex2 = 0;
int lampval = 100;
int procesval = 0;
int procesvala = 0;
int mode = 0;
unsigned long mtime;
int mdex =0;

// Define each Nixie character
int dig1 = 37;
int dig2 = 37;
int dig3 = 37;
int dig4 = 37;

int data[36] = {36,36,36,36,18,39,19,36,6,4,14,19,17,14,13,8,2,18,36,4,13,8,6,12,0,36,12,0,17,10,36,30,36,36,36,36} ;

// Define the 16-Segments Pins as 2 Arrays
int segment[17] = {24,22,25,31,38,36,32,30,28,26,23,27,33,35,34,29,37}; //cathode array
int anode[4] = {39,41,43,45}; //annode array commin annode

// Define the 26 Lamps as a 2D Array
int lamparray[26] [2] = {{12,10},{13,5},{13,7},{12,8},{11,8},{12,7,},{12,6},{12,5},{11,3},{12,4},
                         {12,3},{13,2},{13,3},{13,4},{11,2},{13,10},{11,10},{11,7},{12,9},{11,6},
                         {11,4},{13,6},{11,9},{13,8},{13,9},{11,5}};

//  Define the 12 Lamp Pins for initialization
int lamppin[12] = {2,3,4,5,6,7,8,9,10,11,12,13}; //2 to 10 cathode, 11 to 13 common annode

// Define each LTP587P Segments: A,B,C,D,E,F,G,H,K,M,N,P,R,S,T,U,dp              
boolean segmentvals[40][17] = { { 0,0,0,0,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = A 0
                                { 0,0,0,0,0,0,1,1,1,0,1,0,1,0,1,1,1 },  // = B 1
                                { 0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = C 2
                                { 0,0,0,0,0,0,1,1,1,0,1,1,1,0,1,1,1 },  // = D 3
                                { 0,0,1,1,0,0,0,0,1,1,1,0,1,1,1,0,1 },  // = E 4
                                { 0,0,1,1,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = F 5
                                { 0,0,1,0,0,0,0,0,1,1,1,0,1,1,1,1,1 },  // = G 6
                                { 1,1,0,0,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = H 7
                                { 0,0,1,1,0,0,1,1,1,0,1,1,1,0,1,1,1 },  // = I 8
                                { 1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1 },  // = J 9
                                { 1,1,1,1,1,1,0,0,1,1,0,1,0,1,1,0,1 },  // = K 10
                                { 1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = L 11
                                { 1,1,0,0,1,1,0,0,0,1,0,1,1,1,1,1,1 },  // = M 12
                                { 1,1,0,0,1,1,0,0,0,1,1,1,0,1,1,1,1 },  // = N 13
                                { 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = O 14
                                { 0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,1 },  // = P 15
                                { 0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1 },  // = Q 16
                                { 0,0,0,1,1,1,0,0,1,1,1,0,0,1,1,0,1 },  // = R 17
                                { 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0,1 },  // = S 18
                                { 0,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1 },  // = T 19
                                { 1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1 },  // = U 20
                                { 1,1,1,1,1,1,0,0,1,1,0,1,1,1,0,1,1 },  // = V 21
                                { 1,1,0,0,1,1,0,0,1,1,1,1,0,1,0,1,1 },  // = W 22
                                { 1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1 },  // = X 23
                                { 1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1 },  // = Y 24
                                { 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,1 },  // = Z 25
                                { 0,0,0,0,0,0,0,0,1,1,0,1,1,1,0,1,1 },  // = 0 26
                                { 1,1,0,0,1,1,1,1,1,1,0,1,1,1,1,1,1 },  // = 1 27
                                { 0,0,0,1,0,0,0,1,1,1,1,0,1,1,1,0,1 },  // = 2 28
                                { 0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,1 },  // = 3 29
                                { 1,1,0,0,1,1,1,0,1,1,1,0,1,1,1,0,1 },  // = 4 30
                                { 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0,1 },  // = 5 31
                                { 0,0,1,0,0,0,0,0,1,1,1,0,1,1,1,0,1 },  // = 6 32
                                { 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1 },  // = 7 33
                                { 0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,1 },  // = 8 34
                                { 0,0,0,0,0,0,1,0,1,1,1,0,1,1,1,0,1 },  // = 9 35
                                { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },  // = Space 36
                                { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },  // = Full Lit 37
                                { 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1 },  // = SS 38
                                { 0,1,1,1,0,0,0,1,0,0,1,1,0,1,1,0,1} }; // = & 39
//              LTP587P Segments: A,B,C,D,E,F,G,H,K,M,N,P,R,S,T,U,dp

// Define the 5 Mode LEDs
int led1 = 40;
int led2 = 42;
int led3 = 44;
int led4 = 46;
int led5 = 48;

                                      //4,10,12, 5,11, 6, 3,16,21,25,13,19,14,22,24, 7,23,20,18,15, 0, 8, 1,17, 2, 9
//Define the rotor values                 A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q
static const int rotorvals[12][78] = { {  4,10,12, 5,11, 6, 3,16,21,25,13,19,14,22,24, 7,123,20,18,15, 0, 8, 1,17, 2, 9,
                                         4,10,12, 5,11, 6, 3,16,21,25,13,19,14,22,24, 7,123,20,18,15, 0, 8, 1,17, 2, 9,
                                         4,10,12, 5,11, 6, 3,16,21,25,13,19,14,22,24, 7,123,20,18,15, 0, 8, 1,17, 2, 9 },  // wheel 1

                                       {  0, 9, 3,10,118, 8,17,20,23, 1,11, 7,22,19,12, 2,16, 6,25,13,15,24, 5,21,14, 4,