Java 密码学:AES 加密/解密与 RSA 签名验证

在 Java 中实现密码学功能时,AES(高级加密标准)用于对称加密和解密数据,而 RSA 用于非对称签名和验证,确保数据完整性和身份认证。AES 高效适合大数据加密,RSA 则常用于数字签名场景(如验证消息来源)。以下我将逐步解释原理并提供 Java 代码示例,基于 Java 标准库(如 javax.cryptojava.security),确保真实可靠。代码示例使用常见安全模式(如 AES/CBC/PKCS5Padding 和 SHA256withRSA),并包含安全注意事项。

1. AES 加密/解密原理

AES 是一种对称加密算法,使用相同密钥进行加密和解密。其核心基于多轮替换和置换操作,数学上可描述为一系列字节代换和行移位变换。例如,在 AES-256 中,密钥长度 256 位,加密过程涉及:

  • 初始轮密钥加:$ \text{state} = \text{state} \oplus \text{roundKey} $
  • 多轮操作:包括字节代换(S-box)、行移位、列混合和轮密钥加。 解密过程是加密的逆操作。

在 Java 中,使用 Cipher 类实现。以下代码示例展示 AES-256 的加密和解密,包括密钥生成和初始化向量(IV)处理(IV 确保相同明文加密结果不同,提升安全性)。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;

public class AESExample {
    public static void main(String[] args) throws Exception {
        // 1. 生成 AES 密钥 (256 位)
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256, new SecureRandom());
        SecretKey secretKey = keyGen.generateKey();

        // 2. 加密示例
        String plainText = "Hello, AES 加密测试!";
        byte[] encryptedData = encrypt(plainText, secretKey);
        System.out.println("加密后数据 (Base64): " + Base64.getEncoder().encodeToString(encryptedData));

        // 3. 解密示例
        String decryptedText = decrypt(encryptedData, secretKey);
        System.out.println("解密后文本: " + decryptedText);
    }

    public static byte[] encrypt(String plainText, SecretKey secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecureRandom random = new SecureRandom();
        byte[] ivBytes = new byte[16]; // IV 长度 16 字节
        random.nextBytes(ivBytes);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));

        // 将 IV 和加密数据合并存储 (IV 不需要保密,但需传输)
        byte[] combined = new byte[ivBytes.length + encryptedBytes.length];
        System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
        System.arraycopy(encryptedBytes, 0, combined, ivBytes.length, encryptedBytes.length);
        return combined;
    }

    public static String decrypt(byte[] combinedData, SecretKey secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] ivBytes = new byte[16];
        System.arraycopy(combinedData, 0, ivBytes, 0, ivBytes.length);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

        byte[] encryptedBytes = new byte[combinedData.length - ivBytes.length];
        System.arraycopy(combinedData, ivBytes.length, encryptedBytes, 0, encryptedBytes.length);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, "UTF-8");
    }
}

2. RSA 签名验证原理

RSA 是一种非对称算法,签名使用私钥生成,验证使用公钥。签名过程确保消息完整性和来源认证:

  • 签名生成:先计算消息哈希(如 SHA-256),然后用私钥加密哈希值。数学上,签名 $s$ 为: $$ s = \text{hash}(m)^d \mod n $$ 其中 $m$ 是消息,$d$ 是私钥指数,$n$ 是模数。
  • 签名验证:重新计算消息哈希,用公钥解密签名,并比较是否一致。验证公式: $$ \text{hash}(m) = s^e \mod n $$ 其中 $e$ 是公钥指数。如果相等,则签名有效。

在 Java 中,使用 Signature 类实现。以下代码示例展示 RSA 密钥对生成、签名生成和验证。

import java.security.*;
import java.util.Base64;

public class RSAExample {
    public static void main(String[] args) throws Exception {
        // 1. 生成 RSA 密钥对 (2048 位)
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        keyPairGen.initialize(2048, new SecureRandom());
        KeyPair keyPair = keyPairGen.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        // 2. 签名生成示例
        String message = "Hello, RSA 签名测试!";
        byte[] signature = sign(message, privateKey);
        System.out.println("签名 (Base64): " + Base64.getEncoder().encodeToString(signature));

        // 3. 签名验证示例
        boolean isValid = verify(message, signature, publicKey);
        System.out.println("签名验证结果: " + (isValid ? "有效" : "无效"));
    }

