1. 概述
本文将深入探讨凯撒密码(Caesar Cipher)——一种经典的字母位移加密技术。它的核心思想是通过将明文中的每个字母按固定偏移量进行替换,生成难以直接阅读的密文。
我们将从头实现一个完整的 Java 版凯撒密码工具类,包含:
- ✅ 加密(cipher):给定明文和偏移量,生成密文
- ✅ 解密(decipher):已知偏移量,还原原始消息
- ✅ 破解(break cipher):无需知道偏移量,通过统计分析自动推断出最可能的原始内容
整个过程简单粗暴但极具教学意义,适合想了解古典密码学基础的开发者参考。
2. 凯撒密码原理与实现
2.1 基本原理
凯撒密码属于替换式加密(substitution cipher),其规则非常直观:将字母表中的每个字母向前或向后移动固定位置。
例如偏移量为 3 时,映射关系如下:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
关键细节:
- ⚠️ 字母是循环的:
Z
后面回到A
- 偏移量超过 26 时等效于
offset % 26
- 实际处理中我们只关注
a-z
和空格,忽略大小写差异(示例中统一转为小写)
2.2 Java 加密实现
我们创建一个 CaesarCipher
类,并实现 cipher()
方法:
public class CaesarCipher {
String cipher(String message, int offset) {
StringBuilder result = new StringBuilder();
for (char character : message.toCharArray()) {
if (character != ' ') {
int originalAlphabetPosition = character - 'a';
int newAlphabetPosition = (originalAlphabetPosition + offset) % 26;
char newCharacter = (char) ('a' + newAlphabetPosition);
result.append(newCharacter);
} else {
result.append(character);
}
}
return result.toString();
}
}
📌 核心技巧:
- 利用 ASCII 码计算字母在字母表中的相对位置(
c - 'a'
) - 使用
% 26
自动处理循环问题,避免手动判断越界 - 空格保持不变,增强可读性
测试验证
CaesarCipher cipher = new CaesarCipher();
// 偏移量为 3
String ciphered = cipher.cipher("he told me i could never teach a llama to drive", 3);
assertThat(ciphered).isEqualTo("kh wrog ph l frxog qhyhu whdfk d oodpd wr gulyh");
// 偏移量为 10(部分字母会绕回开头)
ciphered = cipher.cipher("he told me i could never teach a llama to drive", 10);
assertThat(ciphered).isEqualTo("ro dyvn wo s myevn xofob dokmr k vvkwk dy nbsfo");
结果完全符合预期 ✅
3. 解密实现
3.1 解密逻辑
已知偏移量的情况下,解密其实就是“反向加密”:
- ❌ 直接使用负偏移量?Java 实现中
%
运算对负数处理不友好,容易踩坑 - ✅ 更优雅的方式:使用互补偏移量(complementary offset)
👉 公式:
若加密偏移为 n
,则解密偏移为 (26 - n % 26) % 26
这样始终保证偏移量为正,复用已有加密逻辑即可。
3.2 Java 解密代码
直接调用 cipher()
方法,传入互补偏移:
String decipher(String message, int offset) {
return cipher(message, 26 - (offset % 26));
}
测试验证
String decrypted = cipher.decipher("ro dyvn wo s myevn xofob dokmr k vvkwk dy nbsfo", 36);
assertThat(decrypted).isEqualTo("he told me i could never teach a llama to drive");
🔍 偏移量 36 ≡ 10 (mod 26),所以解密时用
26 - 10 = 16
再加密一次即还原原文。
4. 如何破解凯撒密码
4.1 破解思路
现实场景中,我们往往不知道加密偏移量。此时可通过频率分析法暴力破解:
- 尝试所有可能的偏移(0~25)
- 对每种解密结果统计字母出现频率
- 使用 卡方检验(Chi-squared statistic) 对比该频率与标准英文文本的相似度
- 卡方值最小的那个偏移,极大概率就是正确答案
📌 注意:该方法依赖统计规律,短文本或非典型内容可能导致误判
4.2 英文字母基准频率
我们使用标准英文语料库中各字母的出现概率作为基准:
double[] englishLettersProbabilities = {
0.073, 0.009, 0.030, 0.044, 0.130, 0.028, 0.016, 0.035, 0.074, // a-i
0.002, 0.003, 0.035, 0.025, 0.078, 0.074, 0.027, 0.003, // j-q
0.077, 0.063, 0.093, 0.027, 0.013, 0.016, 0.005, 0.019, 0.001 // r-z
};
根据消息长度计算期望频次:
double[] expectedLettersFrequencies = Arrays.stream(englishLettersProbabilities)
.map(probability -> probability * message.length())
.toArray();
例如长度为 100 的文本,期望有约 7.3 个 'a',13 个 'e'
4.3 引入 Apache Commons Math3
为了计算卡方值,引入以下依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
4.4 计算卡方值
遍历所有偏移量,计算每种解密结果的卡方值:
int breakCipher(String message) {
double[] chiSquares = new double[26];
double[] expectedLettersFrequencies = ... // 如上
for (int offset = 0; offset < 26; offset++) {
String decipheredMessage = decipher(message, offset);
long[] observedFrequencies = observedLettersFrequencies(decipheredMessage);
double chiSquare = new ChiSquareTest().chiSquare(expectedLettersFrequencies, observedFrequencies);
chiSquares[offset] = chiSquare;
}
// 返回卡方值最小的偏移
int probableOffset = 0;
for (int offset = 0; offset < 26; offset++) {
if (chiSquares[offset] < chiSquares[probableOffset]) {
probableOffset = offset;
}
}
return probableOffset;
}
辅助方法:统计实际字母频次
long[] observedLettersFrequencies(String message) {
return IntStream.rangeClosed('a', 'z')
.mapToLong(letter -> countLetter((char) letter, message))
.toArray();
}
long countLetter(char letter, String message) {
return message.chars()
.filter(character -> character == letter)
.count();
}
4.5 破解效果验证
测试密文(偏移量为 10 加密):
String encrypted = "ro dyvn wo s myevn xofob dokmr k vvkwk dy nbsfo";
int offset = cipher.breakCipher(encrypted);
assertThat(offset).isEqualTo(10);
String decrypted = cipher.decipher(encrypted, offset);
assertThat(decrypted).isEqualTo("he told me i could never teach a llama to drive");
输出的卡方值如下,可见 offset=10 时显著最低:
Chi-Square for offset 0: 210.69
Chi-Square for offset 1: 327.65
Chi-Square for offset 2: 255.22
Chi-Square for offset 3: 187.12
Chi-Square for offset 4: 734.16
Chi-Square for offset 5: 673.68
Chi-Square for offset 6: 223.35
Chi-Square for offset 7: 111.13
Chi-Square for offset 8: 270.11
Chi-Square for offset 9: 153.26
Chi-Square for offset 10: 23.74 ← 最小,正确!
Chi-Square for offset 11: 643.14
...
5. 总结
本文完整实现了凯撒密码的加密、解密与自动化破解:
- ✅ 加密:利用
% 26
简洁处理字母循环 - ✅ 解密:通过互补偏移量复用加密逻辑
- ✅ 破解:结合 Apache Commons Math3 的卡方检验,基于字母频率自动推断密钥
虽然凯撒密码在现代安全体系中早已过时,但它作为密码学入门的经典案例,依然具有极高的学习价值。理解其原理有助于我们更好地认识更复杂的加密算法。
完整代码示例已托管至 GitHub:
👉 https://github.com/eugenp/tutorials/tree/master/algorithms-modules/algorithms-miscellaneous-6