c++ - 如何使用 CryptoApi 以使用 AES-128 加密超过一个 block 大小的缓冲区

标签 c++ windows aes block-cipher

我需要使用 Windows CryptoApi 和 aes128 CBC 模式加密长度大于 block 大小(128 位)的数据。

为了实现这个目标,我尝试了多种方法,但都没有奏效。 我知道链接背后的理论,理论上我知道我必须做什么。

  1. 将缓冲区分成 block 大小的 block
  2. 必要时填充
  3. xor 第一个 block 的 IV 大小为 block 大小
  4. 加密缓冲区
  5. 重复 - 下一个 block 与前一个 block 异或

上面的一些是由 CryptoApi 实现的(比如用 IV 对第一个 block 进行异或运算)- 我红色的是,当将 TRUE 作为函数的最终参数传递时,CryptEncrypt 也正在完成填充(我可能是错的)。 我也尝试过使用 PKCS7 算法手动填充每个 block ,但也没有得到结果。 AES 初始化代码(这是 CryptoApi 的所有初始化完成的地方,包括算法细节,如 IV 和 key 设置):

BOOL AesInitialization()
{

    DWORD dwStatus;
    HCRYPTHASH hHash;
    string FinalIv;

    if (!CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, NULL))
    {
        dwStatus = GetLastError();
        printf("CryptAcquireContext failed: %x\n", dwStatus);
        return FALSE;
    }

    if (!CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, &hHash))
    {
        dwStatus = GetLastError();
        printf("CryptCreateHash failed: %x\n", dwStatus);
        clean();
        return FALSE;
    }

    //FinalKey = key;

    if (!CryptHashData(hHash, (BYTE*)key, strlen(key), 0))
    {
        dwStatus = GetLastError();
        printf("CryptHashData Failed : %#x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    if (!CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey))
    {
        dwStatus = GetLastError();
        printf("CryptDeriveKey failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    printf("[+] CryptDeriveKey Success\n");

    DWORD dwMode = CRYPT_MODE_CBC;

// Set the mode to Cipher Block Chaining

    if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0))
    {
        dwStatus = GetLastError();
        printf("CryptSetKeyParam - setting mode failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }
// Set the Initialization Vector to ours

    FinalIv =  pad(Iv, AES_BLOCK_SIZE);

    if (!CryptSetKeyParam(hKey, KP_IV, (BYTE *)&FinalIv[0], 0))
    {
        dwStatus = GetLastError();
        printf("CryptSetKeyParam - setting IV failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

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

这是完成加密的代码的实际部分

/*My code has changed several times as I tried many ways to accomplish my 
goal.
My current code(which doesn't include manual padding right now), looks 
like this:*/


BOOL AesEncrypt(string & PlainTxt)
{
    // deviding into blocks sized AES_BLOCK_SIZE
    string cipher;
    string encrypted;
    DWORD block_length;
    unsigned total_length = PlainTxt.length();

    encrypted.resize(AES_BLOCK_SIZE * 2); // give enough space for padding

   for (unsigned index = 0; index < total_length; index += AES_BLOCK_SIZE)
    {
        encrypted.clear(); // clear former content
        memcpy_s(&encrypted[0], encrypted.size(), &PlainTxt[index], AES_BLOCK_SIZE);

        block_length = AES_BLOCK_SIZE;

        if (!CryptEncrypt(hKey, NULL, true, 0, (BYTE *)&encrypted[0], &block_length, encrypted.length()))
        {
            printf("aes failed with %d\n", GetLastError());
            return FALSE;
        }
        cipher.append(encrypted.data()); // append data to final string
    }

    PlainTxt.clear();
    PlainTxt.resize(cipher.size());
    PlainTxt.append(cipher.data());

    return TRUE;
}

第一次迭代失败,出现 234 错误 (MORE_DATA),长度由 CryptEncrypt 设置为 0x80(128,以字节为单位)。

最佳答案

我很确定您不需要一次一个 block 地加密数据。如果您的数据在单个缓冲区中,则只需调用一次 CryptEncrypt 即可加密整个缓冲区。 , Final 标志设置为 true。

CryptEncrypt 函数还允许您通过将 Final 设置为 false 来加密任意大小的多个 block 中的数据 - 只要它是密码 block 大小的倍数除最后一次通话外的所有通话。如果您是,这可能很有用。尝试加密一个太长的大文件,无法一次全部加载到内存中,但您不需要在此处使用该功能。

事实上,您需要做的(在为 hKey 设置正确的密码模式和 IV 之后)是这样的:

DWORD dwDataLen = data.length();
data.resize(dwDataLen + AES_BLOCK_SIZE); // give enough space for padding
success = CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen, data.length());
data.resize(dwDataLen);  // resize to actual ciphertext length

其中 data 是一个 std::string,它最初包含明文并将被密文覆盖。

并且,为了证明这确实有效,here's a full self-contained test program :

#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

#pragma comment (lib, "advapi32")

using std::string;

char *key = "swordfish";

HCRYPTPROV hCryptProv = NULL; 
HCRYPTKEY hKey = NULL;

#define AES_BLOCK_SIZE 16

BOOL AesInitialization()
{
    char *stage = "n/a";
    BOOL success = TRUE;

    HCRYPTHASH hHash = NULL;

    if (success) {
        stage = "CryptAcquireContext";
        success = CryptAcquireContext(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, NULL);
    }
    if (success) {
        stage = "CryptCreateHash";
        success = CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, &hHash);
    }
    if (success) {
        stage = "CryptHashData";
        success = CryptHashData(hHash, (BYTE*)key, strlen(key), 0);
    }
    if (success) {
        stage = "CryptDeriveKey";
        success = CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey);
    }
    if (success) {
        stage = "CryptSetKeyParam";
        DWORD dwMode = CRYPT_MODE_CBC;
        success = CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0);
    }

    if (!success) printf("AES initialization - %s failed: 0x%08x\n", stage, GetLastError());
    if (hHash != NULL) CryptDestroyHash(hHash);
    return success;
}

BOOL AesEncrypt(string &data, string &iv)
{
    char *stage = "n/a";
    BOOL success = TRUE;
    
    if (success) {
        stage = "CryptGenRandom";
        iv.resize(AES_BLOCK_SIZE);  // allocate space for random IV
        success = CryptGenRandom(hCryptProv, AES_BLOCK_SIZE, (BYTE *)&iv[0]);
    }
    if (success) {
        stage = "CryptSetKeyParam";
        success = CryptSetKeyParam(hKey, KP_IV, (BYTE *)&iv[0], 0);
    }
    if (success) {
        stage = "CryptEncrypt";
        DWORD dwDataLen = data.length();
        data.resize(dwDataLen + AES_BLOCK_SIZE); // give enough space for padding
        success = CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen, data.length());
        data.resize(dwDataLen);  // resize to actual ciphertext length
    }

    if (!success) printf("AES encryption - %s failed: 0x%08x\n", stage, GetLastError());
    return success;
}

BOOL AesDecrypt(string &data, string &iv)
{
    char *stage = "n/a";
    BOOL success = TRUE;
    
    if (success) {
        stage = "CryptSetKeyParam";
        iv.resize(AES_BLOCK_SIZE);  // ensure IV has the correct size
        success = CryptSetKeyParam(hKey, KP_IV, (BYTE *)&iv[0], 0);
    }
    if (success) {
        stage = "CryptDecrypt";
        DWORD dwDataLen = data.length();
        success = CryptDecrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen);
        data.resize(dwDataLen);  // resize to actual ciphertext length
    }

    if (!success) printf("AES encryption - %s failed: 0x%08x\n", stage, GetLastError());
    return success;
}

