You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:28:45