引言
凯撒密码(Caesar Cipher)是古罗马时期 Julius Caesar 用于军事通信的加密方法,其原理简单却影响深远——将明文中的每个字母按固定偏移量向后推移。尽管在现代密码学中它已被完全破解,但作为入门级的加密算法,凯撒密码完美展示了替换密码的核心思想,也是理解现代加密体系的绝佳起点。本文将用Java实现凯撒密码的完整系统,不仅包含基础的移位加密,还将扩展到仿射加密,并实现基于卡方统计学的自动破解算法。
凯撒密码原理
基础移位加密
设偏移量为 k(1 ≤ k ≤ 25),加密公式为:
C = (P + k) mod 26
P = (C - k) mod 26
其中 P 为明文字母对应的数字(A=0, B=1, …, Z=25),C 为密文。
public class CaesarCipher {
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
* 加密:将每个字母按偏移量k后移
*/
public static String encrypt(String plaintext, int shift) {
StringBuilder sb = new StringBuilder();
shift = ((shift % 26) + 26) % 26; // 规范化到0-25
for (char c : plaintext.toUpperCase().toCharArray()) {
if (Character.isLetter(c)) {
int idx = ALPHABET.indexOf(c);
int newIdx = (idx + shift) % 26;
sb.append(ALPHABET.charAt(newIdx));
} else {
sb.append(c); // 非字母字符保持不变
}
}
return sb.toString();
}
/**
* 解密:将每个字母按偏移量k前移
*/
public static String decrypt(String ciphertext, int shift) {
return encrypt(ciphertext, 26 - shift); // 解密即反方向移位
}
public static void main(String[] args) {
String text = "HELLO WORLD";
int key = 3;
String encrypted = encrypt(text, key);
String decrypted = decrypt(encrypted, key);
System.out.println("明文: " + text);
System.out.println("密钥: " + key);
System.out.println("密文: " + encrypted);
System.out.println("解密: " + decrypted);
}
}
仿射加密扩展
仿射加密是凯撒密码的广义形式,引入乘数密钥 a(需与26互质):
C = (a × P + b) mod 26
P = a⁻¹ × (C - b) mod 26
其中 a⁻¹ 是 a 在模26下的乘法逆元。
public class AffineCipher {
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
* 扩展欧几里得算法求乘法逆元
*/
static int modInverse(int a, int m) {
int m0 = m, t, q;
int x0 = 0, x1 = 1;
if (m == 1) return 0;
while (a > 1) {
q = a / m;
t = m;
m = a % m;
a = t;
t = x0;
x0 = x1 - q * x0;
x1 = t;
}
if (x1 < 0) x1 += m0;
return x1;
}
/**
* 判断a是否与26互质
*/
static boolean isValidA(int a) {
return gcd(a, 26) == 1;
}
static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public static String encrypt(String plaintext, int a, int b) {
if (!isValidA(a)) throw new IllegalArgumentException("a必须与26互质");
StringBuilder sb = new StringBuilder();
for (char c : plaintext.toUpperCase().toCharArray()) {
if (Character.isLetter(c)) {
int p = ALPHABET.indexOf(c);
int cipher = (a * p + b) % 26;
sb.append(ALPHABET.charAt(cipher));
} else {
sb.append(c);
}
}
return sb.toString();
}
public static String decrypt(String ciphertext, int a, int b) {
if (!isValidA(a)) throw new IllegalArgumentException("a必须与26互质");
int aInv = modInverse(a, 26);
StringBuilder sb = new StringBuilder();
for (char c : ciphertext.toUpperCase().toCharArray()) {
if (Character.isLetter(c)) {
int cipher = ALPHABET.indexOf(c);
int plain = ((cipher - b) * aInv) % 26;
if (plain < 0) plain += 26;
sb.append(ALPHABET.charAt(plain));
} else {
sb.append(c);
}
}
return sb.toString();
}
}
基于频率分析的自动破解
凯撒密码的密钥空间仅有25种可能,暴力枚举即可破解。但对于稍长的密文,基于字母频率统计的方法更为优雅高效。
英语字母频率表
根据大规模英文文本统计,各字母出现频率如下:
static final double[] ENGLISH_FREQ = {
8.167, 1.492, 2.782, 4.253, 12.702, // A-E
2.228, 2.015, 6.094, 6.966, 0.153, // F-J
0.772, 4.025, 2.406, 6.749, 7.507, // K-O
1.929, 0.095, 5.987, 6.327, 9.056, // P-T
2.758, 0.978, 2.360, 0.150, 1.974, // U-Y
0.074 // Z
};
卡方统计检验
卡方检验衡量观察频率与期望频率的偏离程度:
χ² = Σ (观察值 - 期望值)² / 期望值
public class CaesarCracker {
private static final double[] ENGLISH_FREQ = {
8.167, 1.492, 2.782, 4.253, 12.702, 2.228, 2.015, 6.094, 6.966, 0.153,
0.772, 4.025, 2.406, 6.749, 7.507, 1.929, 0.095, 5.987, 6.327, 9.056,
2.758, 0.978, 2.360, 0.150, 1.974, 0.074
};
/**
* 计算文本中各字母的频率
*/
static double[] calculateFreq(String text) {
double[] freq = new double[26];
int total = 0;
for (char c : text.toUpperCase().toCharArray()) {
if (Character.isLetter(c)) {
freq[c - 'A']++;
total++;
}
}
if (total == 0) return freq;
for (int i = 0; i < 26; i++) freq[i] = freq[i] * 100.0 / total;
return freq;
}
/**
* 计算卡方统计量(越小表示越接近英文分布)
*/
static double chiSquare(double[] observed) {
double chi = 0;
for (int i = 0; i < 26; i++) {
double expected = ENGLISH_FREQ[i];
double diff = observed[i] - expected;
chi += (diff * diff) / expected;
}
return chi;
}
/**
* 自动破解凯撒密码:尝试所有25种偏移,选择卡方值最小的
*/
public static int crack(String ciphertext) {
double minChi = Double.MAX_VALUE;
int bestShift = 0;
for (int shift = 1; shift <= 25; shift++) {
String decrypted = CaesarCipher.decrypt(ciphertext, shift);
double[] freq = calculateFreq(decrypted);
double chi = chiSquare(freq);
if (chi < minChi) {
minChi = chi;
bestShift = shift;
}
}
return bestShift;
}
/**
* 带置信度评估的破解
*/
public static CrackResult crackWithConfidence(String ciphertext) {
int shift = crack(ciphertext);
String plaintext = CaesarCipher.decrypt(ciphertext, shift);
double[] freq = calculateFreq(plaintext);
double chi = chiSquare(freq);
// 计算置信度:与第二优解的差距
double secondMinChi = Double.MAX_VALUE;
for (int s = 1; s <= 25; s++) {
if (s == shift) continue;
double c = chiSquare(calculateFreq(CaesarCipher.decrypt(ciphertext, s)));
if (c < secondMinChi) secondMinChi = c;
}
double confidence = secondMinChi / chi;
return new CrackResult(shift, plaintext, chi, confidence);
}
static class CrackResult {
int shift;
String plaintext;
double chiSquare;
double confidence;
CrackResult(int s, String p, double c, double conf) {
shift = s; plaintext = p; chiSquare = c; confidence = conf;
}
@Override
public String toString() {
return String.format("密钥=%d, 置信度=%.2f, 明文=%s", shift, confidence, plaintext);
}
}
}
完整的密码系统测试
public class CryptoDemo {
public static void main(String[] args) {
String original = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG";
System.out.println("=== 原始明文 ===");
System.out.println(original);
System.out.println();
// 凯撒加密
int caesarKey = 7;
String caesarCipher = CaesarCipher.encrypt(original, caesarKey);
System.out.println("=== 凯撒加密 (密钥=" + caesarKey + ") ===");
System.out.println("密文: " + caesarCipher);
// 自动破解
int crackedKey = CaesarCracker.crack(caesarCipher);
System.out.println("自动破解密钥: " + crackedKey);
System.out.println("破解明文: " + CaesarCipher.decrypt(caesarCipher, crackedKey));
System.out.println();
// 仿射加密
int a = 5, b = 8; // a必须与26互质
String affineCipher = AffineCipher.encrypt(original, a, b);
System.out.println("=== 仿射加密 (a=" + a + ", b=" + b + ") ===");
System.out.println("密文: " + affineCipher);
System.out.println("解密: " + AffineCipher.decrypt(affineCipher, a, b));
System.out.println();
// 暴力破解仿射密码
System.out.println("=== 仿射密码暴力破解 ===");
crackAffineBruteForce(affineCipher);
}
static void crackAffineBruteForce(String cipher) {
int[] validA = {1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25}; // 与26互质的数
double minChi = Double.MAX_VALUE;
String bestPlain = "";
int bestA = 0, bestB = 0;
for (int a : validA) {
for (int b = 0; b < 26; b++) {
String plain = AffineCipher.decrypt(cipher, a, b);
double chi = CaesarCracker.chiSquare(CaesarCracker.calculateFreq(plain));
if (chi < minChi) {
minChi = chi;
bestPlain = plain;
bestA = a; bestB = b;
}
}
}
System.out.printf("最优密钥: a=%d, b=%d%n", bestA, bestB);
System.out.println("破解明文: " + bestPlain);
}
}
安全性分析
| 攻击方式 | 凯撒密码 | 仿射密码 |
|---|---|---|
| 密钥空间 | 25 | 12 × 26 = 312 |
| 暴力破解 | 瞬间完成 | 瞬间完成 |
| 频率分析 | 极易破解 | 极易破解 |
| 已知明文攻击 | 1个字母即可确定密钥 | 2个字母即可解方程组 |
凯撒密码和仿射密码都属于古典密码,其致命弱点在于:单字母替换保持了明文的统计特征。现代密码学通过以下方式解决这一问题:
– 多表替换(如Vigenère密码):不同位置使用不同替换表
– 扩散与混淆(如AES):让每个密文字母依赖于多个明文字母
小结
本文通过Java实现了完整的凯撒密码系统:
– 基础移位加密与解密算法
– 仿射加密的扩展实现(引入乘数密钥与模逆元)
– 基于卡方统计学的自动频率分析破解
– 扩展欧几里得算法求模逆元的数学实现
尽管凯撒密码在现代已无实用安全性,但它承载的历史意义和教学价值不可忽视。从简单的位移运算到复杂的模逆元计算,从直观的频率分析到严谨的卡方检验,这一古老密码为我们打开了密码学世界的大门。