1 简介
前一段时间看了B站介绍恩尼格玛密码机的视频,试了试用Java来实现一台恩尼格玛密码机,在此文章中来简单介绍自己实现的思路和代码以供大家学习参考,如有错误,欢迎指出。
单表替换密码会由于字母分布的规律被破解出来,可以使用多表替换来增加破解难度,而恩尼格玛密码可以算是一种多表替换的加密方法。
恩尼格玛密码机加密原理简单的来说就是每当输入一位明文的时候有规律地动态更换替换表。由于其结构独特的设计使恩尼格玛密码具有自反性和排己性。
2 原理介绍及代码
2.1 密码机模拟
密码机包含一个输入部分,三个转子,一个反射板。由于其自反的特性,加密和解密的方法一致所以只有一个加密方法,还有一个设置三个转子位置的方法。
//一个恩尼格玛密码机由一个输入轮,三个转轮和一个反射板构成
public class EnigmaCoder {
private InputWheel input;
private Rotator r1,r2,r3;
private Reflector ref;
public EnigmaCoder(int num1,int num2,int num3) {
Integer[] array = {1,24,25,9,10,11,12,4,19,20,21,26,0,5,6,7,8,22,23,15,16,13,14,2,3,17,18};
List<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, array);
input = new InputWheel();
r1 = new Rotator(list,num1,1);
r2 = new Rotator(list,num2,2);
r3 = new Rotator(list,num3,3);
ref = new Reflector();
}
String encode(String str) {
String result = "";
Character ch;
int temp;
for(int i = 0;i < str.length();i++) {
ch = str.charAt(i);
temp = ref.getNum(r3.getNum(r2.getNum(r1.getNum(input.inputChar(ch)))));
temp = r1.getNumBack(r2.getNumBack(r3.getNumBack(temp)));
ch = input.inputNum(temp);
result += ch;
}
return result;
}
void setCoder(int num1,int num2,int num3) {
r1.setPoint(num1);
r2.setPoint(num2);
r3.setPoint(num3);
}
}
2.2 输入轮模拟
明文一位一位从密码机传到输入轮中,输入轮将明文对应的下标,最终在转子中变换的是替换表的下标,也就是整形数。输入轮中包含两个方法,一个是文字转为对应下标的整形数,另一个是下标转文字。
public class InputWheel {
final static int CHAR_NUMBER = 27;//26个字母和一个空格
List<Character> link;
public InputWheel() {
Character[] str = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' '};
link = new ArrayList<Character>();
Collections.addAll(link, str);
}
int inputChar(Character ch) {
int num = -1;//默认为错误输出
for(int i = 0;i < CHAR_NUMBER;i++) {
if(ch.equals(link.get(i))) {
num = i;
break;
}
}
return num;
}
Character inputNum(int num) {
Character ch = '?';//默认为错误输出
ch = link.get(num%CHAR_NUMBER);
return ch;
}
}
2.3 转子模拟
转子由一个代表接线的整形数组,当前转子的指针和转子的序号(外圈转子转一圈内圈转子转一个刻度),该密码机为三个转子,每个转子的接线和初始位置都可以单独设置。由于需要有设置当前转子指针的功能,所有有get和set指针的两个方法。还有通过替代表输入和输出的两个方法。
不同序号的转子判断的代码我是拿switch写的,如果需要拓展功能,可以用type进行计算,最终达到一样的效果,并且还能继续叠加转子。
public class Rotator {
final static int CHAR_NUMBER = 27;//26个字母和一个空格
private List<Integer> link;
private List<Integer> rlink;
private int count,point,type;
public Rotator(List<Integer> link,int point,int type) {
super();
this.link = link;
Integer[] array = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26};
rlink = new ArrayList<Integer>();
Collections.addAll(rlink, array);
for(int i = 0; i < link.size(); i++) {
rlink.set(link.get(i), i);
}
this.point = point;
this.type = type;
count = 0;
}
int getNum(int offset) {//所选数字为目前最低位的指针加上输入的偏移量
count++;
int sequence = offset + point;
if(sequence >= CHAR_NUMBER) sequence = sequence%CHAR_NUMBER;
//先加charnumber使之变为正数,再取其余数
int num = (link.get(sequence) - point + CHAR_NUMBER)%CHAR_NUMBER;
return num;
}
int getNumBack(int offset) {
count++;
int sequence = offset + point;
if(sequence >= CHAR_NUMBER) sequence = sequence%CHAR_NUMBER;
int num = (rlink.get(sequence) - point + CHAR_NUMBER)%CHAR_NUMBER;
switch(type) {
case 1: {//最低级转轮每次读取都转一次,字符进入后再出来共读取两次
if(count%2 == 0) {
point++;
count = 0;
}
break;
}
case 2: {//最低轮转一圈第二轮转一刻
if(count%CHAR_NUMBER == 0) {
point++;
count = 0;
}
break;
}
case 3: {
if(count%(CHAR_NUMBER*CHAR_NUMBER) == 0) {
point++;
count = 0;
}
break;
}
default:{
break;
}
}
return num;
}
void setPoint(int point) {
this.point = point;
count = 0;
}
int getPoint() {
return point;
}
}
2.4 反射板模拟
反射板只需要一个获得反射后下标的方法即可。需要注意的是反射板内部的link(替代表)和转子内部的不同,反射板的link需要正序的数组两两替换得到,转子则没有要求。
public class Reflector {
final static int CHAR_NUMBER = 27;//26个字母和一个空格
private List<Integer> link;
public Reflector() {
super();
Integer[] array = {1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,17,16,19,18,21,20,23,22,25,24,26};
link = new ArrayList<Integer>();
Collections.addAll(link, array);
}
int getNum(int i) {
return link.get(i);
}
}
3 结果演示及测试代码
public class Test {
public Test() {
EnigmaCoder coder = new EnigmaCoder(0,0,0);
BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); //ctr + shift + o
try {
System.out.println("请输入需要加密的字符串:");
String str = in.readLine();
System.out.println("加密前为:"+str);
str = coder.encode(str);
System.out.println("加密后为:'"+str+"'");
coder.setCoder(0, 0, 0);
System.out.println("请输入需要解密的字符串:");
str = in.readLine();
str = coder.encode(str);
System.out.println("解密后为:'"+str+"'");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Test ts = new Test();
}
}