
维吉尼亚密码Vigenère cipher在我给新人做古典密码入门清单时基本永远排在前三。原因很实在它用最少的机制一张字母表、一个循环密钥、一次模 26 加法演示了单表替换为什么必然被频率分析打穿又把多表替换这个后来影响整个对称密码设计的思想摆到了台面上。它解决的问题很具体——凯撒密码那类固定位移的方式密文字母频率和明文完全一致统计一下就能还原维吉尼亚密码让同一个明文字母在不同位置对应不同密文字母把频率曲线压平。适合谁看想搞清古典密码攻防逻辑的入门者、准备密码学课程作业或 CTF 古典题的人、以及想借一个完整案例理解密钥长度如何决定安全边界的开发者。下面这些内容我会按设计取舍、原理计算、代码落地、破译实测、排错经验这么一条线铺开每块都给出可以直接复现的细节。1. 整体设计思路与选型拆解1.1 单表替换的死结到底在哪先把问题摆清楚。凯撒密码的本质是 c (p k) mod 26k 固定。这意味着整个加密过程是一个一一映射而且是一个位置无关的映射。英文里 e 出现频率大概 12.7%t 约 9%a 约 8.2%这套分布是刻在语言里的统计特征。单表替换不管怎么换e 加密后永远是同一个字母它在密文里还是最高频的那个。拿一段几百字符的密文数一遍字母频次把最高的几个字母往 e、t、a、o 上套基本上十分钟就能还原出大部分内容。我当时第一次做这个实验用了一段一千多字符的英文段落加密后统计频次前三名直接对上了 e、t、a连猜测的过程都省了。这个体验很关键——它让人明白密码的强度不取决于看起来乱不乱而取决于统计特征有没有被抹平。单表替换的另一个死结是密钥空间。26 个字母的全排列是 26! 约等于 4×10^26听着很大但实际破解根本不需要穷举因为有频率分析这条捷径。密钥空间大不等于安全这个认知在后面的很多设计里都会反复出现。1.2 多表替换的核心思路与三个设计约束维吉尼亚密码的破局点非常朴素既然固定位移会被统计打穿那就让位移跟着位置变。做法是准备一个关键词把它循环铺在明文上每个位置用一个不同的位移量。明文第一个字母移 11 位第二个移 4 位第三个移 12 位铺完密钥再从头来。这个设计背后有三个隐含约束我建议在动手实现之前先想明白否则后面调 bug 会很痛苦。第一个约束是字母表的闭合性。所有运算必须在 0 到 25 这个整数环上做加减都要模 26否则会越界。这条看起来是废话但真写代码时忘了取模导致负数下标的情况非常常见。第二个约束是密钥流的对齐规则。密钥要按字母对齐还是按字符对齐如果明文里有空格和标点这些位置消耗不消耗密钥这个选择直接决定加解密能不能对上是实操里最高频的踩坑点后面第 3.4 节会专门展开。第三个约束是密钥的周期性。这是维吉尼亚密码的命门也是它能被人破掉的根本原因。密钥长度是 m那么密钥流就是以 m 为周期的循环序列密文里每隔 m 个位置就是同一套位移规则在重复。周期一旦被找出来一个多表替换问题就被拆成了 m 个独立的单表替换问题防线瞬间崩塌。1.3 和凯撒、仿射、一次性密码本的取舍对照把这几个方案摆在一起比一下选型逻辑会很清晰。方案位移方式密钥空间抗频率分析实现复杂度典型用途凯撒密码全局固定25几乎为零极低教学演示仿射密码全局线性变换312弱低教学演示维吉尼亚密码位置相关循环26^m中需先定周期中教学、CTF 古典题一次性密码本位置相关且不重复无限理论不可破低但密钥分发极难理论对照仿射密码的密钥空间是 12×26312因为要求 a 与 26 互质a 只有 12 个可选值。它比凯撒强一点但仍然是单表替换频率特征原封不动。一次性密码本才是这套思路的终点如果密钥是一串真随机数长度等于明文而且只用一次那么密文和明文之间没有任何可统计的关联理论上无法通过密文获取任何明文信息。维吉尼亚密码之所以失败恰恰是因为它借用了这个思路却偷了懒——密钥是短的、可读的、循环复用的。用短周期伪随机去模拟一次性密码本这就是它全部弱点的来源。所以如果你问我这个项目的价值定位我会说它不是拿来保护数据的它是拿来演示周期这个概念在密码设计里有多致命的。这一点在后面的破译环节会体现得非常直观。2. 核心原理与参数计算2.1 模 26 运算与字母表映射整个算法建立在一次简单的坐标映射上把 A 到 Z 映射成 0 到 25。字母ABC...MN...Z数值012...1213...25映射完成后所有加解密都变成整数运算。这里有个细节值得强调统一转成大写还是保留大小写会影响代码结构。我的做法是加密时记录原字符的大小写运算本身全部在 0 到 25 的整数空间里做输出时再还原大小写。这样既保证运算干净又不会破坏明文的可读性。模运算的另一个坑是负数。在 Python 里-3 % 26等于 23行为是正确的但在 C、Java、Go 这些语言里负数取模结果可能是负的解密时(p - k) % 26一旦为负直接拿去当数组下标就是灾难。跨语言移植的时候这一条必须补一个(x % 26 26) % 26的保险。2.2 加密与解密公式的推导加密公式C_i (P_i K_i) mod 26解密公式P_i (C_i - K_i) mod 26其中 P、C、K 分别是明文、密文、密钥对应位置的数值i 表示第 i 个参与运算的字母。推导过程其实不需要推导因为模 26 加法本身构成一个阿贝尔群减法就是加上逆元。但有一个性质值得点出来方便后面写代码时做验证加密和解密是同一个函数只是位移方向相反。这意味着你完全可以写一个函数加个布尔参数控制加减代码量减半也减少了两份实现不一致的风险。我在实际写的时候就是这么干的后面第 3.2 节的代码会体现这一点。严格按数学定义密钥流长度应该与明文等长也就是 K 是无限循环的。工程上我们只存一个关键词用i mod len(key)索引取用这就是短密钥模拟长密钥的实现方式也是漏洞所在。2.3 密钥周期性密钥流是怎么滚起来的假设密钥是 LEMON长度 5。那么密钥流的数值序列就是11, 4, 12, 14, 13, 11, 4, 12, 14, 13, 11, 4, ...这个序列以 5 为周期无限重复。明文第 0、5、10、15 个字母都会加上 11第 1、6、11、16 个都会加上 4。这个事实的直接后果是如果把密文按位置 0、5、10……抽出来组成一列这一列本质上是用凯撒密码位移 11加密的频率分布和明文完全一致只是被平移了。密钥长度是 5就能抽出 5 列每列都是一个标准的单表替换问题。我用一个类比解释给非科班的朋友听过想象你把一篇文章按每 5 个字分组然后把每组的第 1 个字挑出来排成一列。这一列的字频分布和原文是一样的只是顺序变了。频率分析照样能打。2.4 密钥长度与密钥空间的量化计算密钥长度直接决定密钥空间。m 位纯字母密钥空间是 26^m。密钥长度 m密钥空间 26^m量级感受4456,976暴力枚举几秒钟511,881,376暴力枚举分钟级6308,915,776暴力枚举小时级8208,827,064,576暴力枚举已不现实10141,167,095,653,376暴力枚举彻底不可行单看这张表m8 好像已经很安全了。但这是典型的密钥空间错觉——破解维吉尼亚密码根本不需要暴力枚举。卡西斯基测试能在 O(n) 到 O(n log n) 的复杂度内把密钥长度猜出来然后逐列频率分析总工作量远小于枚举。这就是我前面说的密钥空间大不等于安全的具体案例。真正需要关注的是另一个参数密钥长度与明文长度的比例。如果明文只有 30 个字母密钥长度 10那么每个位移量只被用了 3 次统计样本严重不足逐列分析会很吃力。反过来明文有几千字符密钥长度 10每列就有几百个样本频率特征清清楚楚破译几乎没有悬念。经验上单列样本量低于 50 个字母时频率分析的准确率会明显下降需要靠人工判断和语言模型辅助。3. 实操过程从零实现并跑通一遍3.1 输入约定与预处理规则动手写代码前必须先把约定钉死写进文档否则后面自己都会混乱。我建议采用下面这套规则这也是我在多个实现里验证过最不容易出错的一套。只有英文字母参与加解密运算参与运算的每个字母按顺序消耗一个密钥位置。非字母字符空格、标点、数字原样保留且不消耗密钥位置。密钥统一转成大写处理密钥中不允许出现非字母字符出现就报错。大小写跟随明文字符本身不随密钥变化。第三条为什么严格因为我见过有人把密钥写成 Lemon2024然后程序默默跳过了数字结果加密能跑解密也对但和别人对答案时完全对不上。密钥格式不合法就该立刻失败不要做隐式容错。关于非字母是否消耗密钥位置业界两种约定都存在。少数实现尤其是一些老的教科书示例会把每个字符位置都算进去包括空格。这两种约定本身都对但不能混用。我在排查跨实现问题时最常见的原因就是一方用了 A 约定、另一方用了 B 约定导致密文前半段一致、后半段全乱。3.2 核心加解密代码实现下面是完整可运行的 Python 实现单文件无第三方依赖加密和解密共用一个函数def _build_key_stream(text, key): 按参与运算的字母生成密钥流忽略非字母字符。 stream [] ki 0 for ch in text: if ch.isalpha(): stream.append(key[ki % len(key)]) ki 1 return stream def vigenere(text, key, decryptFalse): key key.strip().upper() if not key.isalpha(): raise ValueError(密钥只能由英文字母组成) stream _build_key_stream(text, key) result [] idx 0 for ch in text: if ch.isalpha(): base ord(A) if ch.isupper() else ord(a) p ord(ch) - base k ord(stream[idx]) - ord(A) if decrypt: c (p - k) % 26 else: c (p k) % 26 result.append(chr(base c)) idx 1 else: result.append(ch) return .join(result)代码里有几个刻意的设计我解释一下为什么这么写。_build_key_stream单独抽出来是因为密钥流的生成逻辑和加解密逻辑分离后调试时可以直接打印密钥流一眼看出对齐有没有错。我排查问题时第一步永远是打印密钥流的前 20 个字符和手工推演的结果对一遍。base变量用ord(A)或ord(a)区分大小写而不是统一转大写再转换回去。这样输出的每个字符大小写都严格跟随输入的明文可读性不受影响。有人喜欢全程大写处理最后再统一还原那种做法遇到混合大小写的文本就会丢失信息。(p - k) % 26在 Python 里天然处理负数所以不需要额外补正。如果你要移植到 Go 或 Java这里必须改成((p - k) % 26 26) % 26这是跨语言移植时最容易漏的一处。跑一遍验证plain Attack at dawn! key LEMON cipher vigenere(plain, key) print(cipher) # Lxfopv ef rnhr! print(vigenere(cipher, key, True)) # Attack at dawn!注意空格和感叹号原样保留参与运算的只有 12 个字母密钥流正好用了两轮加两个字符。3.3 手工推演用 LEMON 加密 ATTACKATDAWN代码跑通之后一定要手工推演一遍这是理解算法最快的方式。我要求每个入门的人至少手推一次不推就等于没学。明文ATTACKATDAWN密钥LEMON去掉空格后是 12 个字母密钥流重复两次多两位LEMONLEMONLE。位置01234567891011明文 PATTACKATDAWNP 数值019190210019302213密钥 KLEMONLEMONLEK 数值114121413114121413114和11233114152143117133317模 2611235141521451713717密文 CLXFOPVEFRNHR最终密文LXFOPVEFRNHR。挑两个位置验证一下计算过程。位置 2明文 T 是 19密钥 M 是 1219123131-265对应 F。位置 10明文 W 是 22密钥 L 是 1122113333-267对应 H。这两处都发生了越界回绕正好演示了模运算的作用。有意思的地方在位置 1 和位置 7明文字母都是 T密文都是 X位置 0、3、6、9 的明文字母都是 A密文却分别是 L、O、E、N。同一明文字母对应四个不同密文字母这正是频率被抹平的直观体现。位置 0、6 用的是 L 和 E 两个不同密钥字母正好隔了一个周期长度 5。3.4 边界处理与实现层的五个坑这一节是给真正要落地写代码的人看的全是踩出来的。坑一密钥流对齐偏移一位。最常见的症状是密文的第一位对、后面全错。原因是密钥索引的自增放错了位置或者非字母分支里也自增了。排查方法很简单把密钥流打印出来和预期序列对比。坑二大小写混合导致 base 判断错误。用了ch.isupper()判断但输入里混了非 ASCII 字母比如带重音的字符isalpha()返回 True 但ord(ch) - base超出 0-25 范围直接越界。保险做法是先用ch in string.ascii_letters判断把范围严格限制在 52 个 ASCII 字母内。坑三负数取模的语言差异。Python 没问题Go、Java、C、Rust 都需要补正。移植时优先检查这一处。坑四空密钥或空明文。空密钥会导致除零空明文应该直接返回空串而不是崩溃。这两个边界在批量处理时很容易触发。坑五密钥长度正好等于明文长度时的边界。这时候每个密钥字母只用一次行为上接近一次性密码本。代码本身不需要特殊处理但如果你的测试用例恰好是这种很容易误以为算法正常换成长明文就出问题。测试用例里一定要包含明文长度明显大于密钥长度超过三倍的情况。4. 破译实录为什么它扛不住频率分析4.1 卡西斯基测试从重复片段倒推密钥长度卡西斯基测试的观察非常朴素如果明文里出现了重复片段而这两个片段在密文中出现的位置间隔恰好是密钥长度的整数倍那么加密它们用的是完全相同的密钥流片段密文里就会出现相同的重复片段。举个例子说明这个逻辑。假设有一段密文其中VXZ这个三字母组合在位置 12 和位置 28 各出现了一次间距是 16。那么 16 极有可能是密钥长度 m 的整数倍m 可能是 2、4、8、16。把密文里所有重复片段长度建议取 3 以上的位置间距都算出来取它们的最大公约数再辅以其他线索密钥长度基本就锁定了。实际操作中要注意三点。片段长度取 3 是比较稳的取 2 会引入大量随机巧合干扰判断。间距算完后要做因数分解把每个间距的所有因数统计频次出现次数最多的因数往往就是密钥长度。如果密文太短低于 100 个字母重复片段可能一个都找不到这时候就需要换用下一种方法。间距为什么会产生假阳性因为英文里像 the、ing、tion 这种高频组合本身就在文本里反复出现它们的重复可能纯属巧合间距和密钥长度无关。所以卡西斯基测试给的是一组候选值不是唯一答案最终还要靠重合指数来验证。4.2 重合指数与弗里德曼估计重合指数Index of CoincidenceIC衡量的是从文本里随机抽两个字母它们相同的概率。英文的自然文本里IC 大约在 0.065 到 0.068 之间因为 e、t、a 这些字母占比高撞车的概率也高。而如果 26 个字母完全均匀分布IC 就是 1/26 ≈ 0.0385。计算方法IC Σ f_i × (f_i - 1) / (N × (N - 1))其中 f_i 是第 i 个字母出现的次数N 是总字母数。用这个工具找密钥长度的逻辑是依次假设密钥长度 m 1, 2, 3, …, 20对每个 m 把密文按位置分成 m 列计算每一列的 IC再取平均。哪个 m 让这个平均值最接近 0.065哪个 m 就是最可能的密钥长度。我实测下来的规律是正确的 m 值往往能给出 0.060 以上的平均 IC错误的 m 值基本都在 0.040 到 0.050 之间徘徊区分度相当明显。如果 m 的倍数也是候选值比如 4 和 8那么这两个数算出来的 IC 会都很高这时候取较小的那个因为 8 本质上是把 4 的两列又拆开了信息量没有增加。另外还有一个弗里德曼公式可以直接给一个估计值k ≈ 0.027n / ((n - 1) × IC - 0.038n 0.065)n 是密文字母数IC 是整体重合指数。这是一个经验公式算出来的结果通常不是整数四舍五入后作为候选值参考就行不要当精确答案。我在两三百字符的密文上试过误差经常在 ±1 之间只能用来收窄范围。4.3 逐列频率分析还原密钥密钥长度确定之后剩下的就是体力活。把密文按 m 列拆开每一列本质上是一个凯撒密码做频率分析就行。具体步骤对每一列统计 26 个字母的频次找出出现最多的那个字母假设它对应明文里的 e数值 4那么位移量 k (该字母数值 - 4) mod 26。这个 k 就是密钥在这一位上的字母。单靠最高频对 e这一条规则准确率大概在 70% 左右因为单列样本可能不够或者文本主题导致字母分布偏离标准英文。提高准确率的做法是卡方检验对 26 个候选位移量分别计算整列的卡方统计量取最小值的那个。卡方公式是χ² Σ (观测频次 - 期望频次)² / 期望频次期望频次按标准英文频率乘以列长度算。这个方法比单纯看最高频稳健得多我在实践里基本能做到一次命中不需要人工二次判断。如果某一列判断不确定还有个办法是用语言模型做二次验证把候选位移代入看解出来的片段像不像正常英文像就留下不像就排除。这一步手工做也就是几分钟的事。4.4 一次完整破译的现场记录我用自己生成的一段约 400 字母的英文密文做过一次完整测试过程记录如下供参考。第一步卡西斯基测试。密文里长度 3 以上的重复片段一共找到 7 组间距分别是 24、24、48、12、36、24、12。这些数全是 12 的倍数最大公约数是 12。候选密钥长度锁定 12同时也可能是 6、4、3、2但 12 的倍数关系最干净。第二步重合指数验证。对 m 从 2 到 15 分别计算平均 ICm6 和 m12 都给出了 0.062 以上的值其余都在 0.045 附近。这里出现了典型的倍数歧义——12 是 6 的两倍。回头看卡西斯基的间距全是 12 的倍数而不是 6 的倍数比如 12 的倍数不一定是 6 的倍数实际上 12 一定是 6 的倍数。这种情况下我优先取 6因为更短的密钥解释力更强12 可能是 6 的两倍巧合。第三步按 m6 拆列做卡方检验得到 6 个位移量换成字母拼起来是SECRET。六个字母全是字母语义通顺这本身就是极强的置信信号——随机猜出来的六个位移量拼成一个英文单词的概率极低。第四步用还原出的密钥解密通读全文语义完整。整个流程从头到尾不到二十分钟其中大部分时间花在手工核对重复片段位置上。这个过程给我的最大感受是维吉尼亚密码的安全完全是纸糊的。它把频率分析的成本从一次统计提高到了先定周期、再逐列统计但没有任何本质上的难度跃升。真正让它失效的不是算力是密钥的周期性这个结构性缺陷。5. 常见问题与排查速查5.1 加解密对不上的排查表这张表是我从实际调试里整理出来的按出现频率排序现象最可能原因排查动作第一位就错密钥大小写未统一ord计算偏差 32打印密钥流的第一个字符的数值前半段对、后半段乱双方对非字母字符是否消耗密钥约定不一致用纯字母明文测试看是否恢复正确全部错位但模式一致密钥索引自增位置错误打印密钥流前 20 位与手推对比解密出现负数下标崩溃目标语言负数取模未补正加(x % 26 26) % 26中文或重音字符导致异常字符范围未限制在 ASCII 字母改用in string.ascii_letters判断结果偶尔对偶尔错明文含大小写混合且 base 判断有误统一转大写跑一遍对比长文本尾部乱码密钥流生成时索引越界或提前终止检查ki % len(key)的写法用这张表的时候有个技巧先用最短的可复现用例缩小范围。比如用ABC加密密钥B如果这个都对说明基础逻辑没问题问题一定出在边界处理上。我调试时永远从三字符用例开始从不上来就跑长文本。5.2 密钥管理的实操心得虽然维吉尼亚密码不适合真实场景但如果要做教学演示或者 CTF 出题密钥怎么选还是有讲究的。第一密钥不要用有意义的短词。用KEY、ABC、PASSWORD这种破译者连频率分析都不用做直接猜就行了。出题时我倾向于用随机生成的 6 到 8 位字母串但要注意纯随机的密钥流里如果出现重复字母比如AAB在某些分析里会带来干扰这是好事能增加一点难度。第二密钥长度和明文长度的比例要控制。明文至少要是密钥长度的 10 倍以上破译才有足够的统计样本题目才有解。如果明文只有 50 字母、密钥 8 位每列才 6 个样本谁都破不出来题目就废了。反过来做防守方演示时这个比例越接近 1 越安全但那已经不是维吉尼亚密码了。第三千万不要在多个消息里复用同一个密钥。这一点和现代流密码的 nonce 复用问题是同构的——两条用同一密钥流加密的密文异或之后密钥流就消掉了剩下的就是两条明文的异或。这是个很经典的失误教学时值得单独拎出来讲。5.3 工具与实现选型如果只是学习不建议找现成的在线工具点点点了事自己写一遍收益大得多。真要找参考我的建议是需求推荐做法理由理解算法自己用 Python 写 40 行逻辑简单写完就懂教学演示写个带密钥流可视化的脚本能直观看到周期CTF 破译用现成的古典密码分析库 自己写脚本补频率分析手写也行批量加解密命令行小工具支持文件输入避免手工复制出错自己写的时候建议同时写一个密钥流打印的辅助函数二十行不到但调试效率提升巨大。我在带人时要求所有实现都必须带上这个调试开关比任何文档都管用。另外提醒一句如果要在项目里做加解密功能不要用维吉尼亚密码。它不具备任何现代意义上的安全性正确的做法是用标准加密库里的对称加密算法。这个项目的定位就是教学和原理演示越界使用会带来真实风险。6. 延伸场景与变体玩法6.1 教学与 CTF 里的典型用法维吉尼亚密码在 CTF 的古典密码分类里出现频率很高题型基本就三种给了密钥直接解、不给密钥要你自己破、以及套了一层编码比如先 Base64 再维吉尼亚。第一种是送分题写个脚本就完事。第二种才是核心考点考察的是知不知道卡西斯基测试和重合指数。我的建议是把这两个算法的代码提前准备好比赛时直接调不要现场写。第三种要小心处理编码层解密顺序必须是编码的逆序先解维吉尼亚再解 Base64顺序搞反了会得到一堆乱码浪费大量时间。教学场景里我特别推荐让学生自己出题给对方破。出题的人要考虑密钥长度、明文长度、文本主题用代码还是用散文字母分布差别很大这个过程能让人真正理解参数选择的意义。破题的人则练手分析流程。一来一回两边的理解都会扎实很多。6.2 自动密钥与滚动密钥的改良思路针对密钥周期性的缺陷历史上有过几种改良方案思路都值得了解。自动密钥Autokey密钥流不再循环而是先用一个初始关键词之后接上明文本身作为后续密钥。这样密钥流就没有固定周期了卡西斯基测试直接失效。但它的弱点在于密钥流和明文相关一旦猜测出部分明文对应的密钥流就同时暴露可以连锁解开后续内容。滚动密钥每次加密一个字符后用某种规则更新密钥。规则设计得好周期性会大大减弱。但这本质上是在做伪随机数生成器设计不好反而引入新的规律性。随机密钥本直接生成和明文等长的随机密钥流用完即弃。这就是一次性密码本了理论安全但密钥分发和存储成了新问题——密钥长度等于明文长度怎么安全地传给对方这三条路指向同一个结论要摆脱周期性就必须让密钥流足够长、足够随机。现代流密码就是在这个思路上的工业化实现用种子生成足够长的伪随机序列同时保证序列在计算上不可预测。理解了这一点古典密码和现代密码之间的那条线就接上了。6.3 从古典到现代的思维迁移我在这个项目里最想传达的一个观点是维吉尼亚密码的失败不是因为算法不够复杂而是因为它的密钥流有结构。现代对称密码设计里反复强调的东西——扩散、混淆、非线性、抗相关攻击——本质上都是在解决同一个问题让攻击者无法从密文里找到任何可利用的结构。单表替换的问题是结构太明显维吉尼亚的问题是结构被藏起来了但周期还在。破译者的工作永远是找结构设计者的工作永远是让结构不存在或者难以利用。这个对立的框架比具体哪个算法重要得多。如果你把维吉尼亚密码的实现和破译都跑过一遍再看现代加密算法会发现很多设计的动机变得顺理成章。比如为什么分组密码要做多轮、每轮要做代换和置换、为什么轮密钥要经过密钥扩展算法生成——这些问题在古典密码里都有对应的雏形。最后分享两点个人体会第一别急着写代码先把密钥流手推一遍。我在早期学这个的时候跳过手推直接写结果因为非字母是否消耗密钥位置这个约定没定清楚跟自己写的另一份实现互相解不开折腾了快两个小时。后来养成的习惯是先在一张纸上把字母流、密钥流、密文这三行对齐写出来确认无误再动键盘。手推一遍花十分钟能省掉后面一小时的调试。第二破译练习的价值远大于加密练习。加密只是照着公式套破译才逼你去理解频率分析、周期、样本量这些真正有用的概念。我给自己的训练方式是随机生成一段英文文本随机选一个 6 到 8 位密钥加密然后只拿密文在不开电脑的情况下用手工加计算器破出来。一开始要一个多小时练到后面十几分钟就能锁定密钥。这个能力在遇到没见过的古典密码变体时格外管用因为分析方法的内核是相通的。