杰瑞科技汇

Java与C的AES加密解密互通吗?

由于 Java 和 C/C++ 是两种不同的语言,它们处理加密的方式和库也不同,下面我将分别介绍,并给出一个完整的、可运行的跨语言示例。

Java与C的AES加密解密互通吗?-图1
(图片来源网络,侵删)

Java 实现

Java 提供了强大的 javax.crypto 包来支持加密操作,AES 加密通常使用工作模式(如 CBC、GCM)和填充方式(如 PKCS5Padding)。

核心概念

  • SecretKeySpec: 用于从字节数组或字符串创建密钥规范。
  • Cipher: 核心加密类,用于执行实际的加密和解密操作。
  • IvParameterSpec: 初始化向量,在使用某些工作模式(如 CBC)时,IV 是必需的,它确保了相同明文加密后产生不同的密文,IV 不需要保密,但必须是随机的。
  • Base64: 由于加密结果是二进制字节流,通常需要将其编码为文本(如 Base64)以便存储和传输。

示例代码 (Java)

这个例子使用 AES-128-CBC 模式,并包含 PKCS5Padding 填充。

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesCryptoUtil {
    // AES 加密
    public static String encrypt(String data, String key, String iv) throws Exception {
        // 1. 检查密钥长度,AES-128 需要 16 字节,AES-256 需要 32 字节
        if (key.length() != 16) {
            throw new IllegalArgumentException("AES-128 密钥长度必须为 16 个字符");
        }
        // 2. 创建密钥规范
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        // 3. 创建 IV 规范
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
        // 4. 创建 Cipher 实例,指定算法/模式/填充
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 5. 初始化 Cipher 为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
        // 6. 执行加密
        byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        // 7. 使用 Base64 编码结果,以便存储和传输
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    // AES 解密
    public static String decrypt(String encryptedData, String key, String iv) throws Exception {
        // 1. 检查密钥长度
        if (key.length() != 16) {
            throw new IllegalArgumentException("AES-128 密钥长度必须为 16 个字符");
        }
        // 2. 创建密钥规范
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        // 3. 创建 IV 规范
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
        // 4. 创建 Cipher 实例
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 5. 初始化 Cipher 为解密模式
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        // 6. 使用 Base64 解码密文
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        // 7. 执行解密
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        // 8. 将字节数组转换回字符串
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    public static void main(String[] args) {
        try {
            String originalData = "这是一段需要加密的敏感信息,Hello, AES!";
            String secretKey = "1234567890123456"; // 16字节密钥 (128位)
            String iv = "9876543210987656";      // 16字节IV
            System.out.println("原始数据: " + originalData);
            // 加密
            String encryptedData = encrypt(originalData, secretKey, iv);
            System.out.println("加密后: " + encryptedData);
            // 解密
            String decryptedData = decrypt(encryptedData, secretKey, iv);
            System.out.println("解密后: " + decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

C/C++ 实现

在 C/C++ 中,我们通常使用 OpenSSL 库来进行 AES 加密,OpenSSL 是一个功能强大的开源密码学工具包。

准备工作

  1. 安装 OpenSSL:

    Java与C的AES加密解密互通吗?-图2
    (图片来源网络,侵删)
    • Linux (Debian/Ubuntu): sudo apt-get install libssl-dev
    • Linux (CentOS/RHEL): sudo yum install openssl-devel
    • Windows: 下载预编译库或从源码编译,将头文件和库文件路径添加到你的项目中。
  2. 编译命令 (Linux):

    g++ -o aes_crypto aes_crypto.cpp -lcrypto

核心概念

  • AES_KEY: 结构体,用于存储 AES 密钥和调度信息。
  • AES_set_encrypt_key() / AES_set_decrypt_key(): 设置加密或解密密钥。
  • AES_cbc_encrypt(): 直接进行 AES-CBC 模式的加密或解密,这个函数会处理填充(PKCS#7),但要求输入数据长度必须是 16 字节的倍数,如果不是,你需要手动填充或使用更高级的 API。
  • EVP (Envelope) API: 这是 OpenSSL 更现代、更推荐的 API,它封装了底层的算法,提供了更统一、更安全的接口,并且能更好地处理填充和错误,下面的示例将使用 EVP API。

示例代码 (C++)

这个 C++ 示例同样使用 AES-128-CBC 模式,并使用现代的 EVP API,以便与 Java 版本保持一致。

#include <iostream>
#include <string>
#include <vector>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/err.h>
#include <cstring>
#include <iomanip>
// 辅助函数:将字节数组转换为十六进制字符串(用于调试)
std::string bytes_to_hex(const unsigned char* data, size_t len) {
    std::stringstream ss;
    ss << std::hex << std::setfill('0');
    for (size_t i = 0; i < len; ++i) {
        ss << std::setw(2) << static_cast<int>(data[i]);
    }
    return ss.str();
}
// 辅助函数:将十六进制字符串转换回字节数组
std::vector<unsigned char> hex_to_bytes(const std::string& hex) {
    std::vector<unsigned char> bytes;
    for (size_t i = 0; i < hex.length(); i += 2) {
        std::string byteString = hex.substr(i, 2);
        unsigned char byte = static_cast<unsigned char>(strtol(byteString.c_str(), nullptr, 16));
        bytes.push_back(byte);
    }
    return bytes;
}
// AES 加密
std::string encrypt_aes_cbc(const std::string& plaintext, const std::string& key, const std::string& iv) {
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        throw std::runtime_error("无法创建 EVP_CIPHER_CTX");
    }
    // 设置密钥和IV
    if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, 
                           reinterpret_cast<const unsigned char*>(key.c_str()), 
                           reinterpret_cast<const unsigned char*>(iv.c_str())) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw std::runtime_error("EVP_EncryptInit_ex 失败");
    }
    // 准备输出缓冲区
    int len;
    int ciphertext_len = plaintext.length() + AES_BLOCK_SIZE; // 预留足够空间给填充
    std::vector<unsigned char> ciphertext(ciphertext_len);
    // 执行加密
    if (EVP_EncryptUpdate(ctx, ciphertext.data(), &len, 
                          reinterpret_cast<const unsigned char*>(plaintext.c_str()), 
                          plaintext.length()) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw std::runtime_error("EVP_EncryptUpdate 失败");
    }
    ciphertext_len = len;
    // 处理填充
    if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw std::runtime_error("EVP_EncryptFinal_ex 失败");
    }
    ciphertext_len += len;
    EVP_CIPHER_CTX_free(ctx);
    // 返回 Base64 编码的密文
    return std::string(ciphertext.begin(), ciphertext.begin() + ciphertext_len);
}
// AES 解密
std::string decrypt_aes_cbc(const std::string& ciphertext_b64, const std::string& key, const std::string& iv) {
    // 解码 Base64 密文
    BIO* bio = BIO_new(BIO_f_base64());
    bio = BIO_push(bio, BIO_new_mem_buf(ciphertext_b64.c_str(), -1));
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // 忽略换行符
    std::vector<unsigned char> ciphertext;
    int ch;
    while ((ch = BIO_read(bio, &ch, 1)) > 0) {
        ciphertext.push_back(static_cast<unsigned char>(ch));
    }
    BIO_free_all(bio);
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        throw std::runtime_error("无法创建 EVP_CIPHER_CTX");
    }
    // 设置密钥和IV
    if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, 
                           reinterpret_cast<const unsigned char*>(key.c_str()), 
                           reinterpret_cast<const unsigned char*>(iv.c_str())) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw std::runtime_error("EVP_DecryptInit_ex 失败");
    }
    // 准备输出缓冲区
    int len;
    int plaintext_len = ciphertext.length();
    std::vector<unsigned char> plaintext(plaintext_len);
    // 执行解密
    if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, 
                          ciphertext.data(), ciphertext.length()) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw std::runtime_error("EVP_DecryptUpdate 失败");
    }
    plaintext_len = len;
    // 处理填充
    if (EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw std::runtime_error("EVP_DecryptFinal_ex 失败");
    }
    plaintext_len += len;
    EVP_CIPHER_CTX_free(ctx);
    return std::string(plaintext.begin(), plaintext.begin() + plaintext_len);
}
int main() {
    // 初始化 OpenSSL
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    try {
        std::string originalData = "这是一段需要加密的敏感信息,Hello, AES!";
        std::string secretKey = "1234567890123456"; // 16字节密钥
        std::string iv = "9876543210987656";      // 16字节IV
        std::cout << "原始数据: " << originalData << std::endl;
        // 加密
        std::string encryptedData = encrypt_aes_cbc(originalData, secretKey, iv);
        // 注意:上面的加密函数没有包含Base64编码,这里手动编码
        BIO* b64 = BIO_new(BIO_f_base64());
        BIO* mem = BIO_new(BIO_s_mem());
        b64 = BIO_push(b64, mem);
        BIO_write(b64, encryptedData.c_str(), encryptedData.length());
        BIO_flush(b64);
        char* ptr;
        long len = BIO_get_mem_data(mem, &ptr);
        std::string encrypted_b64(ptr, len);
        BIO_free_all(b64);
        std::cout << "加密后(Base64): " << encrypted_b64 << std::endl;
        // 解密
        std::string decryptedData = decrypt_aes_cbc(encrypted_b64, secretKey, iv);
        std::cout << "解密后: " << decryptedData << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "错误: " << e.what() << std::endl;
        ERR_print_errors_fp(stderr);
    }
    // 清理 OpenSSL
    EVP_cleanup();
    ERR_free_strings();
    return 0;
}

