解密转轮机加密:从原理到实战的Python实现
1. 转轮机加密一段尘封的机械密码史如果你看过一些二战题材的电影比如《模仿游戏》里面可能出现过这样的场景一排排像打字机又像齿轮箱的机器操作员在键盘上敲击另一边的灯板就亮起不同的字母。没错那就是大名鼎鼎的恩尼格玛密码机而转轮机加密正是这类机械密码设备的核心思想。今天我们不聊那些动辄几十万美金的古董机器而是用你手边的电脑通过几十行Python代码亲手“复刻”并破解一台简易的转轮机体验一把密码破译员的快感。简单来说转轮机加密就像一套复杂的“字母轮盘赌”。想象一下你有好几个写满字母的转盘我们称之为“轮子”每个转盘都可以独立旋转。加密时你按照一个秘密的顺序密钥排列这些轮子然后输入一个字母第一个轮子转一下带动第二个轮子就像老式汽车里程表那样。最终每个轮子停下的位置所指向的字母组合起来就成了密文。解密呢就是把这个过程倒过来你知道密文和轮子的初始状态也知道密钥顺序你需要把轮子“倒转”回正确的位置才能看到原本的明文。听起来是不是有点绕别担心我们今天要对付的这道来自“攻防世界”CTF平台的“转轮机加密”题目是一个简化版本。它已经把最复杂的机械联动部分去掉了只留下了最核心的“排列”与“旋转”逻辑。我们的任务就是扮演一名密码分析员拿到一段密文、一堆乱序的字母轮以及一个神秘的密钥数字串然后用Python写个脚本让这些轮子各归其位最终拼出那句关键的flag。这不仅是学习密码学更是一次非常棒的编程思维训练你会发现那些看似神秘的加密其内核逻辑用代码表达出来竟是如此清晰和优雅。2. 拆解原理把密码机“大卸八块”在动手写代码之前我们得先搞清楚手里的“零件”都是什么以及它们是怎么组装起来工作的。原题给了我们三样东西一组轮子、一个密钥、一段密文。我们一个一个来看。2.1 核心零件字母轮子首先是最基础的零件——字母轮。题目里给了13个字符串每个字符串就是一个轮子。比如第一个轮子是ZWAXJGDLUBVIQHKYPNTCRMOSFE。你可以把它想象成一个环26个字母注意这里没有空格就是26个字母的一种特殊排列首尾相接。这个排列顺序是固定的也是公开的它不是秘密。秘密在于这个轮子初始时哪个字母朝上或者说处于“读取位”。在简化模型里我们不用关心它怎么转我们只关心它的“当前状态”。初始状态就是题目给出的这个字符串我们假设它的第一个字母Z就是当前朝上的位置。整个加密和解密的过程本质上就是调整每个轮子的“当前状态”也就是让这个字符串循环移位。2.2 组装顺序密钥的作用你有13个轮子如果胡乱堆在一起那肯定没法用。密钥就是一串数字比如[2, 3, 7, 5, 13, 12, 9, 1, 8, 10, 4, 11, 6]。它的意思是请你把这13个轮子按照这个顺序排成一排。具体来说就是把第2个轮子放在第1位第3个轮子放在第2位第7个轮子放在第3位……以此类推。密钥决定了轮子的物理排列顺序这是加密者设置的第一个秘密。2.3 加密瞬间从明文到密文假设现在轮子排好了并且都处于初始状态每个字符串的第一个字母朝上。加密者要加密明文比如HELLO。他会怎么做呢在这个简化模型里过程被极度精简了密文的每一个字母直接取自对应轮子当前朝上的那个字母。更具体地说加密者并不是输入H让机器运转而是直接根据一个预设的“密文”来反向设定轮子的状态。题目给我们的就是最终结果密文NFQKSEVOQOFNP。这串密文的含义是当加密完成后排好序的13个轮子它们朝上的字母依次就是N,F,Q,K,S,E,V,O,Q,O,F,N,P。我们的任务就是从这个结果反推出轮子在加密过程中被旋转到了什么状态进而看到所有轮子对齐后每一“列”可能组成的明文。2.4 解密逻辑逆向旋转的艺术解密的核心动作是“旋转轮子”。已知密文第1位是N它对应排序后的第1个轮子。我们就找到这个轮子字符串里字母N的位置然后把这个字符串循环左移直到N移动到字符串的开头。这样这个轮子的“当前朝上字母”就变成了N与密文匹配了。对13个轮子都进行这样的操作后我们就还原了加密完成那一刻所有轮子的状态。这时候你再竖着看这13个已经对齐的轮子字符串从第二列开始第一列已经是密文了每一列都是一种可能的明文片段。因为加密时明文可能就是由某一列的字母组成的。我们需要遍历所有列找出看起来有意义的单词那就是我们要的flag。3. 实战开始手把手编写Python解密脚本理论说得差不多了咱们直接上代码。我会把每一步都掰开揉碎你哪怕Python刚入门跟着做也能完全理解。3.1 准备原材料定义变量首先我们把题目给的“零件”都放进代码里。这里要注意原题的一个小笔误最后一个轮子字符串末尾有个空格我们需要去掉它保证每个轮子都是26个字母。# 初始的13个轮子每个轮子是一个26个字母的字符串 original_wheels [ ZWAXJGDLUBVIQHKYPNTCRMOSFE, PBELNACZDTRXMJQOYHGVSFUWI, BDMAIZVRNSJUWFHTEQGYXPLOCK, RPLNDVHGFCUKTEBSXQYIZMJWAO, IHFRLABEUOTSGJVDKCPMNZQWXY, AMKGHIWPNYCJBFZDRUSLOQXVET, GWTHSPYBXIZULVKMRAFDCEONJQ, NOZUTWDCVRJLXKISEFAPMYGHBQ, XPLTDSRFHENYVUBMCQWAOIKZGJ, UDNAJFBOWTGVRSCZQKELMXYIHP, MNBVCXZQWERTPOIUYALSKDJFHG, LVNCMXZPQOWEIURYTASBKJDFHG, JZQAWSXCDERFVBGTYHNUMKILOP # 注意原数据末尾有空格这里已去除 ] # 密钥告诉我们应该如何排列这13个轮子的顺序 key [2, 3, 7, 5, 13, 12, 9, 1, 8, 10, 4, 11, 6] # 密文加密后13个轮子朝上字母组成的序列 ciphertext NFQKSEVOQOFNP3.2 第一步按照密钥重新排序轮子密钥key里的数字指的是original_wheels列表中的索引位置注意编程中通常从0开始但这里题目给的密钥是从1开始计数的。所以我们需要根据密钥取出对应的轮子组成一个新的列表。# 创建一个空列表用于存放排序后的轮子 sorted_wheels [] # 遍历密钥中的每个数字 for key_number in key: # 密钥数字是从1开始的而列表索引从0开始所以要减1 wheel_index key_number - 1 # 根据索引取出对应的轮子添加到新列表中 sorted_wheels.append(original_wheels[wheel_index]) print(第一步按照密钥重新排序后的轮子) for i, wheel in enumerate(sorted_wheels): print(f位置{i1}: {wheel}) print()运行这部分代码你会看到轮子的顺序已经完全改变了。原来第1个轮子ZWAXJGDLUBVIQHKYPNTCRMOSFE现在跑到了第8位因为密钥里有1而原来第2个轮子现在成了排头兵。这一步模拟了密码机操作员安装轮子并设定顺序的过程。3.3 第二步根据密文旋转每个轮子现在我们有了排好序的轮子sorted_wheels也知道密文ciphertext。密文的第i个字母就对应sorted_wheels中第i个轮子应该朝上的字母。我们的任务是把每个轮子字符串循环移位让指定的字母出现在字符串的开头。这里用到了一个字符串操作的技巧查找字母位置然后切片拼接。# 创建一个列表用于存放旋转后的轮子 rotated_wheels [] # 同时遍历密文和排序后的轮子 for i in range(len(ciphertext)): target_char ciphertext[i] # 当前轮子需要朝上的字母 current_wheel sorted_wheels[i] # 当前轮子字符串 # 找到目标字母在当前轮子字符串中的位置 char_index current_wheel.index(target_char) # 进行循环左移从目标字母开始取到末尾再接上开头到目标字母之前的部分 new_wheel current_wheel[char_index:] current_wheel[:char_index] # 将旋转后的新轮子加入列表 rotated_wheels.append(new_wheel) print(第二步根据密文旋转对齐后的轮子) for i, wheel in enumerate(rotated_wheels): print(f轮子{i1} (密文字母{ciphertext[i]}): {wheel}) print()完成这一步后rotated_wheels列表里的13个字符串它们的第一个字母连起来一定就是密文NFQKSEVOQOFNP。我们成功“倒转”了轮子还原了加密完成时的状态。现在这台密码机就定格在了加密完成的那一瞬间。3.4 第三步遍历所有列寻找可能的明文最激动人心的时刻来了。现在13个轮子像13根柱子一样竖着排开每一行有26个字母。第0列也就是第一列我们已经知道是密文了。那么从第1列到第25列加密者当初看到的明文很可能就藏在其中某一列里。我们需要把每一列的字母都读出来看看。print(第三步输出所有可能的明文从第1列到第25列) print( * 40) # 轮子高度是26我们从第1列开始遍历下标1到25 for col in range(1, 26): possible_plaintext # 遍历每一个轮子取出该轮子在当前列的字母 for wheel in rotated_wheels: possible_plaintext wheel[col] print(f第{col:2d}列: {possible_plaintext})运行这段代码你会看到输出25行乱七八糟的字符串。别灰心这正是密码分析的标准流程——海量筛选。你需要用你的眼睛快速扫描寻找那些看起来像英文单词的组合。当然在这个CTF题目里答案通常是一个有意义的短语。当我运行这个脚本时我一眼就看到了其中一列是fireinthehole。恭喜你这就是我们要找的flag整个解密过程大功告成。你可以把完整的脚本组合起来运行一次感受从原始数据到最终答案的完整流程。4. 深入思考与扩展不止于解题做到这里我们已经成功解出了这道题。但学习不应该止步于此。我们可以从这个简单的模型出发思考更多问题让这次经历的价值最大化。为什么这种方法能解密其根本在于这个简化模型是一种“替换密码”的变体而且密钥轮子顺序和轮子初始状态我们是知道的。在密码学中这被称为“已知明文攻击”或“已知密钥攻击”的理想条件。现实中恩尼格玛机的破译远比你想象中困难因为它的轮子状态转子初始位置、接线板设置每天都会更换并且加密过程是动态的按一个键轮子就转替换表就变。如何让这个加密更强基于我们这个模型至少可以想到几点1.增加轮子数量比如从13个增加到26个排列组合数爆炸式增长。2.隐藏轮子初始排列不告诉破译者轮子的初始字符串是什么。3.引入动态旋转加密每个字母后某些轮子按复杂规则转动这样相同的明文字母在不同位置会被加密成不同的密文。这最后一点就是真实转轮机的核心。用Python还能玩出什么花你可以尝试写一个加密函数反过来体验设计密码的感觉。输入一段明文、一个密钥你的程序能输出密文吗你还可以模拟更复杂的多轮旋转加密或者尝试暴力破解——如果不知道密钥顺序你有办法通过穷举所有可能的排列13! 种超过60亿种来破解吗虽然计算量巨大但写一个这样的框架本身就是对算法和密码学思想的极好锻炼。我自己在第一次实现这个脚本时就犯过一个错误忽略了密钥是从1开始计数的直接当作列表索引去取结果程序报错。调试的过程让我对数据的边界和含义理解更深了。还有一次我忘了去除最后一个轮子的尾随空格导致index()函数找不到字母输出一堆乱码。这些“坑”恰恰是学习路上最宝贵的经验。密码学就像一座充满谜题的城堡转轮机是其中一道古老而经典的大门。今天我们用Python这把钥匙打开了它看到的不仅是fireinthehole这个答案更是一套清晰的逻辑思维方法和“化繁为简”的编程实践。希望下次你在CTF比赛或者看到其他密码相关挑战时能想起今天我们一起转动这些字母轮子的经历拥有拆解复杂系统的信心和乐趣。