一、背景、来历与用途:先搞清它们从哪来
学一个密码,如果只背规则,很快就会忘。所以先花三分钟把时间线捋清楚。
1.1 背景:单表替换的致命伤
最早的替换密码(凯撒密码、仿射密码)都是单表替换:每个字母固定换成另一个字母。它的问题很直白:
一个字母永远映射到同一个字母,字母频率被完整保留;
英文里
E / T / A / O占比极高,攻击者拿一段密文做频率统计,再配合TH / HE / IN这类常见字母组合,很快就能还原明文。
只要统计规律不变,替换关系就一定能被反推。这就是 19 世纪密码学家必须解决的痛点。
1.2 中间站:Playfair 的突破与它的两个麻烦
1854 年出现的Playfair 密码换了个角度:一次加密两个字母(digraph),让单个字母的频率统计失真。它只用一个5×5 方阵,规则三条:
| 情形 | 规则 |
|---|---|
| 两字母同一行 | 各自右移一格(到行尾回卷到行首) |
| 两字母同一列 | 各自下移一格(到底部回卷到顶部) |
| 两字母既不同行也不同列 | 构成矩形的对角,互换列 |
Playfair 一度是英军的标准战地密码(布尔战争、一战中都有使用),但它有两个让人难受的地方:
规则分叉:三种情况要分别处理,战地手算时容易看错"到底同不同行/同不同列";
必须处理双字母:明文里出现
LL、SS这类连续相同字母时,同一格无法构成矩形,规则失效,必须先插入填充字符(如X)把它们拆开;解密时还要再把填充字符删掉。
1.3 来历:Delastelle 的"多用几个方阵"
1899 年前后,法国密码学家Félix Delastelle(1840–1902)给出了一个更优雅的解法。他的代表作是 1902 年出版的《Traité élémentaire de cryptographie》(密码学基础教程),四方密码正是这本书里最有名的发明之一;二方密码同样属于他这一套体系,在历史上还有个外号叫double Playfair(双 Playfair)。
他的核心思路只有一句话:
与其让一个方阵承担所有情况,不如用多个方阵分工——第一个字母去 A 阵定位,第二个字母去 B 阵定位,密文字母则从另外的方阵里"交叉取"出来。
这样一来:
特殊分支消失了,只剩一条"矩形规则";
两个明文字母位置相同时(双字母),取出的两个密文字母来自不同方阵,天然不会退化成同一个字母——双字母问题自动消失。
按使用方阵的数量,就有了本文的两个主角:
| 名称 | 英文 | 方阵数量 | 方阵排列 |
|---|---|---|---|
| 二方密码 | Two-square cipher | 2 | 左右并排(或上下堆叠) |
| 四方密码 | Four-square cipher | 4 | 2×2 田字格 |
1.4 用途:它们当年用来干什么
当年(19 世纪末~二战):
战地手工加解密。这类密码只要一支笔、一张纸、一张写好的方阵卡片,不依赖任何机器,非常适合前线电台、侦察分队、情报员在野外的即时通信。
军事通信。二战期间德军使用的
Doppelkastenschlüssel(双密码卡)就是二方密码这一类的实现;对当时的通信兵来说,它比 Enigma 更"随手可用"——虽然强度远不如机器密码。低强度、短时通信。情报传递、一次性暗号这类"只要撑过很短一段时间"的场合,用它换来的便利性很划算。
现在:
教学。密码学课程用它讲"多字母替换""密钥空间""统计攻击"这几个核心概念:规则清晰、可以完全手算,又能清楚展示"设计上的小改动会带来多大的强度差异"。
CTF 与谜题。CTF 的古典密码题、报纸上的字谜栏目、密码爱好者社区里,二方/四方密码都是常客。
复古游戏与兴趣项目。很多解谜游戏、密码学小工具里都能看到它的实现。
但必须强调:它们都是古典密码,在现代计算能力面前不能保护任何真实数据。学习它们的价值在于理解原理和设计取舍,而不是拿来加密你的文件。
二、地基:25 个字母的 5×5 方阵
2.1 字母表怎么放进方阵
英文字母有 26 个,塞不进 5×5=25 的格子,所以传统做法是合并两个字母:
最常用的是
I和J合并(本文全部采用这一种,遇到J一律当I处理);也可以合并
C/K、V/W、I/Y等,只要双方约定一致即可。
因此方阵里用的是这 25 个字母:
A B C D E F G H I K L M N O P Q R S T U V W X Y Z
注意:没有J,字母表在I之后直接跳到K。
2.2 标准方阵(明文方阵)
最简单的排法就是按字母表顺序从左到右、从上到下填满,这也是四方密码里"明文方阵"的固定形态:
| 行\列 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | A | B | C | D | E |
| 1 | F | G | H | I | K |
| 2 | L | M | N | O | P |
| 3 | Q | R | S | T | U |
| 4 | V | W | X | Y | Z |
本文所有方阵的行、列都从 0 开始编号,方便和代码里的数组下标直接对应。
2.3 关键字方阵怎么造(三步)
光用标准方阵就没有密钥了,所以至少要有一些方阵是用"关键字"打乱的。生成规则三步:
关键字转大写,
J换成I;从左到右写下关键字里的字母,重复出现的只保留第一次;
关键字写完后,把字母表中还没出现过的字母按顺序补在后面,填满 25 格。
举个例子,关键字是EXAMPLE:
去重后的关键字:
E X A M P L(6 个字母)剩余字母按序补:
B C D F G H I K N O Q R S T U V W Y Z拼起来:
E X A M P L B C D F G H I K N O Q R S T U V W Y Z
排成方阵就是:
| 行\列 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | E | X | A | M | P |
| 1 | L | B | C | D | F |
| 2 | G | H | I | K | N |
| 3 | O | Q | R | S | T |
| 4 | U | V | W | Y | Z |
2.4 本文要用到的 4 个关键字方阵
为了后面举例,这里一次性把四个方阵都造好(都用上面的三步规则,建议你自己动手验证一遍):
方阵 A:关键字EXAMPLE
| 行\列 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | E | X | A | M | P |
| 1 | L | B | C | D | F |
| 2 | G | H | I | K | N |
| 3 | O | Q | R | S | T |
| 4 | U | V | W | Y | Z |
方阵 B:关键字KEYWORD
| 行\列 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | K | E | Y | W | O |
| 1 | R | D | A | B | C |
| 2 | F | G | H | I | L |
| 3 | M | N | P | Q | S |
| 4 | T | U | V | X | Z |
方阵 C:关键字PLAYFAIR
| 行\列 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | P | L | A | Y | F |
| 1 | I | R | B | C | D |
| 2 | E | G | H | K | M |
| 3 | N | O | Q | S | T |
| 4 | U | V | W | X | Z |
方阵 D:关键字CIPHER
| 行\列 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | C | I | P | H | E |
| 1 | R | A | B | D | F |
| 2 | G | K | L | M | N |
| 3 | O | Q | S | T | U |
| 4 | V | W | X | Y | Z |
三、四方密码(Four-square)
3.1 布局:四个方阵摆成田字格
左上角和右下角放同一个标准明文方阵,右上角和左下角放两个关键字方阵:
+-------------------+-------------------+ | PLAIN | CIPHER-1 | | 0 1 2 3 4 | 0 1 2 3 4 | | 0 A B C D E | 0 E X A M P | | 1 F G H I K | 1 L B C D F | | 2 L M N O P | 2 G H I K N | | 3 Q R S T U | 3 O Q R S T | | 4 V W X Y Z | 4 U V W Y Z | +-------------------+-------------------+ +-------------------+-------------------+ | CIPHER-2 | PLAIN | | 0 1 2 3 4 | 0 1 2 3 4 | | 0 K E Y W O | 0 A B C D E | | 1 R D A B C | 1 F G H I K | | 2 F G H I L | 2 L M N O P | | 3 M N P Q S | 3 Q R S T U | | 4 T U V X Z | 4 V W X Y Z | +-------------------+-------------------+对应关系:
| 位置 | 名称 | 方阵来源 |
|---|---|---|
| 左上 | PLAIN | 2.2 节的标准字母表方阵 |
| 右上 | CIPHER-1 | 方阵 A(关键字EXAMPLE) |
| 左下 | CIPHER-2 | 方阵 B(关键字KEYWORD) |
| 右下 | PLAIN | 与左上完全相同的标准字母表方阵 |
关键设计:明文方阵是同一个,但两个密文方阵用的是不同的关键字。这正是四方密码强度高于二方密码的原因,4.5 节会展开讲。
3.2 加密规则:一个矩形,两次交叉取字
把明文按两两一组切开(做成HE LL OW OR LD这样)。对每一组甲乙:
甲(第 1 个字母)在左上明文方阵里定位,得到它的行号 r₁;
乙(第 2 个字母)在右下明文方阵里定位,得到它的列号 c₂;
到右上密文方阵取第
r₁行、第c₂列的那个字母 —— 这是密文第 1 个字母;反过来,用乙的行号 r₂和甲的列号 c₁,到左下密文方阵取第
r₂行第c₁列的字母 —— 这是密文第 2 个字母。
一句话口诀:
甲的行 + 乙的列 → 右上取一;乙的行 + 甲的列 → 左下取二。
几何上,这相当于把左上和右下这两个明文字母看成一个矩形的对角顶点,然后去右上、左下这两个"另外的方阵"里,读另外两个角上的字母——这才是"四方"名字的由来:四个角,各用一次。
3.3 逐步走一遍:加密HELLOWORLD
预处理:转大写、去空格 →HELLOWORLD(10 个字母,偶数,无需补字符)。切分为HE/LL/OW/OR/LD。
第一组HE:
H在左上明文方阵:第1行、第2列;E在右下明文方阵:第 0 行、第4列;右上阵(EXAMPLE)第
1行第4列 →L B C D F里下标 4 的字母 =F;左下阵(KEYWORD)第
0行第2列 →K E Y W O里下标 2 的字母 =Y;得到密文对
FY。
其余各对同理,完整过程如下表:
| 明文对 | 甲的位置(左上) | 乙的位置(右下) | 密文 1 = 右上甲行 | 密文 2 = 左下乙行 | 密文对 |
|---|---|---|---|---|---|
| HE | H(1,2) | E(0,4) | 右上1 =F | 左下0 =Y | FY |
| LL | L(2,0) | L(2,0) | 右上2 =G | 左下2 =F | GF |
| OW | O(2,3) | W(4,1) | 右上2 =H | 左下4 =X | HX |
| OR | O(2,3) | R(3,1) | 右上2 =H | 左下3 =Q | HQ |
| LD | L(2,0) | D(0,3) | 右上2 =K | 左下0 =K | KK |
密文结果:FYGFHXHQKK
注意第二组
LL:两个字母在明文方阵里位置完全相同,但因为一个去右上、一个去左下取值,结果是GF而不是GG。四方密码因此完全不需要像 Playfair 那样插填充字符处理双字母。
3.4 解密:把规则倒过来读
解密时,"甲、乙"的身份变成"密文第 1、2 个字母",查找的方阵也跟着换:
密文第 1 个字母在右上密文方阵里定位 → 得到 (r₁, c₂);
密文第 2 个字母在左下密文方阵里定位 → 得到 (r₂, c₁);
明文字母 1 = 左上明文方阵r₁;
明文字母 2 = 右下明文方阵r₂。
验证密文对FY:
F在右上阵(EXAMPLE)→ 第 1 行第 4 列 ⇒ r₁=1、c₂=4;Y在左下阵(KEYWORD)→ 第 0 行第 2 列 ⇒ r₂=0、c₁=2;明文 1 = 左上1 =H,明文 2 = 右下0 =E⇒ 还原出
HE✓
整串FYGFHXHQKK解回来就是HELLOWORLD,与加密完全对称。
四、二方密码(Two-square)
4.1 布局:两个方阵,两种摆法
二方密码只用两个方阵,有两种变体:
横向二方(horizontal):两阵左右并排 ——第 1 个字母查左阵,第 2 个字母查右阵;
纵向二方(vertical):两阵上下堆叠 ——第 1 个字母查上阵,第 2 个字母查下阵。
两种变体的"交叉取值"逻辑完全一样,本文以横向为例:
+-------------------+-------------------+ | LEFT | RIGHT | | 0 1 2 3 4 | 0 1 2 3 4 | | 0 P L A Y F | 0 C I P H E | | 1 I R B C D | 1 R A B D F | | 2 E G H K M | 2 G K L M N | | 3 N O Q S T | 3 O Q S T U | | 4 U V W X Z | 4 V W X Y Z | +-------------------+-------------------+左阵 = 方阵 C(关键字PLAYFAIR),右阵 = 方阵 D(关键字CIPHER)。
与四方密码不同,这里两个方阵都必须是完整的 25 个字母,因为它们各自被"指定"承担第 1 / 第 2 个字母的定位任务,不存在二选一的歧义。
4.2 加密规则:左边取行,右边取列
对明文的每一组甲乙:
甲在左阵定位 → 得到行号 r₁、列号 c₁;
乙在右阵定位 → 得到行号 r₂、列号 c₂;
密文 1 =左阵r₁(甲的行 + 乙的列,回到左阵取字);
密文 2 =右阵r₂(乙的行 + 甲的列,回到右阵取字)。
口诀:
甲的行配乙的列,回左阵取第一个;乙的行配甲的列,回右阵取第二个。
可以把它想象成:两个方阵被"拼接"成一个 5×10 的大矩形,而密文就是同一行里两个字母的交叉投影。
4.3 逐步走一遍:加密ATTACKATDAWN
明文转大写去空格 →ATTACKATDAWN(12 个字母,偶数)。切分为AT/TA/CK/AT/DA/WN。
| 明文对 | 甲的位置(左阵) | 乙的位置(右阵) | 密文 1 = 左甲行 | 密文 2 = 右乙行 | 密文对 |
|---|---|---|---|---|---|
| AT | A(0,2) | T(3,3) | 左0 =Y | 右3 =S | YS |
| TA | T(3,4) | A(1,1) | 左3 =O | 右1 =F | OF |
| CK | C(1,3) | K(2,1) | 左1 =R | 右2 =M | RM |
| AT | A(0,2) | T(3,3) | 左0 =Y | 右3 =S | YS |
| DA | D(1,4) | A(1,1) | 左1 =R | 右1 =F | RF |
| WN | W(4,2) | N(2,4) | 左4 =Z | 右2 =L | ZL |
密文结果:YSOFRMYSRFZL
拿第一组对照着算一遍:A在左阵第 0 行第 2 列,T在右阵第 3 行第 3 列。
密文 1 = 左阵第 0 行第 3 列 =
P L A Y F里下标 3 的字母 =Y;密文 2 = 右阵第 3 行第 2 列 =
O Q S T U里下标 2 的字母 =S;得到
YS✓
4.4 解密:同样的交叉,反向查找
密文第 1 个字母在左阵定位 → 得到 (r₁, c₂);
密文第 2 个字母在右阵定位 → 得到 (r₂, c₁);
明文字母 1 = 左阵r₁;
明文字母 2 = 右阵r₂。
验证密文对YS:Y在左阵第 0 行第 3 列 ⇒ r₁=0、c₂=3;S在右阵第 3 行第 2 列 ⇒ r₂=3、c₁=2。
明文 1 = 左阵0 =A;明文 2 = 右阵3 =T⇒ 还原出
AT✓
4.5 二方密码的两个硬伤
硬伤 1:同列退化,密文可能等于明文。
横向二方里,如果甲在左阵的列号恰好等于乙在右阵的列号,那么:
密文 1 = 左阵[r₁][c₂] = 左阵[r₁][c₁] = 甲 (列号相同,取回的就是自己) 密文 2 = 右阵[r₂][c₁] = 右阵[r₂][c₂] = 乙
整组字母原封不动地抄进了密文。用本文的PLAYFAIR / CIPHER方阵试试AB:
A在左阵第 0 行第 2 列;B在右阵第 1 行第 2 列;列号相同 → 密文 =
AB。
在这组方阵下,5 个列号 × 5 × 5 =125 种字母组合都会出现这种"原地不动"的情况。而 Playfair 遇到类似情况至少还有"右移一格"的兜底规则,二方密码什么都没有——明文信息直接泄漏。
硬伤 2:行信息暴露,方阵结构可被统计出来。
再看加密公式:密文 1 = 左阵r₁,它必然落在左阵的第 r₁ 行上。也就是说:
密文字母 1 所在的"行",就是明文字母 1 在左阵里所在的"行"。
每行只有 5 个字母,攻击者只要统计"哪些字母会互相映射",就能先把 25 个字母的行划分(分成 5 组、每组 5 个字母)恢复出来,密钥空间被砍掉一大截;行内 5 个字母的排列虽然还剩,但配合频率分析,用不了太长密文就能解出来。
四方密码没有这个问题:密文 1 取自右上阵的打乱位置,密文与明文的位置关系被彻底搅乱,强度明显更高。
五、完整 Python 实现
下面这份代码可以直接复制运行,把上面所有例子跑一遍。
# -*- coding: utf-8 -*- """二方密码 / 四方密码 教学实现:方阵生成 + 加解密 + 演示""" ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ" # 25 个字母,I/J 合并,所以没有 J PAD = "X" # 明文长度为奇数时的填充字符 def build_square(keyword=""): """按关键字生成 5x5 方阵:关键字去重后在前,其余字母按序补全。""" letters = [] for ch in keyword.upper().replace("J", "I"): if ch.isalpha() and ch not in letters: letters.append(ch) for ch in ALPHABET: if ch not in letters: letters.append(ch) return [letters[i * 5:(i + 1) * 5] for i in range(5)] def locate(square, ch): """返回字母在方阵中的 (行, 列),行列从 0 开始。""" ch = ch.upper().replace("J", "I") for r, row in enumerate(square): if ch in row: return r, row.index(ch) raise ValueError(f"字母 {ch} 不在方阵中") def normalize(text): """预处理:转大写、剔除字母以外的字符、J -> I、奇数长度补 PAD。""" text = "".join(c for c in text.upper().replace("J", "I") if c.isalpha()) return text + PAD if len(text) % 2 else text def to_pairs(text): """两两分组:HELLOWORLD -> ['HE', 'LL', 'OW', 'OR', 'LD']""" return [text[i:i + 2] for i in range(0, len(text), 2)] class FourSquareCipher: """四方密码:左上、右下为同一个明文方阵;右上、左下各一个关键字方阵。""" def __init__(self, key1="", key2=""): self.plain = build_square("") self.top_right = build_square(key1) self.bottom_left = build_square(key2) def encrypt(self, text): out = [] for a, b in to_pairs(normalize(text)): r1, c1 = locate(self.plain, a) # 甲的行、列 r2, c2 = locate(self.plain, b) # 乙的行、列 out.append(self.top_right[r1][c2]) # 甲的行 + 乙的列 -> 右上 out.append(self.bottom_left[r2][c1]) # 乙的行 + 甲的列 -> 左下 return "".join(out) def decrypt(self, text): out = [] for a, b in to_pairs(normalize(text)): r1, c2 = locate(self.top_right, a) # 反查,拿到 甲行 / 乙列 r2, c1 = locate(self.bottom_left, b) # 反查,拿到 乙行 / 甲列 out.append(self.plain[r1][c1]) out.append(self.plain[r2][c2]) return "".join(out) class TwoSquareCipher: """二方密码(横向):左阵管第 1 个字母,右阵管第 2 个字母。""" def __init__(self, key1="", key2=""): self.left = build_square(key1) self.right = build_square(key2) def encrypt(self, text): out = [] for a, b in to_pairs(normalize(text)): r1, c1 = locate(self.left, a) r2, c2 = locate(self.right, b) out.append(self.left[r1][c2]) # 左阵:甲行 + 乙列 out.append(self.right[r2][c1]) # 右阵:乙行 + 甲列 return "".join(out) def decrypt(self, text): out = [] for a, b in to_pairs(normalize(text)): r1, c2 = locate(self.left, a) r2, c1 = locate(self.right, b) out.append(self.left[r1][c1]) out.append(self.right[r2][c2]) return "".join(out) def dump(square, title): """把方阵打印成和文中一致的形状。""" print(f"[{title}]") print(" " + " ".join(str(c) for c in range(5))) for r, row in enumerate(square): print(f" {r} " + " ".join(row)) if __name__ == "__main__": dump(build_square(""), "左上/右下:明文方阵") dump(build_square("EXAMPLE"), "右上:密文方阵(key = EXAMPLE)") dump(build_square("KEYWORD"), "左下:密文方阵(key = KEYWORD)") f4 = FourSquareCipher("EXAMPLE", "KEYWORD") ct = f4.encrypt("HELLO WORLD") print("四方密码 HELLO WORLD ->", ct, "-> 解密", f4.decrypt(ct)) f2 = TwoSquareCipher("PLAYFAIR", "CIPHER") ct2 = f2.encrypt("ATTACK AT DAWN") print("二方密码 ATTACK AT DAWN ->", ct2, "-> 解密", f2.decrypt(ct2)) print("退化演示 AB ->", f2.encrypt("AB")) # 同列退化:密文 = 明文运行结果(和文中手算完全一致):
[左上/右下:明文方阵] 0 1 2 3 4 0 A B C D E 1 F G H I K 2 L M N O P 3 Q R S T U 4 V W X Y Z [右上:密文方阵(key = EXAMPLE)] 0 1 2 3 4 0 E X A M P 1 L B C D F 2 G H I K N 3 O Q R S T 4 U V W Y Z [左下:密文方阵(key = KEYWORD)] 0 1 2 3 4 0 K E Y W O 1 R D A B C 2 F G H I L 3 M N P Q S 4 T U V X Z 四方密码 HELLO WORLD -> FYGFHXHQKK -> 解密 HELLOWORLD 二方密码 ATTACK AT DAWN -> YSOFRMYSRFZL -> 解密 ATTACKATDAWN 退化演示 AB -> AB5.1 几个实现细节
normalize里的PAD:二方、四方都按双字母加密,明文长度必须是偶数。长度为奇数时补一个X(也可以用Q、Z)。四方密码虽然不怕"双字母",但仍然要处理奇数长度。J的处理:加密前J -> I,所以解密结果里不会出现J,需要按约定人工还原。关键字要够长、够随机:关键字越短,方阵越接近标准字母表;两个关键字不能相同,否则四方密码的右上、左下阵一致,双字母又会退化成相同密文(比如
LL -> GG),可被直接识别。只处理英文字母:数字、标点、中文在上面的实现里会被直接丢掉。工程上通常先做编码/数字化处理,而不是硬塞进 25 格。
六、对比:二方、四方与 Playfair
| 维度 | Playfair | 二方密码 | 四方密码 |
|---|---|---|---|
| 方阵数量 | 1 | 2 | 4 |
| 加密单位 | 双字母 | 双字母 | 双字母 |
| 核心规则 | 同行右移 / 同列下移 / 矩形换列 | 甲行配乙列、乙行配甲列,交叉取值 | 甲行配乙列 → 右上;乙行配甲列 → 左下 |
| 规则分支 | 3 条,手算易错 | 1 条 | 1 条 |
双字母(如LL) | 必须插填充字符拆开 | 不需要特殊处理 | 不需要特殊处理 |
| 同列退化风险 | 有兜底规则 | 无兜底,密文 = 明文 | 不会退化 |
| 结构弱点 | 单字母频率失真,但双字母频率仍在 | 行信息暴露、同列退化 | 明显强于前两者 |
| 手算友好度 | 中 | 高 | 高 |
| 现代安全性 | 不安全 | 不安全 | 不安全(但三者中最强) |
强度关系一句话:
四方密码 > 二方密码 > Playfair,但它们都属于古典密码,在现代标准下全部不安全。
6.1 四方密码怎么被破解(了解即可)
四方密码是确定性的双字母替换:同样的字母对永远映射到同样的密文对。这意味着:
明文里重复出现的组合(如
ATTACKATDAWN里的AT两次都变成YS)会在密文中暴露结构;密文的双字母频率仍然保留了大量统计特征;
只要密文足够长(几百个字符以上),就可以用频率分析 + 爬山 / 模拟退火搜索自动还原两个关键字,已有公开工具能做到这一点。
二方密码更弱:它的"行信息泄漏"让攻击者能先把方阵的行分组恢复出来,剩下的搜索空间小得多,用已知明文攻击更是几步就能推完。
七、动手练习(附答案)
建议先把上面的方阵抄在纸上,每题都真的用手算一遍,再对答案。
练习 1(四方密码加密)关键字EXAMPLE/KEYWORD,方阵用 3.1 节的布局,加密明文SECRET MESSAGE。
练习 2(四方密码解密)同样方阵,解密NEMSNEMMEWWG。
练习 3(二方密码加密)关键字PLAYFAIR/CIPHER,横向二方,加密明文GOOD LUCK。
练习 4(二方密码解密)同样方阵,解密ZPPISKHV。
练习 5(思考题)在本文的PLAYFAIR / CIPHER方阵下,为什么AB加密后还是AB?这一类"原地不动"的组合一共有多少种?
参考答案
练习 1:先预处理——去空格得SECRETMESSAGE(13 个字母,奇数)→ 补X得SECRETMESSAGEX。切分并查表:
| 明文对 | 密文 1 = 右上甲行 | 密文 2 = 左下乙行 | 密文对 |
|---|---|---|---|
| SE | 右上3 = T | 左下0 = Y | TY |
| CR | 右上0 = X | 左下3 = P | XP |
| ET | 右上0 = M | 左下3 = S | MS |
| ME | 右上2 = N | 左下0 = E | NE |
| SS | 右上3 = R | 左下3 = P | RP |
| AG | 右上0 = X | 左下1 = R | XR |
| EX | 右上0 = A | 左下4 = Z | AZ |
密文 =TYXPMSNERPXRAZ。(注意SS变成了RP,两个字母完全不同——这就是四方密码相对 Playfair 的优势。)
练习 2:NEMSNEMMEWWG切分为NE/MS/NE/MM/EW/WG,逐对反查得ME/ET/ME/AT/DA/WN,即MEETMEATDAWN("meet me at dawn")。
练习 3:GOODLUCK切分为GO/OD/LU/CK:
| 明文对 | 密文 1 = 左甲行 | 密文 2 = 右乙行 | 密文对 |
|---|---|---|---|
| GO | 左2 = E | 右3 = Q | EQ |
| OD | 左3 = S | 右1 = A | SA |
| LU | 左0 = F | 右3 = Q | FQ |
| CK | 左1 = R | 右2 = M | RM |
密文 =EQSAFQRM。
练习 4:ZPPISKHV切分为ZP/PI/SK/HV,逐对反查得WE/LC/OM/EX,即WELCOMEX,去掉末尾填充字符X得明文WELCOME。
练习 5:A在左阵的列号是 2,B在右阵的列号也是 2,两个字母列号相同,矩形退化成一条竖线,"交叉取值"取回的就是自己,所以密文 = 明文。这类组合的数量是 5(列)× 5(左阵每列 5 个字母)× 5(右阵每列 5 个字母)=125 种。
八、总结
背景与来历:单表替换打不赢频率分析 → 1854 年 Playfair 用"一次两个字母"破局,但规则分叉、还怕双字母 → 19 世纪末 Delastelle 用"多个方阵分工"给出更干净的答案,这就是二方密码与四方密码。
当年用途:它们是只靠纸笔就能完成的战地手工密码,用在军事通信、情报传递这类短时低强度场合;今天主要活在教学、CTF 与谜题里,绝不能保护真实数据。
方阵是地基:5×5 装 25 个字母(
I/J合并);标准方阵按字母序填,关键字方阵按"关键字去重 + 剩余补全"三步生成。先把方阵画对,加解密就只剩查表。二方密码:两阵左右并排,甲行配乙列、乙行配甲列交叉取值。规则只有一条、手算舒服,但同列会原地退化,且密文字母的行号暴露了明文字母的行号,强度最弱。
四方密码:四阵摆成田字格,左上右下是同一个明文方阵,右上左下各用一个关键字方阵;加密"甲行乙列去右上、乙行甲列去左下",解密反向查。结构对称、天然不吃双字母,是三者中最强的。
都不安全:它们只是把单字母频率问题升级成了双字母频率问题。学习它们的价值在于体会"多表/多字母替换如何用空间换强度",以及少一条分支规则、多一个方阵,安全性到底差多少。
如果这篇把二方/四方密码的方阵算明白了,欢迎点赞 + 收藏 + 关注。下一篇我会用同一套方阵讲Bifid、Trifid、ADFGVX这几个 Delastelle 家族的兄弟密码,以及怎么用程序把四方密码"暴力"破解出来。
本文所有方阵、密文均由文中的 Python 代码实际运行验证过,可放心对照手算。