二、古典密码学:凯撒密码、维吉尼亚密码、仿射密码的原理与破解方法
古典密码学,说白了就是现代密码学的老祖宗。虽然今天没人再用它们保护银行数据,但理解这些基础算法,能帮你搞懂加密的本质——替换和换位。我当年刚入行时,就是从破解一个凯撒密码开始对密码学产生兴趣的。嗯,咱们一个一个来看。
2.1 凯撒密码:最简单的替换
凯撒密码的原理极其简单:把字母表平移固定的位数。比如移3位,A变成D,B变成E,以此类推。我记得古罗马时期凯撒就用这种方式传递军令,当时确实够用。
核心公式:
- 加密:C = (P + K) mod 26
- 解密:P = (C - K) mod 26
其中P是明文数字(A=0, B=1, ..., Z=25),C是密文数字,K是密钥(1~25)。
举个例子,明文"HELLO",密钥K=3:
- H(7) → K(10)
- E(4) → H(7)
- L(11) → O(14)
- L(11) → O(14)
- O(14) → R(17)
结果就是"KHOOR"。是不是很简单?
破解方法:暴力穷举
凯撒密码的密钥空间只有25种可能。我经常跟新人说,你手动试25次就能破解,写个脚本更是秒出结果。为什么会这样?因为密钥太少,根本扛不住现代计算能力。
# Python 暴力破解凯撒密码
def caesar_break(ciphertext):
for key in range(1, 26):
plain = ''
for c in ciphertext.upper():
if c.isalpha():
plain += chr((ord(c) - 65 - key) % 26 + 65)
else:
plain += c
print(f"Key {key:2d}: {plain}")
caesar_break("KHOOR")
输出结果里,只有Key=3那行是"HELLO",其他都是乱码。一眼就能看出来。
个人经验:我在做CTF比赛时,经常遇到凯撒密码的变种——只加密字母、保留标点。写脚本时记得过滤非字母字符,否则解密结果会带一堆奇怪符号。
2.2 维吉尼亚密码:多表替换的升级
凯撒密码太脆弱,于是有人发明了维吉尼亚密码。它用关键词决定每个字母的偏移量,说白了就是多个凯撒密码轮着用。你想想看,如果密钥是"KEY",明文第一个字母用K(10)偏移,第二个用E(4)偏移,第三个用Y(24)偏移,然后重复。
核心公式:
- 加密:Ci = (Pi + Ki mod len(K)) mod 26
- 解密:Pi = (Ci - Ki mod len(K)) mod 26
举个例子,明文"ATTACK",密钥"KEY":
- A(0) + K(10) = K(10)
- T(19) + E(4) = X(23)
- T(19) + Y(24) = R(17)
- A(0) + K(10) = K(10)
- C(2) + E(4) = G(6)
- K(10) + Y(24) = I(8)
密文是"KXRKGI"。你看,同样的字母A,第一次加密成K,第二次还是K,但T加密成了X和R——这就是多表替换的效果。
破解方法:卡西斯基测试 + 频率分析
维吉尼亚密码比凯撒难破,但也不是铁板一块。破解分两步:
- 找密钥长度:用卡西斯基测试,找密文中重复出现的片段,计算间隔的最大公约数。
- 逐列频率分析:按密钥长度分组,每组就是一个凯撒密码,用频率分析破解。
我曾经踩过的坑:有一次破解维吉尼亚密码,卡西斯基测试给出的密钥长度是5,但实际是10。后来发现是因为明文本身有重复模式,干扰了测试结果。建议多试几个候选长度,别只信第一个。
# 卡西斯基测试简化版
def kasiski_examination(ciphertext, min_len=3):
from collections import defaultdict
repeats = defaultdict(list)
for i in range(len(ciphertext) - min_len + 1):
seq = ciphertext[i:i+min_len]
repeats[seq].append(i)
distances = []
for seq, positions in repeats.items():
if len(positions) > 1:
for j in range(1, len(positions)):
distances.append(positions[j] - positions[j-1])
# 找距离的最大公约数
from math import gcd
if distances:
g = distances[0]
for d in distances[1:]:
g = gcd(g, d)
return g
return None
2.3 仿射密码:数学化的替换
仿射密码是凯撒密码的数学升级版。它用线性变换来加密:C = (a × P + b) mod 26。这里a和b都是密钥,但a必须和26互质,否则解密时找不到逆元。
核心公式:
- 加密:C = (a × P + b) mod 26
- 解密:P = a-1 × (C - b) mod 26
其中a-1是a模26的乘法逆元,满足a × a-1 ≡ 1 mod 26。
举个例子,a=5, b=8,加密"HI":
- H(7) → (5×7+8) mod 26 = 43 mod 26 = 17 → R
- I(8) → (5×8+8) mod 26 = 48 mod 26 = 22 → W
密文是"RW"。解密时先求5的逆元,5×21=105≡1 mod 26,所以a-1=21。然后P=21×(C-8) mod 26,就能还原回"HI"。
破解方法:已知明文攻击 + 穷举
仿射密码的密钥空间是12×26=312种(a有12种选择,b有26种)。虽然比凯撒大,但依然脆弱。如果有已知明文对,可以直接解方程求a和b。
我建议:在实际项目中,仿射密码常用于简单的数据混淆,比如隐藏URL参数。但千万别用于真正的加密——312种密钥,现代计算机一秒能试几百万次。
# 仿射密码破解(已知明文攻击)
def affine_break_known(plaintext, ciphertext):
# 假设已知至少两个字符的对应关系
p0 = ord(plaintext[0].upper()) - 65
c0 = ord(ciphertext[0].upper()) - 65
p1 = ord(plaintext[1].upper()) - 65
c1 = ord(ciphertext[1].upper()) - 65
# 解方程组:c0 = (a*p0 + b) mod 26, c1 = (a*p1 + b) mod 26
# 相减得:c1-c0 = a*(p1-p0) mod 26
diff_c = (c1 - c0) % 26
diff_p = (p1 - p0) % 26
# 求a = diff_c * diff_p^-1 mod 26
for a in range(1, 26):
if (a * diff_p) % 26 == diff_c:
b = (c0 - a * p0) % 26
return a, b
return None, None
2.4 三种密码对比
| 特性 | 凯撒密码 | 维吉尼亚密码 | 仿射密码 |
|---|---|---|---|
| 密钥类型 | 单整数(1~25) | 关键词(字符串) | 两个整数(a, b) |
| 密钥空间 | 25 | 26n (n为密钥长度) | 312 |
| 替换方式 | 单表替换 | 多表替换 | 单表替换(线性) |
| 破解难度 | 极低 | 中等 | 低 |
| 典型破解方法 | 暴力穷举 | 卡西斯基+频率分析 | 已知明文攻击 |
2.5 知识体系图
下面这张图展示了本章的核心逻辑:从凯撒到维吉尼亚再到仿射,破解方法也逐步升级。
2.6 避坑指南
我曾经犯过的错:
- 用凯撒密码加密用户密码——结果被同事一眼看穿,当场社死。
- 破解维吉尼亚密码时,忘了考虑密钥长度可能不是卡西斯基测试的直接结果,浪费了半天时间。
- 仿射密码的a值选了偶数,导致解密失败——后来才想起a必须和26互质。
嗯,古典密码学就讲到这里。记住一句话:任何依赖算法保密性的加密都是不安全的。凯撒、维吉尼亚、仿射,它们的共同弱点就是密钥空间太小,而且密文保留了明文的统计特征。现代密码学怎么解决这些问题?咱们后面章节慢慢聊。