如何使用OpenSSL AES算法加密每一帧图像数据
使用OpenSSL AES加密OpenCV捕获的可变长度帧数据
嘿,我来帮你搞定这个问题!你现在已经能处理固定16字节的AES加解密,但面对可变长度的frame_data(std::vector<unsigned char>),得先搞清楚AES的核心特性:它是块加密算法,只能处理128位(16字节)的固定块,所以直接用你现有的AES_encrypt函数处理任意长度数据肯定不行——得结合分组密码模式(比如CBC、GCM)和填充机制来适配可变长度的数据。
下面我分两种常用的方案给你讲,先从基础的CBC模式入手,再提更安全高效的GCM模式:
方案一:AES-CBC模式(带PKCS#7填充)
CBC是最常用的分组模式之一,需要配合初始化向量(IV)和填充来处理非16字节倍数的数据。
核心步骤
- 生成随机IV:每次加密都要生成一个16字节的随机IV,它不需要保密,但必须和密钥配对唯一(不能用同一个IV加密两个不同的明文)
- PKCS#7填充:把
frame_data的长度补成16的整数倍,填充的字节值等于需要填充的字节数(比如差3字节就填充3个0x03) - 加密操作:用OpenSSL的低级AES接口完成加密,最后把IV和密文放在一起(解密时需要先取出IV)
代码实现
#include <openssl/aes.h> #include <openssl/rand.h> #include <vector> #include <cstring> // PKCS#7填充函数 void pkcs7_pad(std::vector<unsigned char>& data) { int block_size = AES_BLOCK_SIZE; // 16字节 int padding_len = block_size - (data.size() % block_size); for (int i = 0; i < padding_len; ++i) { data.push_back(static_cast<unsigned char>(padding_len)); } } // PKCS#7去填充函数 bool pkcs7_unpad(std::vector<unsigned char>& data) { if (data.empty()) return false; unsigned char padding_len = data.back(); if (padding_len > AES_BLOCK_SIZE || padding_len == 0) return false; // 检查填充是否合法 for (size_t i = data.size() - padding_len; i < data.size(); ++i) { if (data[i] != padding_len) return false; } data.resize(data.size() - padding_len); return true; } // AES-CBC加密 std::vector<unsigned char> aes_cbc_encrypt(const std::vector<unsigned char>& plaintext, const unsigned char* key) { std::vector<unsigned char> result; // 1. 生成随机IV unsigned char iv[AES_BLOCK_SIZE]; if (!RAND_bytes(iv, AES_BLOCK_SIZE)) { return result; // 生成随机数失败 } // 2. 填充明文 std::vector<unsigned char> padded_plaintext = plaintext; pkcs7_pad(padded_plaintext); // 3. 初始化加密上下文 AES_KEY enc_key; if (AES_set_encrypt_key(key, 128, &enc_key) != 0) { return result; // 密钥设置失败 } // 4. 执行加密 result.resize(AES_BLOCK_SIZE + padded_plaintext.size()); memcpy(result.data(), iv, AES_BLOCK_SIZE); // 把IV放在结果开头 AES_cbc_encrypt(padded_plaintext.data(), result.data() + AES_BLOCK_SIZE, padded_plaintext.size(), &enc_key, iv, AES_ENCRYPT); return result; } // AES-CBC解密 std::vector<unsigned char> aes_cbc_decrypt(const std::vector<unsigned char>& ciphertext, const unsigned char* key) { std::vector<unsigned char> result; if (ciphertext.size() < AES_BLOCK_SIZE) { return result; // 密文长度不够(至少要包含IV) } // 1. 取出IV unsigned char iv[AES_BLOCK_SIZE]; memcpy(iv, ciphertext.data(), AES_BLOCK_SIZE); // 2. 初始化解密上下文 AES_KEY dec_key; if (AES_set_decrypt_key(key, 128, &dec_key) != 0) { return result; } // 3. 执行解密 result.resize(ciphertext.size() - AES_BLOCK_SIZE); AES_cbc_encrypt(ciphertext.data() + AES_BLOCK_SIZE, result.data(), result.size(), &dec_key, iv, AES_DECRYPT); // 4. 去填充 if (!pkcs7_unpad(result)) { result.clear(); } return result; } // 使用示例 int main() { unsigned char key[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; // 替换成你的密钥 std::vector<unsigned char> frame_data = { /* 你的OpenCV帧数据 */ }; // 加密 auto cipher_data = aes_cbc_encrypt(frame_data, key); // 解密 auto decrypted_data = aes_cbc_decrypt(cipher_data, key); return 0; }
方案二:AES-GCM模式(无需填充,带完整性校验)
如果你需要更高的安全性(比如防止数据被篡改),GCM模式是更好的选择——它不需要填充,还能生成一个认证标签(Tag),解密时可以验证数据是否被篡改。推荐用OpenSSL的EVP接口来实现,比低级的AES接口更易用。
核心代码示例
#include <openssl/evp.h> #include <openssl/rand.h> #include <vector> #include <cstring> // AES-GCM加密(包含IV和Tag) std::vector<unsigned char> aes_gcm_encrypt(const std::vector<unsigned char>& plaintext, const unsigned char* key) { std::vector<unsigned char> result; EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) return result; // 生成随机IV(GCM推荐用12字节IV,效率更高) unsigned char iv[12]; if (!RAND_bytes(iv, sizeof(iv))) { EVP_CIPHER_CTX_free(ctx); return result; } // 初始化加密上下文 if (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, key, iv) != 1) { EVP_CIPHER_CTX_free(ctx); return result; } // 执行加密 std::vector<unsigned char> ciphertext(plaintext.size() + EVP_CIPHER_block_size(EVP_aes_128_gcm())); int len; if (EVP_EncryptUpdate(ctx, ciphertext.data(), &len, plaintext.data(), plaintext.size()) != 1) { EVP_CIPHER_CTX_free(ctx); return result; } int ciphertext_len = len; // 结束加密,获取认证标签(Tag) unsigned char tag[16]; if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) != 1) { EVP_CIPHER_CTX_free(ctx); return result; } ciphertext_len += len; EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag); // 组装结果:IV(12) + 密文 + Tag(16) result.resize(12 + ciphertext_len + 16); memcpy(result.data(), iv, 12); memcpy(result.data() + 12, ciphertext.data(), ciphertext_len); memcpy(result.data() + 12 + ciphertext_len, tag, 16); EVP_CIPHER_CTX_free(ctx); return result; } // AES-GCM解密 std::vector<unsigned char> aes_gcm_decrypt(const std::vector<unsigned char>& ciphertext_with_iv_tag, const unsigned char* key) { std::vector<unsigned char> result; if (ciphertext_with_iv_tag.size() < 12 + 16) { // IV(12) + Tag(16) return result; } EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) return result; // 取出IV、密文、Tag const unsigned char* iv = ciphertext_with_iv_tag.data(); const unsigned char* ciphertext = iv + 12; int ciphertext_len = ciphertext_with_iv_tag.size() - 12 - 16; const unsigned char* tag = ciphertext + ciphertext_len; // 初始化解密上下文 if (EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, key, iv) != 1) { EVP_CIPHER_CTX_free(ctx); return result; } // 设置要验证的Tag if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag) != 1) { EVP_CIPHER_CTX_free(ctx); return result; } // 执行解密 result.resize(ciphertext_len); int len; if (EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext, ciphertext_len) != 1) { EVP_CIPHER_CTX_free(ctx); result.clear(); return result; } int plaintext_len = len; // 结束解密,验证Tag if (EVP_DecryptFinal_ex(ctx, result.data() + len, &len) != 1) { EVP_CIPHER_CTX_free(ctx); result.clear(); return result; // 验证失败,数据可能被篡改 } plaintext_len += len; result.resize(plaintext_len); EVP_CIPHER_CTX_free(ctx); return result; }
注意事项
- 密钥要保密,且长度符合AES规格(128位=16字节,192位=24字节,256位=32字节)
- IV必须随机生成,不能重复使用同一个IV和密钥组合,否则会泄露密钥信息
- GCM模式的IV推荐用12字节,这样Tag的生成效率更高
- 如果你用OpenSSL 1.1.0及以上版本,EVP接口的使用更简洁,不需要手动管理很多上下文细节
内容的提问来源于stack exchange,提问作者CoXier
相关产品推荐
相关产品推荐

