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 破解思路

现实场景中,我们往往不知道加密偏移量。此时可通过频率分析法暴力破解:

  1. 尝试所有可能的偏移(0~25)
  2. 对每种解密结果统计字母出现频率
  3. 使用 卡方检验(Chi-squared statistic) 对比该频率与标准英文文本的相似度
  4. 卡方值最小的那个偏移,极大概率就是正确答案

📌 注意:该方法依赖统计规律,短文本或非典型内容可能导致误判

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


原始标题:The Caesar Cipher in Java