int main()
{
    if (!AesInitialization()) return 1;

    string message = "Hello, World! This is a test of AES-CBC encryption.";
    printf("Plaintext: %s\n\n", message.data());
    
    string iv;
    if (!AesEncrypt(message, iv)) return 1;

    printf("CBC IV (hex):");
    for (unsigned char c : iv) printf(" %02X", c);
    printf("\n");

    printf("Ciphertext (hex):");
    for (unsigned char c : message) printf(" %02X", c);
    printf("\n\n");
    
    if (!AesDecrypt(message, iv)) return 1;

    printf("Decrypted (hex):");
    for (unsigned char c : message) printf(" %02X", c);
    printf("\n");
    printf("Decrypted (text): %s\n", message.data());

    return 0;
}

我不会试图以此来获得任何 C++ 代码风格奖,因为我实际上对 C++ 或 Microsoft Cryptography API(FWIW,它实际上是一个纯 C API,恰好在 C++ 中也可用)。但至少它有效。

顺便说一句,您可能应该注意 CryptEncrypt 文档页面顶部的注释:

Important  This API is deprecated. New and existing software should start using Cryptography Next Generation APIs. Microsoft may remove this API in future releases.

只是让你知道。

(此外,请记住,如果可以避免的话,您可能不应该使用普通的旧 CBC 模式。请改用 authenticated encryption 模式——最好是像 AES-SIV 这样的防误用模式。如果您确实想要要使用 CBC,至少在加密后对密文应用 message authentication code,并在解密前对其进行验证。除了保护您的数据的完整性外,它还保护您的加密代码免受类似 padding oracle attacks 的影响。)

关于c++ - 如何使用 CryptoApi 以使用 AES-128 加密超过一个 block 大小的缓冲区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57850242/

相关文章:

c++ - 基本使用类来执行基本的定量生物学

c++ - 如何有效地为20000*20000矩阵分配内存?

c++ - 调试帮助 : C++ error

c# - 进行 AES 加密 CBC

java - 我在解包时收到 'CKR_WRAPPED_KEY_INVALID' 错误。如何将 AES key 解包为 DES2 key ?

c++ - 当您将文字常量分配给右值引用时会发生什么?

windows - Nodejs - Windows key /证书存储

c# - 在 Windows 环境中使用 Etsy 的 StatsD

windows - 如何从 C 代码 (Win32) 生成 RFC1123 日期字符串

javascript - 如何修复 Javascript 中的 'invalid ciphertext size' 错误? (aes)