今日推荐文章:API调用中的身份验证与授权实践-腾讯云开发者社区-腾讯云
点评:深入探讨了API调用的身份认证与授权的问题,并给出了实现,非常值得学习
一、介绍
在二战期间,加密大大添加破解的难度。其中最有名的就属于二战德国的Enigma密码机,号称永远不可能被破解(后面还是被破解了,笑)
恩尼格玛密码机Enigma是二战期间德国广泛使用的加密设备,其复杂的机械结构和加密算法在当时被认为是极其安全的。
下面将详细解释这款密码机是如何对字母进行加密的
首先,我们先了解一下电路,其中包含了三样东西,电池、开关、灯;如下图

好的,当开关A按下,电路闭合,灯泡A亮起;非常简单的原因
这是一组电路的情况,现在让我们多添加几个电路

这也非常好理解,当开关按下,对应电路的灯泡将亮起;这非常容易理解
接下来,我们尝试重新组合电路,将灯泡与开关打乱,那么将会得到

仅仅就是改变了电路的位置,那么我们不妨按下开关,会发生什么情况
当按下开关A,灯泡B亮了
当按下开关B,灯泡C亮了
当按下开关C,灯泡A亮了
到了这一步,你已经初步理解了Enigma密码机是如何工作的了,我在上面仅仅只有3个开关,那么只需要将这种电路添加至26个,覆盖到每一个字母,那么就能实现加密了。
新的问题出现了,如果仅仅只是这么简单的电路,那还达不到加密吧
确实,如同凯撒密码一般,如果仅仅只是将字母相互之间替换,语言学家会非常轻松地,将分析常用单词的高频字母,还原出原本的明文。
Enigma密码机,神奇的地方在于,他每次按下相同的字母,亮起的灯泡是不同的。这是如何实现的?
我们引入一个新的东西,转子
转子是一个扁圆柱体,两边各装有26个金属触点,每个金属触点代表了一个字母,如下图

在转子的内部,实现了我们上面给出的简单电路,打乱了它
当按下开关A,转子内部走线会到达B
当按下开关B,转子内部走线会到达C
当按下开关C,转子内部走线会到达另外一个字母
内部就是将26个字母的电路走线打乱,这也非常好理解。
但仅仅只有这个,还不足以完成加密吧
我们继续往下看,一个转子不行,Enigma密码机有三个这样的转子

转子相互组合,触点两两相连,假设
我们按下开关A,
电流经过右边转子后,输出成为了B
接着电流进过中间转子后,输出成为了G
再接着经过左边转子后,输出成为了Z
好的,以为就这样结束了吗?不,电流会经过反射器还会回来

