c++中openssl的gcm+tag加密代码入门

51 阅读3分钟

配置省略,直接上代码。足够简单,里面有注释。当然,key需要用派生算法生成,推荐argon2id方式。这里只作展示,使用随机值。

#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>

// 常量定义
const int KEY_SIZE = 32;    // AES-256 需要32字节密钥
const int IV_SIZE = 12;     // GCM推荐使用12字节IV
const int TAG_SIZE = 16;    // GCM认证标签为16字节

// 辅助函数:打印字节数组为十六进制
void printHex(const std::string& label, const unsigned char* data, size_t len) {
    std::cout << label << ": ";
    for (size_t i = 0; i < len; i++) {
        std::cout << std::hex << std::setw(2) << std::setfill('0') 
                  << static_cast<int>(data[i]) << " ";
    }
    std::cout << std::dec << std::endl;
}

// 加密函数
std::vector<unsigned char> encrypt(const unsigned char* plaintext, size_t plaintext_len, 
                                   const unsigned char* key, const unsigned char* iv, 
                                   unsigned char* tag) {
    // 创建加密上下文
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    
    // 初始化加密操作,使用AES-256-GCM模式
    // 参数: 上下文, 加密算法, 引擎(nullptr表示默认), 密钥, 初始化向量
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, key, iv);
    
    // 为输出分配足够空间 (明文长度 + 最大填充长度)
    std::vector<unsigned char> ciphertext(plaintext_len + EVP_MAX_BLOCK_LENGTH);
    int len = 0;
    int ciphertext_len = 0;
    
    // 执行加密操作
    // 参数: 上下文, 输出缓冲区, 输出长度, 输入数据, 输入长度
    EVP_EncryptUpdate(ctx, ciphertext.data(), &len, plaintext, plaintext_len);
    ciphertext_len = len;
    
    // 完成加密,处理任何最终块
    // 参数: 上下文, 输出缓冲区(追加), 输出长度
    EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len);
    ciphertext_len += len;
    
    // 获取GCM认证标签
    // 参数: 上下文, 命令(获取标签), 标签长度, 标签缓冲区
    // GCM标签是对所有已加密数据的一个"校验和"
    // 它用于验证数据的完整性和真实性
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG_SIZE, tag);
    
    // 调整输出向量大小为实际密文长度
    ciphertext.resize(ciphertext_len);
    
    // 清理上下文
    EVP_CIPHER_CTX_free(ctx);
    
    return ciphertext;
}

// 解密函数
std::vector<unsigned char> decrypt(const unsigned char* ciphertext, size_t ciphertext_len,
                                  const unsigned char* key, const unsigned char* iv,
                                  const unsigned char* tag) {
    // 创建解密上下文
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    
    // 初始化解密操作,使用相同的AES-256-GCM模式
    EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, key, iv);
    
    // 为输出分配足够空间
    std::vector<unsigned char> plaintext(ciphertext_len);
    int len = 0;
    int plaintext_len = 0;
    
    // 执行解密操作
    EVP_DecryptUpdate(ctx, plaintext.data(), &len, ciphertext, ciphertext_len);
    plaintext_len = len;
    
    // 设置期望的GCM认证标签
    // 这一步是GCM模式特有的,必须在EVP_DecryptFinal_ex之前设置
    // 如果提供的标签与加密生成的标签不匹配,解密将失败
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, TAG_SIZE, (void*)tag);
    
    // 完成解密,此步骤会验证认证标签
    // 如果验证失败,返回值小于等于0
    int ret = EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len);
    
    // 清理上下文
    EVP_CIPHER_CTX_free(ctx);
    
    // 验证GCM标签是否匹配
    if (ret <= 0) {
        std::cerr << "GCM标签验证失败!数据可能被篡改" << std::endl;
        return std::vector<unsigned char>();
    }
    
    // 调整明文大小并返回
    plaintext_len += len;
    plaintext.resize(plaintext_len);
    return plaintext;
}

int main() {
    // 示例消息
    std::string message = "这是一条需要加密的重要消息";
    const unsigned char* plaintext = reinterpret_cast<const unsigned char*>(message.data());
    size_t plaintext_len = message.length();
    
    // 生成随机密钥
    unsigned char key[KEY_SIZE];
    RAND_bytes(key, KEY_SIZE);
    
    // 生成随机IV
    unsigned char iv[IV_SIZE];
    RAND_bytes(iv, IV_SIZE);
    
    // GCM认证标签
    unsigned char tag[TAG_SIZE];
    
    // 打印输入数据
    printHex("原始数据", plaintext, plaintext_len);
    printHex("密钥", key, KEY_SIZE);
    printHex("IV", iv, IV_SIZE);
    
    // 加密
    std::vector<unsigned char> ciphertext = encrypt(plaintext, plaintext_len, key, iv, tag);
    printHex("密文", ciphertext.data(), ciphertext.size());
    printHex("GCM标签", tag, TAG_SIZE);
    
    std::cout << "\n======== 篡改测试 ========\n" << std::endl;
    
    // 正常解密
    std::vector<unsigned char> decrypted = decrypt(ciphertext.data(), ciphertext.size(), key, iv, tag);
    if (!decrypted.empty()) {
        std::string decrypted_str(reinterpret_cast<char*>(decrypted.data()), decrypted.size());
        std::cout << "解密结果: " << decrypted_str << std::endl;
    }
    
    // 篡改密文并尝试解密(应该失败)
    if (!ciphertext.empty()) {
        ciphertext[0] ^= 1; // 修改密文的第一个字节
        printHex("被篡改的密文", ciphertext.data(), ciphertext.size());
        
        std::vector<unsigned char> tampered_decrypted = decrypt(ciphertext.data(), ciphertext.size(), key, iv, tag);
        if (tampered_decrypted.empty()) {
            std::cout << "篡改检测成功:GCM标签验证拒绝了被修改的数据" << std::endl;
        } else {
            std::cout << "警告:篡改的数据通过了验证(不应该发生)" << std::endl;
        }
    }
    
    return 0;
}