    public static byte[] sign(String message, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes("UTF-8"));
        return signature.sign();
    }

    public static boolean verify(String message, byte[] signature, PublicKey publicKey) throws Exception {
        Signature verifySignature = Signature.getInstance("SHA256withRSA");
        verifySignature.initVerify(publicKey);
        verifySignature.update(message.getBytes("UTF-8"));
        return verifySignature.verify(signature);
    }
}

3. 完整整合示例

以下代码将 AES 加密/解密和 RSA 签名验证结合在一个程序中。例如,先对消息进行 AES 加密,然后用 RSA 签名加密后的数据,最后验证签名并解密。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.*;
import java.util.Base64;

public class CryptoIntegration {
    public static void main(String[] args) throws Exception {
        // 生成 AES 密钥和 RSA 密钥对
        SecretKey aesKey = generateAESKey();
        KeyPair rsaKeyPair = generateRSAKeyPair();

        // 原始消息
        String originalMessage = "集成测试: AES 和 RSA 协同工作!";
        System.out.println("原始消息: " + originalMessage);

        // 步骤1: AES 加密消息
        byte[] encryptedData = encryptWithAES(originalMessage, aesKey);
        System.out.println("AES 加密后数据 (Base64): " + Base64.getEncoder().encodeToString(encryptedData));

        // 步骤2: 对加密数据生成 RSA 签名
        byte[] signature = signWithRSA(encryptedData, rsaKeyPair.getPrivate());
        System.out.println("RSA 签名 (Base64): " + Base64.getEncoder().encodeToString(signature));

        // 步骤3: 验证 RSA 签名
        boolean isSignatureValid = verifyRSASignature(encryptedData, signature, rsaKeyPair.getPublic());
        System.out.println("签名验证: " + (isSignatureValid ? "成功" : "失败"));

        if (isSignatureValid) {
            // 步骤4: AES 解密消息
            String decryptedMessage = decryptWithAES(encryptedData, aesKey);
            System.out.println("解密后消息: " + decryptedMessage);
        }
    }

    // AES 密钥生成
    public static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256, new SecureRandom());
        return keyGen.generateKey();
    }

    // RSA 密钥对生成
    public static KeyPair generateRSAKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        keyPairGen.initialize(2048, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    // AES 加密
    public static byte[] encryptWithAES(String plainText, SecretKey secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecureRandom random = new SecureRandom();
        byte[] ivBytes = new byte[16];
        random.nextBytes(ivBytes);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));

        byte[] combined = new byte[ivBytes.length + encryptedBytes.length];
        System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
        System.arraycopy(encryptedBytes, 0, combined, ivBytes.length, encryptedBytes.length);
        return combined;
    }

    // AES 解密
    public static String decryptWithAES(byte[] combinedData, SecretKey secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] ivBytes = new byte[16];
        System.arraycopy(combinedData, 0, ivBytes, 0, ivBytes.length);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

        byte[] encryptedBytes = new byte[combinedData.length - ivBytes.length];
        System.arraycopy(combinedData, ivBytes.length, encryptedBytes, 0, encryptedBytes.length);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, "UTF-8");
    }

    // RSA 签名生成
    public static byte[] signWithRSA(byte[] data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    // RSA 签名验证
    public static boolean verifyRSASignature(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
        Signature verifySignature = Signature.getInstance("SHA256withRSA");
        verifySignature.initVerify(publicKey);
        verifySignature.update(data);
        return verifySignature.verify(signature);
    }
}

安全注意事项
  • 密钥管理:切勿硬编码密钥。在实际应用中,使用 Java KeyStore (JKS) 或硬件安全模块 (HSM) 存储密钥。AES 密钥应定期更换。
  • 算法选择:使用强参数,如 AES-256 和 RSA-2048(或更高)。避免过时算法(如 DES 或 SHA-1)。
  • 初始化向量 (IV):IV 必须随机且唯一(每次加密生成新 IV),并安全传输(如与加密数据一起存储)。
  • 异常处理:代码中省略了部分异常处理以简洁,但实际中需捕获 GeneralSecurityException 等异常。
  • 性能考虑:AES 适合大数据加密,RSA 较慢,通常用于小数据(如加密 AES 密钥或签名)。在整合示例中,RSA 签名应用于加密数据,而非原始消息。
  • 依赖:确保 Java 环境支持这些算法(标准 JDK 8+ 已包含)。

通过以上步骤,您可以安全地实现 AES 加密/解密和 RSA 签名验证。运行代码前,请测试并调整异常处理。如果有具体场景需求(如文件加密),可进一步优化代码。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