那么也就是说,前面左边转子输入了个Z进入反射器,
反射器会经过内部两两相连的布线,返回个Y给到左边转子,
经过左边转子内部过后,返回个C给到中间转子
经过中间转子内部过后,返回个E给到右边转子
至此联通电路,可以计算到,当中经过了7次映射,可以算作7次凯撒密码的加密
还没完呢,这有三个转子A、B、C,分别可以进行组合,这有多少种可能
A、B、CA、C、BB、A、CB、C、AC、A、BC、B、A不急,还不止这些,在Enigma机器按下开关的那一刻,会使得转子转动
按照上面排列的顺序不同,我们区分一下,分别是低速转子、中速转子、高速转子
就像时针、分针、秒针一样。秒针转一圈,分针跳一刻度;分针转一圈,时针跳一刻度
当然,Enigma机器转子的一圈是26,也就是英文字母的数字,26进制可以理解吗?
正是因为转子的转动,才会使得我们,就算连续按下相同的字母,亮起的字母灯也是不一样的
这也就是说,只要确认了,转子的初始摆放位置,我们就能对密文进行解密
假设我们的转子是这样摆放的,A、B、C,且每个转子的初始刻度都为1,
我们对一段明文加密后,将密文给到德军下级
德军下级,将Enigma机器的转子位置,转子的初始刻度摆放得和我们一样
它将密文一个一个字母输入后,一个一个灯泡亮起,就得到了解密后的明文
忘记说一个概念了 除了对三个转子进行组合外,每个转子都有自己的状态,即转子当前的初始摆放刻度 在转子的环面上,刻上了
1-26的数字,就是代表转子刻度 哪个数字显示,就代表了当前转子所处的刻度
这也就是Enigma密码机,神奇的地方,加密机即为解密机
最后一个保险,就是接插板,这比起转子,原理非常简单。
就是在机器前面,添加了扰乱映射,通过电线将字母两两相连
由于机器大小原因,这种两两字母之间的映射,只能够添加10组
当我将字母A-Z相连,键盘按下A、将以Z的电路往后走去
好了,让我们回顾一下整个电路的流程
我们需要转子类、反射器类、转子组类;
先编写简单的反射器类,顺便把加密的方法写了。很简单就是从一个字母,映射成为另一个字母
反射器的构造器,就是为了维护这个字母映射关系
package com.banmoon.enigma.optimize;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public final class Reflector {
public static final Reflector INSTANT = new Reflector("ZYXWVUTSRQPONMLKJIHGFEDCBA");
private final Map<Character, Character> mapping;
/**
* 构造器
*
* @param encodingStr 编码,用于构造反射器
*/
private Reflector(String encodingStr) {
Assert.isTrue(StrUtil.isNotBlank(encodingStr), "encodingStr cannot be blank");
Assert.isTrue(StrUtil.length(encodingStr) == Constant.SIZE, "encodingStr length must be 26");
this.mapping = new HashMap<>(Constant.SIZE);
char[] charArray = encodingStr.toCharArray();
for (int i = 0; i < charArray.length; i++) {
char c = (char) (i + Constant.A_CHAR);
this.mapping.put(c, charArray[i]);
}
}
/**
* 加密
*
* @param c 字符
* @return 加密字符
*/
public char encrypt(char c) {
return this.mapping.get(c);
}
}那么接下来,就是转子,与转子组
转子类,我就写了个静态内部类,放在转子组中
转子组类中,有一个转子数组,用来维护转子
rotors[0],慢速转子,最左边的转子
rotors[1],中间转子,中间的转子
rotors[2],快速转子,最右边的转子
同时一个转动方法rotate(),从最右边的转子转动,判断是否经过了一圈,从而带动下一个转子转动
在转子内部维护了两个映射数组,分别是正向表与反向表
假设当前转子的当前定位currentPosition是处于0,当我们正向输入A时,我们就取正向表0索引的字符输出
假设当前转子的当前定位currentPosition是处于0,当我们反向输入A时,我们就取反向表0索引的字符输出
当然啦,currentPosition不可能一直是0,输入的字母也不能一直是A,这时候我们就要计算它的偏移量,找到正确的触点进行输出
package com.banmoon.enigma.optimize;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
/**
* 转子组
*/
@Data
public class RotorSet {
/**
* 提供了三个转子,用来组合
*/
public static final Rotor ROTOR_A = new Rotor("A", "COMPUTERSVWXYZABDFGHIJKLNQ");
public static final Rotor ROTOR_B = new Rotor("B", "HIJKLNQCOMPUTERSVWXYZABDFG");
public static final Rotor ROTOR_C = new Rotor("C", "PUTEABDFGRSVIMWXYJHKLNQCOZ");
/**
* 3个转子组成 <br>
* rotors[0]:低速转子,中速转子转动26下,此转子转动一次
* rotors[1]:中速转子,高速转子转动26下,此转子转动一次
* rotors[2]:高速转子,每键入一次字符,此转子转动一次
*/
private final Rotor[] rotors;
/**
* 反射器
*/
private final Reflector reflector;
/**
* 当前转子处于哪个定位,通过定位可以确定转子上的字符
*/
private final int[] currentPositionArray;
/**
* 构造转子组<br>
* 设置转子的顺序,3个转子如何摆放
*
* @param leftRotor 左边的转子,低速转子
* @param middleRotor 中间的转子,中速转子
* @param rightRotor 右边的转子,高速转子
* @param reflector 反射器
* @param initPositionStr 初始字符,用于设置转子位置
*/
public RotorSet(Rotor leftRotor, Rotor middleRotor, Rotor rightRotor, Reflector reflector, String initPositionStr) {
this(leftRotor, middleRotor, rightRotor, reflector, StrUtil.split(initPositionStr, StrUtil.COMMA).stream().map(Convert::toInt).toArray(Integer[]::new));
}
/**
* 构造转子组<br>
* 设置转子的顺序,3个转子如何摆放
*
* @param leftRotor 左边的转子,低速转子
* @param middleRotor 中间的转子,中速转子
* @param rightRotor 右边的转子,高速转子
* @param reflector 反射器
* @param initPositionArray 初始位置,用于设置转子位置
*/
public RotorSet(Rotor leftRotor, Rotor middleRotor, Rotor rightRotor, Reflector reflector, Integer[] initPositionArray) {
this.rotors = new Rotor[]{leftRotor, middleRotor, rightRotor};
this.reflector = reflector;
if (ArrayUtil.isNotEmpty(initPositionArray)) {
Assert.isTrue(initPositionArray.length == 3, "initPositionArray length must be 3");
leftRotor.setInitPosition(initPositionArray[0]);
middleRotor.setInitPosition(initPositionArray[1]);
rightRotor.setInitPosition(initPositionArray[2]);
this.currentPositionArray = new int[]{initPositionArray[0], initPositionArray[1], initPositionArray[2]};
} else {
this.currentPositionArray = new int[]{0, 0, 0};
}
}
/**
* 转动转子
*/
public void rotate() {
if (rotors[2].rotate()) {
if (rotors[1].rotate()) {
rotors[0].rotate();
}
}
currentPositionArray[0] = rotors[0].getCurrentPosition();
currentPositionArray[1] = rotors[1].getCurrentPosition();
currentPositionArray[2] = rotors[2].getCurrentPosition();
}
/**
* 加密
*
* @param c 字符
* @return 加密字符
*/
public char reflectorEncrypt(char c) {
return reflector.encrypt(c);
}
/**
* 转子
*/
@Data
public static class Rotor {
/**
* 转子名称
*/
private String name;
/**
* 26位编码表
*/
private int[] encoding;
/**
* 26位编码表,反向
*/
private int[] inverseEncoding;
/**
* 初始转子的定位
*/
private int initPosition;
/**
* 当前转子的定位
*/
private int currentPosition;
public Rotor(String name, String encodingStr) {
Assert.isTrue(StrUtil.isNotBlank(encodingStr), "encodingStr cannot be blank");
Assert.isTrue(encodingStr.length() == Constant.SIZE, "encodingStr length must be 26");
char[] charArray = encodingStr.toCharArray();
this.name = name;
this.initPosition = 0;
this.currentPosition = 0;
this.encoding = new int[charArray.length];
this.inverseEncoding = new int[charArray.length];
for (int i = 0; i < charArray.length; i++) {
this.encoding[i] = charArray[i] - Constant.A_CHAR;
}
for (int i = 0; i < this.encoding.length; i++) {
int index = this.encoding[i];
this.inverseEncoding[index] = i;
}
}
public void setInitPosition(int initPosition) {
this.initPosition = initPosition;
this.currentPosition = initPosition;
}
/**
* 转动转子
*
* @return 是否完成转子一圈的转动
*/
public boolean rotate() {
currentPosition = (currentPosition + 1) % Constant.SIZE;
return currentPosition == initPosition;
}
}
}插接板的代码比较简单
维护了一个字母映射的表,正常构造器中是输入什么就输出什么
混淆了字母,就会两两相连
package com.banmoon.enigma.optimize;
import cn.hutool.core.lang.Assert;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 插接板
*/
@Data
public class Plugboard {
private final Map<Character, Character> mapping;
public Plugboard() {
this.mapping = new HashMap<>(Constant.SIZE);
for (char c = 'A'; c <= 'Z'; c++) {
this.mapping.put(c, c);
}
}
/**
* 混淆
*/
public void confuse(Character key, Character value) {
Assert.isTrue(key >= 'A' && key <= 'Z', "plugboard key must be a letter");
Assert.isTrue(value >= 'A' && value <= 'Z', "plugboard value must be a letter");
mapping.put(key, value);
mapping.put(value, key);
}
/**
* 混淆
*/
public void confuse(Map<Character, Character> mapping) {
mapping.forEach(this::confuse);
}
/**
* 加密
*
* @param c 字符
* @return 加密字符
*/
public char encrypt(char c) {
return mapping.get(c);
}
}接下来就是组合代码了,也是比较简单,就是将转子组与接插板组合在一起的类
package com.banmoon.enigma.optimize;
import cn.hutool.core.lang.Assert;
import lombok.Data;
import java.util.Map;
/**
* 恩格码密码机
*/
@Data
public class EnigmaMachine {
/**
* 转子组
*/
private RotorSet rotorSet;
/**
* 插接板
*/
private Plugboard plugboard;
public EnigmaMachine(RotorSet rotorSet, Plugboard plugboard) {
this.rotorSet = rotorSet;
this.plugboard = plugboard;
}
/**
* 混淆插接板
*/
public void confusePlugboard(Character key, Character value) {
plugboard.confuse(key, value);
}
/**
* 混淆插接板
*/
public void confusePlugboard(Map<Character, Character> mapping) {
plugboard.confuse(mapping);
}
}重头戏在于,加密方法的编写,我们从EnigmaMachine.java开始
给予两个加密重载方法encrypt()
注意看形参为char的方法,它的步骤就是我们上面分析的电路连通步骤
/**
* 加密
*
* @param input 字符串
* @return 加密字符串
*/
public String encrypt(String input) {
Assert.notBlank(input, "input can not be blank");
input = input.toUpperCase();
char[] charArray = input.toCharArray();
char[] result = new char[charArray.length];
for (int i = 0; i < charArray.length; i++) {
char c = charArray[i];
if (c >= 'A' && c <= 'Z') {
c = encrypt(c);
}
result[i] = c;
}
return new String(result);
}
/**
* 加密
*
* @param c 字符
* @return 加密字符
*/
public char encrypt(char c) {
// 每输入一次字符,都会使转子旋转
rotorSet.rotate();
// 经过插接板
c = plugboard.encrypt(c);
// 经过转子组的转子
c = rotorSet.encrypt(c);
// 经过转子组的反射器
c = rotorSet.reflectorEncrypt(c);
// 再次经过转子组的转子(反向)
c = rotorSet.reverseEncrypt(c);
// 再次经过插接板
return plugboard.encrypt(c);
}最麻烦的就是转子组当中的加密,调试这个偏移量,可是费了不少脑细胞
首先是转子组的正向加密,可以看到是从慢速转子进入,经过中速转子,再经过慢速转子
/**
* 加密
*
* @param c 字符
* @return 加密字符
*/
public char encrypt(char c) {
int i = c - Constant.A_CHAR;
int i1 = rotors[2].encrypt(i);
int i2 = rotors[1].encrypt(i1);
int i3 = rotors[0].encrypt(i2);
return (char) (i3 + Constant.A_CHAR);
}那么在转子中,这个加密方法是什么样的呢
首先计算出了当前转子偏移量,
再计算出当前转子输入字符的位置是哪个触点
获取到当前触点,经过映射之后的字符
再计算出映射之后字符的触点位置
将这个信息返回出去,回到转子组可以看到,带着这个触点位置的信息,输入进了三个转子
/**
* 加密
*
* @param charIndex 字符所在位置
* @return 加密字符所在位置
*/
public int encrypt(int charIndex) {
int offset = currentPosition - initPosition;
int index = (charIndex + offset + 26) % 26;
return (encoding[index] - offset + 26) % 26;
}那么同理,经过反射器之后,返回的情况也是一样的
我们先看转子组的
/**
* 反向加密
*
* @param c 字符
* @return 加密字符
*/
public char reverseEncrypt(char c) {
int i = c - Constant.A_CHAR;
int i1 = rotors[0].reverseEncrypt(i);
int i2 = rotors[1].reverseEncrypt(i1);
int i3 = rotors[2].reverseEncrypt(i2);
return (char) (i3 + Constant.A_CHAR);
}再看转子的
/**
* 加密
*
* @param charIndex 字符所在位置
* @return 加密字符所在位置
*/
public int reverseEncrypt(int charIndex) {
int offset = currentPosition - initPosition;
int index = (charIndex + offset + 26) % 26;
return (inverseEncoding[index] - offset + 26) % 26;
}这边代码有点难懂,简单的来说,就是给每个触点编上了号码,0-25,当你输入A,对应触点的编号就是0
但当前编号为0的触点,连接的转子触点不是0啊,这个需要看偏移量,才能确定上当前与0号触点连接的转子触点是几号
确认完转子几号触点之后,还要看这个触点映射为哪个字符,
再通过偏移量,确认这个字符在当前转子的哪个触点输出
我们来进行测试
package com.banmoon.enigma.optimize;
import static com.banmoon.enigma.optimize.RotorSet.*;
public class Main {
public static void main(String[] args) {
RotorSet rotorSet = new RotorSet(ROTOR_A, ROTOR_B, ROTOR_C, Reflector.INSTANT, "0,0,0");
Plugboard plugboard = new Plugboard();
plugboard.confuse('A', 'B');
plugboard.confuse('C', 'D');
plugboard.confuse('E', 'F');
EnigmaMachine enigmaMachine = new EnigmaMachine(rotorSet, plugboard);
String a = enigmaMachine.encrypt("my name is banmoon");
System.out.println(a);
// 再试另一个密码机解密
RotorSet rotorSet1 = new RotorSet(ROTOR_A, ROTOR_B, ROTOR_C, Reflector.INSTANT, "0,0,0");
EnigmaMachine enigmaMachine1 = new EnigmaMachine(rotorSet1, plugboard);
a = enigmaMachine1.encrypt("FU PHZX YX PJYIVBF");
System.out.println(a);
}
}
就算盟军能拿到Enigma密码机,但由于不知道正确的转子摆放位置,以及插接板的混淆情况
破解密码几乎是不肯能的,我们来试一下,如果转子位置不同,设置的插接板情况不一样会怎么样
package com.banmoon.enigma.optimize;
import static com.banmoon.enigma.optimize.RotorSet.*;
public class Main {
public static void main(String[] args) {
RotorSet rotorSet = new RotorSet(ROTOR_A, ROTOR_B, ROTOR_C, Reflector.INSTANT, "0,0,0");
Plugboard plugboard = new Plugboard();
plugboard.confuse('A', 'B');
plugboard.confuse('C', 'D');
plugboard.confuse('E', 'F');
EnigmaMachine enigmaMachine = new EnigmaMachine(rotorSet, plugboard);
String a = enigmaMachine.encrypt("my name is banmoon");
System.out.println(a);
// 再试另一个密码机解密
RotorSet rotorSet1 = new RotorSet(ROTOR_A, ROTOR_C, ROTOR_B, Reflector.INSTANT, "12,11,10");
EnigmaMachine enigmaMachine1 = new EnigmaMachine(rotorSet1, plugboard);
a = enigmaMachine1.encrypt("FU PHZX YX PJYIVBF");
System.out.println(a);
}
}
不得不说,图灵不愧为计算机之父,这么复杂的密码机,都能破解
以后可以讲解一下,图灵祖师爷是如何破解这台Enigma密码机的
附带一张图片,了解一下这么个加密机的构造,更好了解它的加密原理

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。