配置省略,直接上代码。足够简单,里面有注释。当然,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;
}