每日算法 — 使用java实现凯撒密码:移位加密与频率分析破解

凯撒密码(Caesar Cipher)是古典密码学中最古老的加密算法之一,相传由古罗马的凯撒大帝用于军事通信。虽然其安全性在现代密码学面前不值一提,但它是理解加密算法基础原理的绝佳起点。本文将用Java完整实现凯撒密码的加密、解密,以及基于频率分析的自动化破解。

密码学基础

凯撒密码属于替换密码(Substitution Cipher),其核心思想是将明文中的每个字母按照字母表顺序向后(或向前)移动固定的位数。例如,当移位量为3时:

  • A → D, B → E, C → F, …, X → A, Y → B, Z → C

加密与解密算法

算法步骤

加密:对于每个字母字符,计算其在字母表中的位置,加上密钥k后取模26,得到密文字母。
解密:将密文字母位置减去密钥k后取模26,还原明文字母。

Java实现

/**
 * 凯撒密码实现
 * 支持大小写字母,保留非字母字符
 */
public class CaesarCipher {

    private static final int ALPHABET_SIZE = 26;

    /**
     * 加密方法
     * @param plaintext 明文
     * @param shift 移位量(密钥)
     * @return 密文
     */
    public static String encrypt(String plaintext, int shift) {
        StringBuilder ciphertext = new StringBuilder();
        shift = ((shift % ALPHABET_SIZE) + ALPHABET_SIZE) % ALPHABET_SIZE;

        for (char c : plaintext.toCharArray()) {
            if (Character.isUpperCase(c)) {
                // 大写字母: 'A'=65, 映射到0-25,移位后再映射回来
                char encrypted = (char) (((c - 'A' + shift) % ALPHABET_SIZE) + 'A');
                ciphertext.append(encrypted);
            } else if (Character.isLowerCase(c)) {
                // 小写字母: 'a'=97
                char encrypted = (char) (((c - 'a' + shift) % ALPHABET_SIZE) + 'a');
                ciphertext.append(encrypted);
            } else {
                // 非字母字符保持不变
                ciphertext.append(c);
            }
        }
        return ciphertext.toString();
    }

    /**
     * 解密方法
     * @param ciphertext 密文
     * @param shift 移位量(密钥)
     * @return 明文
     */
    public static String decrypt(String ciphertext, int shift) {
        // 解密即加密的逆运算,相当于用负密钥加密
        return encrypt(ciphertext, ALPHABET_SIZE - (shift % ALPHABET_SIZE));
    }

    /**
     * 暴力破解:尝试所有25种可能的密钥
     * @param ciphertext 密文
     */
    public static void bruteForceDecrypt(String ciphertext) {
        System.out.println("=== 暴力破解所有可能 ===");
        for (int shift = 1; shift < ALPHABET_SIZE; shift++) {
            String decrypted = decrypt(ciphertext, shift);
            System.out.printf("密钥 %2d: %s%n", shift, decrypted);
        }
    }

    public static void main(String[] args) {
        String message = "Hello, World! This is a Caesar Cipher test.";
        int key = 7;

        System.out.println("原始明文: " + message);

        String encrypted = encrypt(message, key);
        System.out.println("加密后(密钥=" + key + "): " + encrypted);

        String decrypted = decrypt(encrypted, key);
        System.out.println("解密后: " + decrypted);

        System.out.println();
        bruteForceDecrypt(encrypted);
    }
}

频率分析破解

对于较长的密文,暴力破解虽然可行,但需要人工判断哪条结果是正确的明文。频率分析(Frequency Analysis)则可以通过统计字母出现频率来自动化破解。

原理

在任何一种自然语言中,各字母的出现频率都有相对稳定的分布。例如,在英语中:
– E是最常见的字母(约12.7%)
– T、A、O、I、N、S、H、R 也很常见
– Q、Z、X、J 则很少出现

对于凯撒密码,密文的频率分布只是明文的频率分布整体平移了。因此,我们可以通过找到密文中最常见的字母,推测它对应明文中的E,从而计算出密钥。

Java实现

import java.util.*;

/**
 * 基于频率分析的凯撒密码自动破解
 */
public class CaesarFrequencyAnalysis {

