写了一天的enigma还是没搞定,唉,古典!码力真的还是有点烂。
Matrix3是rec做的,不写在这篇,之后哪天有空把d3的两道matrix补上然后一起写一篇好了。
K-Cessation
题目描述:
1 | K-Cessation |
题目:
1 | from typing import List,Union,Literal |
ciphertext.txt:
1 | [2, 1, 1, 3, 1, 1, 3, 2, 1, 4, 1, 2, 3, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 3, 1, 6, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 3, 3, 2, 1, 1, 3, 1, 1, 1, 3, 4, 1, 3, 1, 2, 2, 4, 2, 5, 1, 1, 1, 3, 2, 1, 4, 2, 2, 1, 2, 1, 3, 1, 1, 1, 1, 1, 2, 3, 1, 2, 1, 1, 1, 1, 3, 4, 1, 2, 2, 4, 2, 5, 1, 2, 1, 2, 2, 1, 4, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 4, 3, 1, 2, 1, 3, 1, 3, 3, 2, 1, 3, 1, 6, 2, 1, 1, 2, 1, 2, 1, 3, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 2, 2, 3, 1, 1, 4, 1, 3, 1, 1, 1, 2, 1, 1, 2, 4, 1, 1, 5, 2, 4, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 3, 3, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 5, 1, 1, 1, 3, 1, 1, 2, 3, 1, 2, 2, 2, 1, 3, 3, 1, 1, 2, 1, 1, 4, 3, 1, 3, 4, 1, 1, 1, 2, 1, 3, 1, 6, 1, 2, 1, 1, 3, 2, 3, 1, 2, 2, 1, 3, 2, 1, 2, 2, 2, 3, 3, 3, 1, 1, 2, 4, 1, 1, 1, 1, 1, 4, 2, 1, 4, 1, 2, 3, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 3, 2, 1, 2, 1, 1, 1, 4, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 4, 2, 1, 4, 2, 4, 2, 2, 3, 1, 2, 2, 2, 1, 3, 3, 1, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 4, 2, 5, 2, 1, 3, 1, 1, 2, 3, 1, 2, 2, 1, 1, 1, 1, 1, 1, 3, 1, 2, 1, 3, 1, 2, 3, 4, 4, 3, 2, 4, 2, 1, 4, 2, 4, 1, 2, 1, 3, 1, 2, 1, 1, 1, 3, 2, 1, 2, 2, 2, 3, 3, 1, 2, 1, 3, 1, 1, 1, 2, 1, 3, 4, 2, 1, 4, 1, 2, 1, 2, 2, 2, 1, 1, 2, 1, 1, 2, 2, 2, 1, 4, 2, 1, 4, 1, 1, 1, 1, 2, 4, 4, 3, 2, 4, 2, 1, 1, 1, 1, 1, 1, 1, 4, 2, 2, 3, 1, 1, 1, 2, 1, 3, 1, 4, 1, 2, 4, 1, 2, 3, 4, 1, 3, 1, 1, 1, 2, 4, 1, 1, 1, 4, 1, 1, 4, 2, 1, 4, 2, 2, 1, 1, 1, 1, 1, 2, 3, 2, 1, 4, 3, 3, 4, 4, 3, 2, 4, 2, 1, 1, 3, 2, 4, 1, 1, 2, 3, 1, 1, 1, 2, 2, 1, 1, 1, 1, 3, 1, 1, 1, 4, 3, 3, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 4, 2, 5, 1, 1, 4, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2, 4, 3, 1, 1, 1, 1, 3, 4, 3, 1, 1, 4, 1, 6, 2, 1, 1, 1, 3, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 4, 3, 1, 1, 5, 4, 1, 2, 2, 4, 1, 6, 1, 2, 1, 1, 3, 1, 4, 1, 2, 1, 2, 1, 1, 1, 1, 4, 2, 2, 3, 1, 2, 3, 1, 3, 4, 1, 1, 3, 4, 2, 5, 1, 1, 1, 3, 2, 2, 3, 2, 1, 2, 2, 2, 2, 3, 1, 2, 1, 3, 3, 3, 1, 1, 2, 1, 3, 3, 1, 1, 4, 2, 5, 2, 4, 1, 2, 4, 1, 2, 1, 2, 1, 1, 1, 2, 3, 1, 2, 4, 1, 1, 4, 4, 1, 1, 2, 3, 2, 4, 2, 5, 1, 2, 1, 2, 1, 1, 2, 3, 1, 2, 1, 2, 1, 1, 3, 1, 1, 2, 1, 2, 3, 1, 1, 1, 3, 4, 1, 1, 2, 1, 1, 1, 2, 4, 2, 1, 1, 3, 1, 2, 1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 1, 3, 1, 1, 2, 1, 2, 3, 1, 1, 1, 3, 4, 1, 1, 2, 3, 1, 2, 3, 1, 6, 2, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 4, 2, 1, 4, 1, 2, 3, 1, 1, 2, 1] |
flaghash.txt:
1 | d650078ae91d82ebd1d586110960a789c1a15e2cbc053b9daf8d8a4905950720:b840089ce93581869e9c02a7b5aefa7b |
题目基于一个特殊的加密方式:
- 初始化一个随机的长度为64的比特串wheel,并置初始指针为-1
- 把flag转为二进制串,并将他的MSB做随机处理
- 按flag比特进行加密。比如第一比特是0,那么指针向右循环移动至wheel下一个0的位置,并记录本次移动距离;第二比特为1,那么指针向右循环移动至wheel下一个1的位置,并记录本次移动距离。如此重复直到加密完所有比特
- 给出所有移动距离,要求还原明文
可以看出,如果能恢复完整的wheel,就可以直接用作为密文的距离恢复flag的各个bit。而如果MSB没有处理的话,我们就拥有了wheel中64个一定为0的位置(这些位置会产生重复),那么就对我们恢复wheel有很大帮助了。
然而问题就在于MSB有随机处理过,所以对于flag串我们可以说几乎是一无所知,因此要想别的办法。
而注意到以下几个事实:
- 如果当前记录的距离为1,则本次移动涉及两个连续比特,没法得知这两个比特任意一个的信息
- 如果当前记录的距离为2,则本次移动涉及3个连续比特,可以知道的事实是:第二比特和第三比特一定不同
- 如果当前记录的距离为n,则本次移动涉及(n+1)个连续比特,可以知道的事实是:第2-n个比特一定相同,且一定和第n+1个比特不同
而我们有足够多的密文,因此我们可以通过这样的关系,确定很多一定相同或一定不同的比特,也就是把所有比特分为两类。这个时候假设其中某一类比特是0,另一部分就是1,就得到了可能的wheel,然后结合flaghash对所有可能的wheel进行爆破就可以了。
exp:
1 | from Crypto.Util.number import * |
FACRT
题目描述:
1 | ERROR ERROR ERROR, please replace flag{*} to WMCTF{*} |
题目:
1 | import gmpy2 |
题目基于RSA的CRT,但是由于把$m^{d_q}$那部分的高位进行了处理,所以得到的最终结果并不是$m^d$。
而由CRT的式子有:
写成这个形式可以发现后面那一部分相对较小,所以其实就变成了一个AGCD问题:
因此格一格就好。
exp:
1 | from Crypto.Util.number import * |
RSA
题目:
1 | from Crypto.Util.number import * |
题目给出了一个奇怪的矩阵M:
除了RSA的公钥n、e之外再给出$C=M^e$,要求m。
M显然有点特殊,所以先拆成一个反对称阵和单位阵的和形式:
所以有:
这样出来了一个二项式定理的形式,那么把他展开就得到:
自己测试一下会发现A的偶次幂是个对角矩阵,因此最终C的非对角线元素都是上述二项式展开中A的奇数次幂贡献的。用小一点的e去factor一下非对角线元素的因子会发现这些和式均有共同因子:
1 | PR.<m,p,q> = PolynomialRing(ZZ) |
输出:
1 | m + p + q |
所以可以提取出几组C中的代数关系:
1 | (m + 2*p)*enc[1,0] == (m + p + q)*enc[2,0] |
然后groebner就可以解掉。
exp:
1 | from Crypto.Util.number import * |
C O N N E C T 1 0 N
题目描述:
1 | Hand 1n Hand, Heart t0 Heart |
题目:
1 | from Crypto.Util.number import * |
output很长不放在这里,自己本地生成些数据做也是一样的。
这次来自@yolbby ,当时他出完之后我验了这道题,所以就没有在赛中打。
yolbby出题很有意思,先只管出,然后我们一起再看能不能做XD
这题是他之前给他们学校招新赛的躲猫猫中一个题目的升级,不过真的升级太多了,所以打法完全不一样,因此也没有必要去找那个题目做参考。
总而言之题目加密流程如下:
- 将flag前后填上随机字节,使得其填充至指定长度,并保证填充后的flag的0、1比特数量不同
- 把flag分成长度相等的四部分,并转为整数,记在列表S里
- 对S中的每一部分分别进行后续加密
由于S的四部分都是分开的,所以假设就加密第一部分好了。
因为这么做可以少一层括号,括号多了真的头大TT
先看一下shuffle这个函数,对于输入(l,r),它的功能是输出一个随机比特串,但这个比特串0的数量为l,1的数量为r。
又因为题目生成的随机串只有(63,65)和(65,63)两种,不妨就把他们分别记为串a和串b,并记S[0]对应的比特串是m,那么加密流程是:
对m按位加密,记当前位为s(以下以当前bit为1举例):
如果s = 1,则生成随机串b1,并记:
对于c的每一位,记当前位为t,
如果t = 1,则生成随机串b2,并计算d,放到data中:
如果t = 0,则生成随机串a2,并计算e,放到data中:
由于内外套了两层与m的异或,很难直接看出什么性质,所以我们不妨先假设一个简化的情形,也就是外层不与m异或:
1 | data = [[shuffle(r,l)^^secret if j == '1' else shuffle(l,r)^^secret for j in (bin(shuffle(r,l))[2:].rjust(128,'0') if i == '1' else bin(shuffle(l,r))[2:].rjust(128,'0'))] for i in bin(secret)[2:].rjust(128,'0')] |
此时我们知道,如果m当前位s=1,那么data对应的这一行会有65个e串,63个d串;否则会有63个e串,65个d串。
m和data中的数据都是一个128bit的比特串,我们考虑对他们做一个映射:
这个映射显然是个同态,因为:
我们用data中的第一行展开分析,记第一行中每个数据分别为ci,那么此时m与ci的异或就可以同态到乘法:
而我们知道m与ci的异或值要么是a串要么是b串,而由于a、b两种串做映射后变成1、-1的数量相差2,所以这其实告诉了我们一个映射后向量的内积关系:
而这具体是2还是-2我们暂时不得而知,因为我们不清楚每个ci对应的是d串还是e串,但是有一个事情是确定的,那就是第一行d、e两种串的数量分别是63或者65,具体a是63还是b是63取决于m的第一bit。
既然总量上有差异,那么考虑把第一行所有内积进行加和,就得到:
之所以是正负4,这是因为有63对两两抵消掉了,只剩下单出来的两个a串或b串,所以为正负4。
而现在要解决的问题是:到底每一行是正4还是-4?我们知道m当前bit如果是0,这一行d串更多,那么加和中会单出两个b串,所以会得到4;同理如果m当前bit是1就会得到-4。因此我们可以把所有加和写成一个矩阵方程:
把cij组成的矩阵记作M,就有:
也就是:
所以如果是这种情况就简单了,我们只需要求矩阵方程就可以得到m’,再映射回m。
在这个思路基础上,题目在外层多套了一层与m的异或,这使得下面的式子不一定成立:
此时成立的应该是:(因为每更换一bit就会少一对a、b串,而多出两个单的a串或b串)
那么就有:
而实际上进一步测试可以发现m一定满足且仅满足如下两种情况之一,具体是哪种取决于m汉明重量的奇偶性,事实上由于padding的特殊性一定满足模8为0:
不过不管哪种情况,由于m’是1、-1组成的短向量所以都可以BKZ。
exp:
1 | from Crypto.Util.number import * |
*Turing
题目描述:
1 | Do as Turing did. |
题目:
重要一点的文件大概是以下几个:
1 | from hashlib import sha256 |
msg.txt:
1 | Some of the German ciphertexts have been decrypted. Maybe you can find something different. |
由于确实一点都不了解enigma,所以要先找点东西学习一下,结合题目描述知道应该是要考图灵对enigma的破译,所以找了以下一些资料看:
(99+ 封私信 / 80 条消息) 《模仿游戏》中艾伦·图灵是如何破译英格玛的? - 知乎 (zhihu.com)
【中字】二战间谍战——英格玛密码机下(解密)@阿尔法小分队科教组_哔哩哔哩_bilibili
【计算机博物志】战争密码(下集)炸弹机_哔哩哔哩_bilibili
看完这几个会发现不管是加密方式还是破译方式,enigma都比想象的要简单很多,先简单总结一下。
组成
对于题目中的enigma,它有以下几个主要部件:
- 五个加密转子(rotor):每个转子代表着一个字母间的单表代换,记为I、II、III、IV、V。
- 一个反射器,仍然代表一个单表代换
- 一个插线板(plugin):在输入和输出时将插线板连接的字母进行交换
密钥
每次加密时有以下一些不同的配置,这些配置共同构成本次enigma加密的密钥:
五个rotors里面选取其中三个并作排列,如:IV、V、II
种数共有 $A(5,3) = 5 \cdot 4 \cdot 3 = 60$
每个被选中的rotor需要设置一个起始刻度,如:Q、V、M
种数共有 $26^3 = 17576$ 种
插线板需要选择十对字母两两组合,如:AB、CD、EF、GH、IJ、KL、MN、OP、QR、ST
种数共有 $\prod_{i=0}^{9}(\frac{26-2i}{2}) \approx 2^{69}$ 种
剩下没有配对的六个字母可以看作自己和自己交换(也就是不交换),如:UU、VV、WW、XX、YY、ZZ
所以一次enigma加密的密钥可以看成一个三元组:
key =((IV、V、II)、(Q、V、M)、(AB、CD、EF、GH、IJ、KL、MN、OP、QR、ST))
对于破译者来说,如果这三个元素都获得了,就可以解本次enigma的全部密文。
加密
简单来讲,enigma加密的输入输出都是一个英语字母串,并且是按字符来的。我们不妨假设明文是TANG,那么以T这单个字符被上述密钥加密为例,具体来说过程是:
- 输入T,经过plugin,变为S
- B经过第一层转子IV,变为$\alpha$
- $\alpha$经过第二层转子V,变为$\beta$
- $\beta$经过第三层转子II,变为$\delta$
- $\delta$经过反射器,变为$\gamma$
- $\gamma$经过第三层转子II,变为$\theta$
- $\theta$经过第二层转自V,变为$\epsilon$
- $\epsilon$经过第一层转子IV,假设变为E
- E经过plugin,变为F
这就是单次字符加密的全过程,而随着输入字符变多,三个rotor会按如下规律转动(不然为什么叫转子):
- 每输入一个字符,第一层转子转动一格
- 第一层转子转动一圈,第二个转子就转动一格
- 第二层转子转动一圈,第三个转子就转动一格
所以在第一个字符T被加密之后,第一个转子会转一格,那么我们加密第二个字符A的时候第一个转子的单表代换就发生了改变,所以即使第二个输入字母还是T,他的结果也不会和第一个相同。
具体来说对于字符的一次加密流程图如下:(这个图少了plug的部分)
性质
enigma机主要有两个重要性质:
如果转子配置与plugin配置完全一样,那么如果加密 $\alpha$ 得到 $\beta$,在同样的配置下则一定有加密 $\beta$ 得到 $\alpha$,这个性质也叫自反性
从上面的单字符加密示意图也可以看出来,这有点光路图的感觉,而光路是可逆的XD,所以有完整密钥三元组的话,只需要用密钥配置enigma机,然后加密一遍密文就行了
任何字符在任何转子配置下一定不会加密得到其本身,也就是无论什么时候加密 $\alpha$ 都一定不会得到 $\alpha$
了解上面这些对这个题目就已经差不多了,接下来就回到题目。
题目
题意很简单:
连接上靶机后,靶机先生成30个随机英语字母,然后随机选定一个位置pos,在pos处插入一段key,得到待加密的明文text
用不同的enigma密钥对text进行加密,并发送密文,重复11次(10次tmpkey和1次datekey本质上来说没啥区别)
不同的enigma密钥指密钥的三个部分都完全不同:
- 随机选的三个rotors以及排列顺序
- 三个rotors对应的起始刻度
- plugin的设置
要求我们在150s内发送回一段文本ans,ans和text相同字母占比超过0.85就可以拿到flag
恢复text中的key
我们除了密文以外没有关于密钥的任何信息,好在题目还给了个task里完全没有出现过的msg,而很容易就联想到这个msg的内容应该和未知的静态key有关,而最合理的猜测就是key就在这段msg中。
与靶机简单交互一下会发现key的长度应该是17,所以现在的问题就是key究竟是msg中的哪17个字符。
此时我们就要利用上面讲的第二个性质:任何字符在任何转子配置下一定不会加密得到其本身,所以我们可以拿msg中的所有17个连续字符来与每次交互的11个密文做比对。具体步骤类似于滑动窗口,我们可以把11个密文以及我们假设的长为17的key一起排列起来,如下:
1 | OSXJMOYNOBIUZBYHLTMLIMUDTBICTWVXSZEKHKAVPJKSLIP |
我们可以爆破msg里所有17个连续字符当作这段XXXXXXXXXXXXXXXXX,虽然位置pos我们并不知道,但是有一个事情是定的:这11个密文加密的都是同一个text,也就是key被加密的位置一样,所以如果当前爆破的key能够满足下面条件的话:
- key在某一个pos,与上面11段密文在当前位置的那一段都没有相同的字符
这就说明它满足enigma的性质,就有可能是正确的key,反之则一定不是正确的key,可以用代码简单验证一下:
1 | from pyenigma import rotor |
可以发现会有多个可能的key,说明密文组数还是不太够,但由于key显然是静态的,所以多次交互求个交集就可以了,最后可以得到key是:
1 | key = "THEWEATHERTODAYIS" |
恢复enigma的密钥
在拥有key的同时,我们也拥有了pos,所以现在我们现在拥有很多个明密文对如下:(数据只做示例用)
1 | THEWEATHERTODAYIS |
然而每次enigma加密用的密钥三元组都是不一样的,也就是说这么多组明密文对,每组其实都对应一个不同的enigma机,我们只需要破解其中一台,就可以恢复完整text了。因此不妨选第一组做示例:
1 | THEWEATHERTODAYIS |
此时我们知道的明密文对应关系是:
1 | ----THEWEATHERTODAYIS-------------------------- |
图灵把这种明密文对叫做一个crib,而我们现在要做的就是利用这个crib去破解这台enigma完全未知的密钥三元组,我们再回顾一下这三元组分别是什么,有多少种可能:
每次加密时有以下一些不同的配置,这些配置共同构成本次enigma加密的密钥:
五个rotors里面选取其中三个并作排列,如:IV、V、II
种数共有 $A(5,3) = 5 \cdot 4 \cdot 3 = 60$
每个被选中的rotor需要设置一个起始刻度,如:Q、V、M
种数共有 $26^3 = 17576$ 种
插线板需要选择十对字母两两组合,如:AB、CD、EF、GH、IJ、KL、MN、OP、QR、ST
种数共有 $\prod_{i=0}^{9}(\frac{26-2i}{2}) \approx 2^{69}$ 种
剩下没有配对的六个字母可以看作自己和自己交换(也就是不交换),如:UU、VV、WW、XX、YY、ZZ
可以看出如果直接暴力求解的话,复杂度其实主要来自于plug的设置,如果已知plug的话,剩下要爆破的复杂度一共为:
这就很容易接受了。
而图灵做的工作意义很重大,他做到的事情概括下来是:
假设给定一个或多个crib
crib肯定越多越好,越长越好,因为额外信息就更多
爆破密钥的前两个组成元素,也就是rotors和起始刻度
求出所有可能的plug设置
也就是说,图灵可以用2^21左右的可以接受的复杂度来求出少数的可能的plug,这就很厉害了。
效率很低的dfs
而其实这个工作的思路虽然说不容易想到,但图灵已经做出了成果,而理解这个成果其实并不难,核心思路就是剪枝+回溯,我们以刚才的crib做例子:
1 | ----THEWEATHERTODAYIS-------------------------- |
我们爆破所有rotors和起始刻度,也就是我们除了plug的设置以外,当前的enigma已经可以跑起来了。那么对第一个字符T,我们知道其加密为了J,我们需要:
- 假设plug中的设置含有”TA”,那么T经过plug会到A
- 把A送进我们当前配置好的enigma进行加密,他会出来一个确定的字符,假设为B
- B经过plug得到J,说明plug中一定也含有”BJ”
既然没有产生矛盾,那我们就继续向下推进一个字符,来到第二个字符H:
- 由于plug中每个字符仅能与一个字符配对,而此时A、B在刚才的推导中都出现过了,所以我们此时假设plug中含有”HC”
- 把C送进我们当前配置好的enigma进行加密,他会出来一个确定的字符,假设为T
- T经过plug得到W,说明plug中一定也含有”TW”
此时就产生了矛盾,因为plug中每个字符仅能与一个字符配对,不会同时出现”TA”和”TW”。
这说明我们的假设中有地方出错了,那么我们就需要一层层向上回溯,而第一个回溯到的猜测点是”HC”,所以我们下面猜”HD”,然后如法炮制。
如果H和所有字母配对都会产生矛盾怎么办?这说明我们最开始猜测的”TA”就不对,那么我们就需要回溯到最开始,并猜测”TB”,然后往后进行。
如果T和所有字母配对都会产生矛盾怎么办?这说明一个事情:我们配置的enigma机出了问题,也就是说本次爆破的rotors和起始刻度是错误的,所以此时我们就该更换下一个待爆破的enigma机的配置,然后重复刚才的猜测-回溯过程了。
这一部分代码如下:
1 | from pyenigma import rotor |
可以测试出来这样回溯找到正确plug的概率是相当高的,然而问题就在于:他太慢了。测一下要爆的时间得要40h-250h不等(和实际的crib本身结构有关)。
而本身德军用enigma,他们的密钥是一天一换的,所以就算放在当时,我现在写的脚本可能实时性都不够强XD,因此要换思路。
Turing’s method
而图灵用的是一种更巧妙的剪枝思路,具体来说他做了下面的事情:
我们仍然以刚才的crib为例:
1
2
3----THEWEATHERTODAYIS--------------------------
----JWPNHTWXTUYYXUSLJ--------------------------由于enigma的自反性,crib中的明密文可以在同样的配置下互相加密得到,所以可以先把crib的结构绘制成图:
图中存在很特殊的结构,就是环,比如对于”TJSY”这个环,他是该段crib以下部分的明密文形成的:
1
2
3----T---------T---Y-S--------------------------
----J---------Y---S-J--------------------------利用crib中这种环结构,我们可以进一步简化我们的搜索思路。我们从环的任意一个节点开始,比如T,假设他经过plug变成A,那么此时在给定的配置下,我们可以畅通无阻的跑一遍这个环,直到他回到T,也就是:(小写的字母代表变量)
1
2
3
4
5
6
7----T---------T---Y-S--------------------------
plugin | | | |
----A---------r---q-p--------------------------
enigma | | | |
----o---------q---p-o--------------------------
plugin | | | |
----J---------Y---S-J--------------------------为什么说畅通无阻呢?因为我们假设plugin存在”TA”后,假设A经过给定配置的enigma会得到o,那么plugin就一定会有”Jo”,o又可以用enigma跑出p,就一定有”Sp”,如此类推,最后从上图中的第二个链条可以得到一个”Tr”。
那么,按照我们的推断,这个r一定得是A才行,否则说明我们的推断出错了。
这个环结构的好处在于,我们绕过了J、Y、S的plugin设置,只需要讨论T的同时,就可以知道J、Y、S的哪些plugin设置才是合理的。
而又因为enigma的自反性,一旦推出矛盾项,可以说明整个逻辑链条上的所有plugin设置都是不合理的。比如我们以假设”TA”开始推测,一路推测出”RJ”、”SE”、”XY”,最后产生矛盾的”TB”,这代表”TA”、”RJ”、”SE”、”XY”、”TB”都一定是不合理的,所以我们下一次推测就不需要再讨论”TB”了,从”TC”开始就行。
然而对于一个环来说,即使我们当前爆破的enigma参数推的不正确,对于环依然是有一定概率存在字母,使得其不产生矛盾的。而这个概率直观来看也就是1/26。
而我们要爆破的enigma配置参数范围是:
所以要想可能的plugin越少,就需要crib的环尽可能多,如果crib有三个环乃至四个环,那么得到的plugin就越少,就越可能正确。
因此如果从题目角度看,多roll一些crib,然后挑选其中环多一些的去爆破应该耗时会少很多,准确率也更高,用代码测试一下要roll到四个环及以上也并不需要太多次数:
1 | from pyenigma import rotor |
那么接下来就实现一下这种基于多环的dfs:
1 | from pyenigma import rotor |
这个脚本耗时就相对来说能接受很多了,爆破完所有一般只需要一小时以内,对于赛题来说,不管是多进程还是直接多次交互赌一个起始刻度都可以控制时间在150s内。
注意两点:
- 上面控制了一直到roll出环数量大于等于8才开始搜索,不然可能的plugin会有点多。
- 最后得到的plugin貌似还是缺一些字符配对,不过我们可以有多种处理方式:
- 1.对有输出的rotors及配置,再进行一次刚才低效率的彻底的深搜
- 2.直接爆破剩余的字符,这个复杂度并不大
不得不说这个破译真的很艺术,奈何码力不够强,比赛中知道破译手段但是码不出来,还是要多练习。