今日推荐文章: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、C
A、C、B
B、A、C
B、C、A
C、A、B
C、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 删除。