1918年,德国。一个名叫亚瑟·谢尔比乌斯的发明家与他的朋友查理·里特创办了一个名为谢尔比乌斯和里特公司。而他却不知道,他的所作所为将改变第二次世界大战。
不久,谢尔比乌斯发明了一个机器并给它起名叫做Enigma(迷)。这台机器乍看起来好似一台打字机,按下机器键盘上的一个键,机器键盘上方的字一个母就会亮起来,同时在机器顶部的三个转轮最右边的那一个会相应转动一个位置。如果第二次再按下相同的键,另一个字母又会亮起来。
起初这台机器被用来加密商业机密,但是德国军方看上了它。于是在整个二次世界大战进程中,Enigma扮演了极其关键的重要角色——加密纳粹德军的通讯信息。然而不幸谢尔比乌斯却没能活到这一天,他于1929年5月13日骑马时摔在墙上因而撒手人寰。
纳粹德军将Enigma看作是牢不可破的密码,当然,在1941年以前事情的确如此。随着二战深入,德军不断改进Enigma密码机,增加密码机加密的可靠性。在1941年,英国海军捕获了一条德军潜艇U-110。在这艘潜艇上英军意外得到了一台还没来得及销毁的Enigma密码机。这是一历史性的时刻,在此之后Enigma得到了破译,而有人说这导致了二战进程提前两年结束,数以千计的盟军士兵的生命得以拯救。
破译Enigma的行动以绝密方式在英国布莱切利公园内进行,这一行动中不乏世界各地的天才,年轻的天才数学家阿兰图灵便是其中一位。他发明的机电式计算机“炸弹”为密码破译立下了汗马功劳。
图灵和“炸弹”
让我们来仔细看一看这台神奇机器的内部构造:机器大致分为五个部分,分别是:键盘、接线板、转子、反射器、显示器。机器的加密过程是这样的:按下键盘上的一个键,电流从电池中导出。经过接线板,接线板的功能是将一个字母连接至另一字母。而后电流到达定子,定子将电流送至转子。在德国海军型的Enigma机上有4个转子,电流依次经过四个转子到达反射器。反射器的功能类似于接线板,将一个字母的电流信号连接至另一字母后,反射器将电流送回转子。这时电流反序依次经过刚才的4个转子,经定子到达接线板后流至显示器上的一个灯泡。灯泡被点亮,显示加密结果。同时最左边的转子下移一位。加密过程结束。具体的细节大家可以在维基百科上查阅:http://zh.wikipedia.org/wiki/%E6%81%A9%E5%B0%BC%E6%A0%BC%E7%8E%9B%E5%AF%86%E7%A0%81%E6%9C%BA
图001
那么接下来,老C教大家用程序实现整个过程。
老C用的开发工具是VB6。在写代码之前,我们先要准备好一些资源。首先需要的是26个显示器点亮和未点亮时的图片:
。接着是26个按键分别在按下和未按下时的图片:
。然后我们需要转子转动相关图片:
。一些附件图片(比如接线板插口):
。最后是几个背景:
,
,
。
将这些图片按一定规律保存进RES文件,1xx(101-126)是显示器未点亮图片、2xx(201-226)是显示器点亮的图片、3xx(301-326)是键盘按键未按下图、4xx(401-426)键盘按键按下、501,502,601,602分别存放转子转动效果图,700存放接线板插销。
下面我们需要按照键位画好控件数组,将Image1的Image1(1)~Image1(26)分别对应字母A~Z放置在主界面上,将Image2的Image2(1)~Image2(26)分别对应键盘字母A~Z放置在主界面上,其他一些相关对象可以再放置。
好的,接着我们分析机器原理,将机械结构转变为我们的数据结构。首先看图001,根据上述的简单原理我们不难得出,接线板,反射器,转子三者分别可以看作为若干数组。我们使用数组定义语句:Dim RotorN(26) As Integer .为了便于理解,我们将一维数组下标看做1,而不是默认的0.这样,在线路寻址是我们可以这样使用数组:数组下标可以看做入口,数组内容可以看做出口,若一个数组内容为 RotorI(Asc("A")-64)==90,那么即可理解为,电流从A进入从Z流出(Chr(90)=="Z")。
这幅图清楚地反映了原理
在处理转子的旋转方面,我们可以这么做:在转子左右两边分别定义两个数组,比如转子III,II,I,对于转子II,我们可以定义数组3-2,2-1,名称可以为 Riii_Rii(26)和Rii_Ri(26)。在旋转时,就会有如下现象:
入口--(上端)ABCDEFGHIJKLMNOPQRSTUVWXYZ(下端)
出口--(上端)ABCDEFGHIJKLMNOPQRSTUVWXYZ(下端)
原先的情况
入口--(上端)ABCDEFGHIJKLMNOPQRSTUVWXYZ(下端)
出口--(上端)ZABCDEFGHIJKLMNOPQRSTUVWXY(下端)
向下移动一位
入口--(上端)ABCDEFGHIJKLMNOPQRSTUVWXYZ(下端)
出口--(上端)BCDEFGHIJKLMNOPQRSTUVWXYZA(下端)
向上移动一位
我们在编写转动算法时,可以将数组内容预先放置在一临时数组中,然后再进行错位赋值 向上转动可以写作:roto(i) = temp(i-1)向下转动则可写为roto = temp(i+1)。转子转动时,左右两边的接口都需要跟着转动,转子II向下转动时,Riii_Rii和Rii_Ri要跟着向下转动。这里要注意的是,转子II向下转动,Riii_Rii向下转动,而转子III向下转动,Riii_Rii需要向上转动,因为转子二不动,转子三向下转动相当于转子二向上转动,转子三不动。在处理转子进位是,我们可以将进位标记写入变量,比如转子二的两个V型槽可以被写入Public RotorII_V(2) As Integer这样一个变量。在I的转动过程中,如果满足I的位置等于变量任一值,则可调用II的向下转动过程。
以下是转子I的转动过程子程序:
Public Sub OnRotorIII(Optional flagDown As Boolean = True)
Dim i As Integer
Dim r(26) As Integer
If flagDown = True Then
For i = 1 To 26
r(i) = I_Riii(i)
Next
I_Riii(1) = r(26)
For i = 1 To 25
I_Riii(i + 1) = r(i)
Next
For i = 1 To 26
r(i) = Riii_Rii(i)
Next
Riii_Rii(1) = r(26)
For i = 1 To 25
Riii_Rii(i + 1) = r(i)
Next
For i = 1 To 26
r(i) = RotorC(i)
Next
RotorC(1) = r(26)
For i = 1 To 25
RotorC(i + 1) = r(i)
Next
If I_Riii(26) = RotorI_V(1) Or I_Riii(26) = RotorI_V(2) Then
If isTyping = True Then Call FrmMain.Image4_Click(2)
End If
Else
For i = 1 To 26
r(i) = I_Riii(i)
Next
I_Riii(26) = r(1)
For i = 1 To 25
I_Riii(i) = r(i + 1)
Next
For i = 1 To 26
r(i) = Riii_Rii(i)
Next
Riii_Rii(26) = r(1)
For i = 1 To 25
Riii_Rii(i) = r(i + 1)
Next
For i = 1 To 26
r(i) = RotorC(i)
Next
RotorC(26) = r(1)
For i = 1 To 25
RotorC(i) = r(i + 1)
Next
End If
FrmMain.Label1(1).Caption = Format(RotorC(26) - 64, "00")
End Sub
可以看出flagDown控制了转动方向,r(26)是临时数组变量。
处理完转子的转动,下面是加密的主函数。加密函数非常容易完成(甚至是整个程式最容易实现的部分),我们只要将传入的字符ascii码依次经过各个接线板数组、转子连接数组、转子数组、反射器数组即可。当信号经过反射器后,逆向搜寻各个数组,再将信号传入下一数组:
Public Function DisplayEncode(c As String) As Integer
Dim i As Integer
Dim j As Integer
HistoryIn = HistoryIn & UCase(c) '记录输入历史
i = Asc(UCase(c))
i = Plugboard(i - 64)
i = I_Riii(i - 64) 'i-3
i = RotorIII(i - 64)
i = Riii_Rii(i - 64) '3-2
i = RotorII(i - 64)
i = Rii_Ri(i - 64) '2-1
i = RotorI(i - 64)
i = Ri_Rx(i - 64) '1-x
i = RotorX(i - 64)
i = Rx_F(i - 64) 'x-f
i = Reflector(i - 64)
For j = 1 To 26
If Rx_F(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If RotorX(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If Ri_Rx(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If RotorI(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If Rii_Ri(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If RotorII(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If Riii_Rii(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If RotorIII(j) = i Then
i = j + 64
Exit For
End If
Next
For j = 1 To 26
If I_Riii(j) = i Then
i = j + 64
Exit For
End If
Next
i = Plugboard(i - 64)
HistoryOut = HistoryOut & Chr(i) '记录输出历史
DisplayEncode = i - 64
End Function
当我们按照先前说的步骤设置好资源文件后,显示器的点亮工作就变得相当简单:两行代码搞定
i = DisplayEncode(CStr(Chr(Index + 64)))
Image1(i).Picture = LoadResPicture(i + 200, 0)
主要的工作完成以后,接下来是一些琐事,比如画一个历史记录的对话框,一个自动输入的对话框,为接线板写好相应算法之类的。老C不再赘述,具体代码可以看老C提供的源码。最后写一份所有转子,反射器数据的ini,用GetPrivateProfileString api将设置读入,也可以使用WritePrivateProfileString将数据保存写入ini文件。
至此,一个Enigma模拟器就实现了,是不是有些小激动呢?我实现了德军二战的机密O(∩_∩)O哈哈~
Enigma Emulator 源码下载:
|