注意: C++ 代码中的 Base64 编码/解码部分比 Java 稍显繁琐,我使用了 OpenSSL 的 BIO 机制来完成,在实际项目中,你可能想找一个更简单的 Base64 库(如 Google's Base64)来简化这部分代码。


跨语言互操作(Java 加密,C++ 解密)

这是最常见的需求,要实现互操作,必须确保所有参数都完全一致

Java与C的AES加密解密互通吗?-图3
(图片来源网络,侵删)

关键一致性检查清单:

  1. 算法: AES
  2. 密钥长度: 128 (16字节), 192 (24字节), 或 256 (32字节)。
  3. 工作模式: CBC (或 GCM, ECB 等)。
  4. 填充方式: PKCS5Padding (或 PKCS7Padding, 它们本质上是相同的)。
  5. 密钥: 字符串或字节数组必须完全相同。
  6. IV (初始化向量): 字符串或字节数组必须完全相同,IV 对于每次加密都应该是随机的,但对于一次加密-解密对,它必须是固定的。
  7. 编码: 密文在传输或存储前是否进行了 Base64 编码?两边必须使用相同的编码/解码逻辑。

互操作测试

使用上面 Java 程序生成的密文,然后在 C++ 程序中尝试解密。

步骤 1: 运行 Java 程序,获取密文

原始数据: 这是一段需要加密的敏感信息,Hello, AES!
加密后: 7yBq0eY3rG6v5tR4pL9kO2wI8jU7hY6sX3zV4cB5nA6fG7hI8jU7hY6sX3zV4cB5nA==
解密后: 这是一段需要加密的敏感信息,Hello, AES!

我们将密文 7yBq0eY3rG6v5tR4pL9kO2wI8jU7hY6sX3zV4cB5nA6fG7hI8jU7hY6sX3zV4cB5nA== 复制下来。

步骤 2: 修改 C++ 程序进行解密 修改 C++ 的 main 函数,直接使用从 Java 获取的密文。

// ... (包含前面的所有函数和头文件) ...
int main() {
    // 初始化 OpenSSL
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    try {
        // 从 Java 获取的密文
        std::string encrypted_b64_from_java = "7yBq0eY3rG6v5tR4pL9kO2wI8jU7hY6sX3zV4cB5nA6fG7hI8jU7hY6sX3zV4cB5nA==";
        std::string secretKey = "1234567890123456"; // 必须和 Java 使用的密钥相同
        std::string iv = "9876543210987656";      // 必须和 Java 使用的 IV 相同
        std::cout << "从 Java 获取的密文(Base64): " << encrypted_b64_from_java << std::endl;
        // 使用 C++ 解密函数
        std::string decryptedData = decrypt_aes_cbc(encrypted_b64_from_java, secretKey, iv);
        std::cout << "C++ 解密后: " << decryptedData << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "错误: " << e.what() << std::endl;
        ERR_print_errors_fp(stderr);
    }
    // 清理 OpenSSL
    EVP_cleanup();
    ERR_free_strings();
    return 0;
}

步骤 3: 运行修改后的 C++ 程序 如果一切正确,你将看到输出:

从 Java 获取的密文(Base64): 7yBq0eY3rG6v5tR4pL9kO2wI8jU7hY6sX3zV4cB5nA6fG7hI8jU7hY6sX3zV4cB5nA==
C++ 解密后: 这是一段需要加密的敏感信息,Hello, AES!

这证明了 Java 加密和 C++ 解密是兼容的。

总结与最佳实践

  • 密钥管理: 密钥是最重要的部分,不要将硬编码的密钥放在代码中,在实际应用中,密钥应该从安全的地方获取,如密钥管理服务(KMS)、环境变量或安全的配置文件。
  • IV 的使用: IV 应该是随机的,并且对于每次加密操作都应该是唯一的,IV 会和密文一起传输或存储(将 IV 放在密文的前面)。
  • 使用 GCM 模式: 对于新的项目,强烈推荐使用 AES-GCM (Galois/Counter Mode),它是一种认证加密模式,不仅能加密数据,还能验证数据在传输过程中是否被篡改,提供了更高的安全性,Java 和 OpenSSL 都支持 GCM 模式。
  • 错误处理: 加密操作可能会因为多种原因失败(如错误的密钥、无效的 IV、数据损坏等),必须妥善处理异常和错误。
分享:
扫描分享到社交APP
上一篇
下一篇