    // 英语字母标准频率分布(来自大规模语料统计)
    private 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
    };

    private static final int ALPHABET_SIZE = 26;

    /**
     * 统计密文中各字母的出现频率
     */
    public static double[] calculateFrequency(String text) {
        double[] freq = new double[ALPHABET_SIZE];
        int letterCount = 0;

        for (char c : text.toUpperCase().toCharArray()) {
            if (c >= 'A' && c <= 'Z') {
                freq[c - 'A']++;
                letterCount++;
            }
        }

        if (letterCount > 0) {
            for (int i = 0; i < ALPHABET_SIZE; i++) {
                freq[i] = (freq[i] / letterCount) * 100;
            }
        }
        return freq;
    }

    /**
     * 计算两个频率分布之间的卡方统计量
     * 值越小表示越相似
     */
    public static double chiSquareStatistic(double[] observed, double[] expected) {
        double chiSquare = 0;
        for (int i = 0; i < ALPHABET_SIZE; i++) {
            if (expected[i] > 0) {
                double diff = observed[i] - expected[i];
                chiSquare += (diff * diff) / expected[i];
            }
        }
        return chiSquare;
    }

    /**
     * 尝试用指定密钥解密,并返回解密后的频率分布
     */
    public static double[] decryptFrequency(double[] cipherFreq, int shift) {
        double[] plainFreq = new double[ALPHABET_SIZE];
        for (int i = 0; i < ALPHABET_SIZE; i++) {
            int originalIndex = (i + shift) % ALPHABET_SIZE;
            plainFreq[i] = cipherFreq[originalIndex];
        }
        return plainFreq;
    }

    /**
     * 频率分析自动破解
     * @param ciphertext 密文
     * @return 最可能的密钥
     */
    public static int crackCipher(String ciphertext) {
        double[] cipherFreq = calculateFrequency(ciphertext);
        int bestShift = 0;
        double minChiSquare = Double.MAX_VALUE;

        System.out.println("=== 频率分析破解过程 ===");
        for (int shift = 0; shift < ALPHABET_SIZE; shift++) {
            double[] candidateFreq = decryptFrequency(cipherFreq, shift);
            double chiSquare = chiSquareStatistic(candidateFreq, ENGLISH_FREQ);

            System.out.printf("密钥 %2d: 卡方值 = %.4f%n", shift, chiSquare);

            if (chiSquare < minChiSquare) {
                minChiSquare = chiSquare;
                bestShift = shift;
            }
        }

        return bestShift;
    }

    /**
     * 凯撒密码解密(复用前面的实现)
     */
    public static String decrypt(String ciphertext, int shift) {
        StringBuilder plaintext = new StringBuilder();
        shift = ((shift % ALPHABET_SIZE) + ALPHABET_SIZE) % ALPHABET_SIZE;

        for (char c : ciphertext.toCharArray()) {
            if (Character.isUpperCase(c)) {
                char decrypted = (char) (((c - 'A' + ALPHABET_SIZE - shift) % ALPHABET_SIZE) + 'A');
                plaintext.append(decrypted);
            } else if (Character.isLowerCase(c)) {
                char decrypted = (char) (((c - 'a' + ALPHABET_SIZE - shift) % ALPHABET_SIZE) + 'a');
                plaintext.append(decrypted);
            } else {
                plaintext.append(c);
            }
        }
        return plaintext.toString();
    }

    public static void main(String[] args) {
        // 一段较长的英文文本用于测试
        String originalText = "The quick brown fox jumps over the lazy dog. " +
            "Cryptography is the practice and study of techniques for secure communication " +
            "in the presence of adversarial behavior. " +
            "More generally cryptography is about constructing and analyzing protocols " +
            "that prevent third parties or the public from reading private messages.";

        int secretKey = 15;
        String ciphertext = CaesarCipher.encrypt(originalText, secretKey);

        System.out.println("原始明文长度: " + originalText.length());
        System.out.println("加密密钥: " + secretKey);
        System.out.println("密文: " + ciphertext.substring(0, 80) + "...\n");

        // 使用频率分析破解
        int crackedKey = crackCipher(ciphertext);
        System.out.println("\n破解得到的密钥: " + crackedKey);

        String decrypted = decrypt(ciphertext, crackedKey);
        System.out.println("\n解密结果: " + decrypted.substring(0, 100) + "...");
        System.out.println("\n破解" + (crackedKey == secretKey ? "成功!" : "失败!"));
    }
}

安全性分析

凯撒密码的密钥空间仅有25个(排除移位0),现代计算机可以在瞬间完成暴力破解。频率分析对于长度超过几十个字符的密文几乎总是有效。

主要弱点:
1. 密钥空间过小:仅25种可能
2. 保留了明文的语言统计特征
3. 单字母替换,相同明文字母总是映射到相同密文字母

从凯撒密码到现代密码学

凯撒密码的演进方向:
1. 多表替换密码(如维吉尼亚密码):使用多个移位量循环替换
2. 置换密码:打乱字母位置而非替换字母
3. 对称加密(如AES):使用复杂的多轮替换和置换
4. 非对称加密(如RSA):使用数学难题保证安全性

理解凯撒密码的弱点——即统计信息的泄露——是理解现代密码学设计原则的起点。好的加密算法必须使密文看起来是随机的,不泄露任何关于明文的信息(香农的完美保密概念)。

复杂度分析

  • 加密/解密:时间复杂度O(n),空间复杂度O(n),其中n为文本长度
  • 暴力破解:时间复杂度O(26×n),检查所有25个密钥
  • 频率分析:时间复杂度O(n + 26²),统计频率加上比较26种候选

扩展练习

  1. 实现维吉尼亚密码:使用一个关键词作为多字母密钥
  2. 实现ROT13:凯撒密码的特例(移位13),它是自身的逆运算
  3. 实现基于双字母组(digram)频率分析的破解,提高短密文的破解率
  4. 用JavaFX或Swing实现一个可视化的密码破解工具

总结

凯撒密码虽简单,却蕴含了密码学的核心概念:替换、密钥空间、频率分析。通过Java实现加密、解密和自动破解,读者可以深入理解为什么现代加密算法需要巨大的密钥空间和复杂的混淆扩散机制。对于信息安全学习者而言,这是一个不可多得的入门案例。