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

如何用Windows CryptoAPI实现AES-128 CBC模式超块长数据加密?

解决Windows CryptoAPI AES-CBC加密MORE_DATA(234)错误的方案

首先,你的核心问题出在手动分块逻辑和CryptEncrypt参数的错误搭配,加上缓冲区大小计算不足,导致了234(MORE_DATA)错误。下面我会逐一拆解问题,然后给出修正后的完整代码。

问题根源分析

  1. 错误使用Final=TRUE参数:
    你在循环的每个分块都传入TRUE作为Final参数,这会让CryptoAPI认为当前块是最后一块,自动执行PKCS7填充。但你的缓冲区只预留了AES_BLOCK_SIZE*2,当需要填充时,缓冲区空间不足就会触发MORE_DATA错误。而且手动分块完全没必要——CryptEncrypt本身支持流式加密,不需要自己拆分数据。

  2. 缓冲区大小计算错误:
    CryptEncrypt要求最后一个参数传入缓冲区的总可用大小,而你传入的encrypted.length()是初始的128字节,当需要填充或处理数据时,这个空间不够。正确的做法是给加密缓冲区预留至少明文长度 + AES_BLOCK_SIZE的空间(因为PKCS7填充最多会添加一个完整块)。

  3. 密钥算法不匹配:
    你需求是AES-128,但代码里用了CALG_AES_256生成密钥,这会导致密钥长度不符,需要修正为CALG_AES_128。

  4. IV处理不规范:
    AES-CBC的IV必须严格等于块大小(16字节),你的pad(Iv, AES_BLOCK_SIZE)如果是自定义填充,建议直接用零填充到16字节,或者确保输入的Iv本身就是16字节长度,避免后续加密异常。


修正后的完整代码

1. AES初始化代码(修正密钥算法、IV处理)

#include <windows.h>
#include <wincrypt.h>
#include <string>
#include <cstdio>
#include <cstring>

#pragma comment(lib, "advapi32.lib")

HCRYPTPROV hCryptProv = NULL;
HCRYPTKEY hKey = NULL;
bool AesStat = false;
const int AES_BLOCK_SIZE = 16; // AES块大小固定16字节

void clean() {
    if (hKey) CryptDestroyKey(hKey);
    if (hCryptProv) CryptReleaseContext(hCryptProv, 0);
}

// 辅助函数:将字符串填充到指定长度(这里用零填充)
std::string pad_to_length(const std::string& input, size_t target_len) {
    std::string result = input;
    if (result.size() < target_len) {
        result.append(target_len - result.size(), '\0');
    } else if (result.size() > target_len) {
        result.resize(target_len);
    }
    return result;
}

BOOL AesInitialization(const std::string& key, const std::string& Iv) {
    DWORD dwStatus;
    HCRYPTHASH hHash = NULL;

    // 获取加密上下文,不存在则尝试创建
    if (!CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, NULL)) {
        dwStatus = GetLastError();
        if (dwStatus == NTE_BAD_KEYSET) {
            if (!CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET)) {
                printf("CryptAcquireContext (create new) failed: %x\n", GetLastError());
                return FALSE;
            }
        } else {
            printf("CryptAcquireContext failed: %x\n", dwStatus);
            return FALSE;
        }
    }

    // 创建SHA256哈希对象用于派生密钥
    if (!CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, &hHash)) {
        dwStatus = GetLastError();
        printf("CryptCreateHash failed: %x\n", dwStatus);
        clean();
        return FALSE;
    }

    // 哈希原始密钥
    if (!CryptHashData(hHash, (BYTE*)key.c_str(), key.size(), 0)) {
        dwStatus = GetLastError();
        printf("CryptHashData Failed : %#x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    // 派生AES-128密钥(修正为CALG_AES_128)
    if (!CryptDeriveKey(hCryptProv, CALG_AES_128, hHash, 0, &hKey)) {
        dwStatus = GetLastError();
        printf("CryptDeriveKey failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }
    printf("[+] CryptDeriveKey Success\n");

    // 设置CBC模式
    DWORD dwMode = CRYPT_MODE_CBC;
    if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0)) {
        dwStatus = GetLastError();
        printf("CryptSetKeyParam - setting mode failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    // 设置IV(确保是16字节)
    std::string final_iv = pad_to_length(Iv, AES_BLOCK_SIZE);
    if (!CryptSetKeyParam(hKey, KP_IV, (BYTE *)final_iv.c_str(), 0)) {
        dwStatus = GetLastError();
        printf("CryptSetKeyParam - setting IV failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    CryptDestroyHash(hHash);
    AesStat = true;
    return TRUE;
}

2. AES加密代码(移除手动分块,正确处理缓冲区和Final参数)

BOOL AesEncrypt(std::string& PlainTxt) {
    if (!AesStat) {
        printf("AES not initialized!\n");
        return FALSE;
    }

    // 计算加密后所需的缓冲区大小:明文长度 + 块大小(预留填充空间)
    DWORD encrypted_len = PlainTxt.size() + AES_BLOCK_SIZE;
    std::string encrypted(encrypted_len, '\0');
    DWORD data_len = PlainTxt.size();

    // 调用CryptEncrypt:Final参数设为TRUE,让API自动处理分块和填充
    if (!CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE*)encrypted.data(), &data_len, encrypted_len)) {
        DWORD err = GetLastError();
        printf("AES encrypt failed with %d\n", err);
        return FALSE;
    }

    // 调整加密后的字符串到实际长度
    encrypted.resize(data_len);
    PlainTxt.swap(encrypted); // 替换原明文为密文
    return TRUE;
}

关键注意事项

  • 不要手动分块:CryptEncrypt内部已经实现了CBC模式的链式加密和分块逻辑,你只需要传入完整的明文,设置Final=TRUE即可让API自动处理所有分块和PKCS7填充。
  • 缓冲区预留足够空间:加密后的长度可能比明文长(最多一个块的填充),所以必须预留至少明文长度 + AES_BLOCK_SIZE的空间。
  • 密钥和IV的长度要求:AES-128的密钥是16字节,IV必须等于块大小(16字节),否则CryptoAPI会返回错误。
  • 资源清理:使用完加密上下文和密钥后,一定要调用CryptDestroyKey和CryptReleaseContext释放资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者jony

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:43:04