WinAPI - CryptDecrypt() 在 AES 256 中无法正常工作

WinAPI - CryptDecrypt() not working properly in AES 256

我以前在Visual Studio中使用crypto++,但现在我想使用wincrypt.hAPI使用 AES 256IV(cbc 模式)加密字符串的函数。

我做了以下步骤,但我对 CryptEncrypt()CryptDecrypt() 函数感到困惑,因为它们似乎无法正常工作:

这是我的全部 code :

// handles for csp and key
HCRYPTPROV hProv = NULL;
HCRYPTPROV hKey = NULL;
BYTE szKey[DEFAULT_AES_KEY_SIZE + 1] = {0};
BYTE szIV[DEFAULT_IV_SIZE + 1] = {0};
// plain bytes
BYTE szPlainText[BUFFER_FOR_PLAINTEXT + 1] = {0};
DWORD dwPlainSize = 0;

// initalize key and plaintext
StrCpyA((LPSTR)szKey, "00112233445566778899001122334455");
StrCpyA((LPSTR)szIV, "4455667788990011");
StrCpyA((LPSTR)szPlainText, "abcdefghijklmnopqrstuvwxyzabcdef");

// blob data for CryptImportKey() function (include key and version and so on...)
struct AES256KEYBLOB
{
    AES256KEYBLOB() { StrCpyA((LPSTR)szBytes, 0); }
    BLOBHEADER bhHdr;
    DWORD dwKeySize;
    BYTE szBytes[DEFAULT_AES_KEY_SIZE + 1];
} AESBlob;

AESBlob.bhHdr.bType = PLAINTEXTKEYBLOB;
AESBlob.bhHdr.bVersion = CUR_BLOB_VERSION;
AESBlob.bhHdr.reserved = 0;
AESBlob.bhHdr.aiKeyAlg = CALG_AES_256;
AESBlob.dwKeySize = DEFAULT_AES_KEY_SIZE;
StrCpyA((LPSTR)AESBlob.szBytes, (LPCSTR)szKey);

// create a cryptographic service provider (CSP)
if(CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
{
    if(CryptImportKey(hProv, (BYTE*)&AESBlob, sizeof(AES256KEYBLOB), NULL, CRYPT_EXPORTABLE, &hKey))
    {
        if(CryptSetKeyParam(hKey, KP_IV, szIV, 0))
        {
            if(CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, lstrlenA((LPCSTR)szPlainText) + 1))
            {
                printf("\nEncrypted data : %s\nSize : %d\n", (LPCSTR)szPlainText, dwPlainSize);

                if(CryptDecrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize)) {
                    printf("\nDecrypted data : %s\nSize : %d\n", (LPCSTR)szPlainText, dwPlainSize);
                }
                else
                    printf("failed to decrypt!");
            }
            else
                printf("failed to encrypt");
        }
    }
}

只加密了半段明文,并没有解密!即使只改变 szPlainText 值,它总是给我下面的输出(这意味着 CryptEncrypt()CryptDecrypt() 没有按预期工作!):

Encrypted data : U╡π7ÑL|FΩ$}├rUqrstuvwxyzabcdef
Size : 16

Decrypted data : U╡π7ÑL|FΩ$}├rUqrstuvwxyzabcdef
Size : 0

这是一个变体,我是来自 VStudio 2015.

的 运行

code.c:

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

#define DEFAULT_AES_KEY_SIZE 32
#define DEFAULT_IV_SIZE 16
#define BUFFER_FOR_PLAINTEXT 32

#define CLEANUP_CRYPT_STUFF(PROV, KEY) \
    CryptDestroyKey(KEY); \
    CryptReleaseContext(PROV, 0)

#define PRINT_FUNC_ERR_AND_RETURN(FUNC) \
    printf("%s (line %d) failed: %d\n", ##FUNC, __LINE__, GetLastError()); \
    return -1


typedef struct AES256KEYBLOB_ {
    BLOBHEADER bhHdr;
    DWORD dwKeySize;
    BYTE szBytes[DEFAULT_AES_KEY_SIZE + 1];
} AES256KEYBLOB;


int main() {
    // handles for csp and key
    HCRYPTPROV hProv = NULL;
    HCRYPTKEY hKey = NULL;
    BYTE szKey[DEFAULT_AES_KEY_SIZE + 1] = { 0 };
    BYTE szIV[DEFAULT_IV_SIZE + 1] = { 0 };
    // plain bytes
    BYTE szPlainText[BUFFER_FOR_PLAINTEXT + 1] = { 0 }, *pBuf = NULL;
    AES256KEYBLOB AESBlob;
    memset(&AESBlob, 0, sizeof(AESBlob));

    // initalize key and plaintext
    StrCpyA((LPSTR)szKey, "00112233445566778899001122334455");
    StrCpyA((LPSTR)szIV, "4455667788990011");
    StrCpyA((LPSTR)szPlainText, "abcdefghijklmnopqrstuvwxyzabcdef");
    DWORD dwPlainSize = lstrlenA((LPCSTR)szPlainText), dwBufSize = dwPlainSize, dwBufSize2 = dwPlainSize;

    // blob data for CryptImportKey() function (include key and version and so on...)
    AESBlob.bhHdr.bType = PLAINTEXTKEYBLOB;
    AESBlob.bhHdr.bVersion = CUR_BLOB_VERSION;
    AESBlob.bhHdr.reserved = 0;
    AESBlob.bhHdr.aiKeyAlg = CALG_AES_256;
    AESBlob.dwKeySize = DEFAULT_AES_KEY_SIZE;
    StrCpyA((LPSTR)AESBlob.szBytes, (LPCSTR)szKey);

    // create a cryptographic service provider (CSP)
    if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
        PRINT_FUNC_ERR_AND_RETURN(CryptAcquireContextA);
    }
    if (!CryptImportKey(hProv, (BYTE*)&AESBlob, sizeof(AES256KEYBLOB), NULL, CRYPT_EXPORTABLE, &hKey)) {
        CryptReleaseContext(hProv, 0);
        PRINT_FUNC_ERR_AND_RETURN(CryptImportKey);
    }
    if (!CryptSetKeyParam(hKey, KP_IV, szIV, 0)) {
        CLEANUP_CRYPT_STUFF(hProv, hKey);
        PRINT_FUNC_ERR_AND_RETURN(CryptSetKeyParam);
    }
    if (CryptEncrypt(hKey, NULL, TRUE, 0, NULL, &dwBufSize, 0)) {
        printf("%d bytes required to hold the encrypted buf\n", dwBufSize);
        if ((pBuf = calloc(dwBufSize, sizeof(BYTE))) == NULL)
        {
            CLEANUP_CRYPT_STUFF(hProv, hKey);
            PRINT_FUNC_ERR_AND_RETURN(calloc);
        }
        StrCpyA(pBuf, szPlainText);
    } else {
        CLEANUP_CRYPT_STUFF(hProv, hKey);
        PRINT_FUNC_ERR_AND_RETURN(CryptEncrypt);
    }
    if (CryptEncrypt(hKey, NULL, TRUE, 0, pBuf, &dwBufSize2, dwBufSize)) {
        printf("\nEncrypted data: [%s]\nSize: %d\n", (LPCSTR)pBuf, dwBufSize2);
        if (CryptDecrypt(hKey, NULL, TRUE, 0, pBuf, &dwBufSize)) {
            printf("\nDecrypted data: [%s]\nSize: %d\n", (LPCSTR)pBuf, dwBufSize);
        } else {
            free(pBuf);
            CLEANUP_CRYPT_STUFF(hProv, hKey);
            PRINT_FUNC_ERR_AND_RETURN(CryptDecrypt);
        }
    } else {
        free(pBuf);
        CLEANUP_CRYPT_STUFF(hProv, hKey);
        PRINT_FUNC_ERR_AND_RETURN(CryptEncrypt);
    }
    free(pBuf);
    CLEANUP_CRYPT_STUFF(hProv, hKey);
    return 0;
}

备注:

  • 删除了 AES256KEYBLOB 构造函数,因为我遇到了 Access Violation(这在玩内存 "that's not yours" 时是正常的)。用 memset 调用
  • 替换了结构初始化
  • main(逻辑)错误是缓冲区不够大,无法存储加密文本(结合错误值(0) 对于 dwPlainSize - 使函数误导性地成功)。根据[MS.Docs]: CryptEncrypt function:

    If this parameter contains NULL, this function will calculate the required size for the ciphertext and place that in the value pointed to by the pdwDataLen parameter.

    要找出所需的大小,请额外调用该函数,将 pbData 设置为 NULL(这种做法也是在其他 WinAPIs 中遇到过)。然后分配一个缓冲区,用所需的数据填充它,并使 "main" 调用该缓冲区上的函数...

  • 添加了缺失的 #includemain

  • 添加了代码以在不再需要时释放已使用的资源
  • 重构:
    • 否定了if条件,因为我不喜欢那么多嵌套层数
    • 添加了一些方便的宏(CLEANUP_CRYPT_STUFFPRINT_FUNC_ERR_AND_RETURN),以避免代码重复
  • 其他小修复/改进
  • 您可能想要添加一个函数,它从缓冲区中精确打印 N 个字节,如“%s" 说明符仅在遇到 "\0" 时停止,并且仅 (愚蠢的)当 printfing 缓冲区
  • 时,运气阻止了控制台充满垃圾(甚至程序崩溃)
  • 可能还有一些与此功能相关的其他方面我没有处理(因为我不是这方面的专家),但目标只是让 smth 工作

输出:

48 bytes required to hold the encrypted buf

Encrypted data: [<É╙åh∩φ:bOPs  r2w~w╪c╟D╡ï╥V╟neΓßv∩·J8cÅ╥²²²²s]
Size: 48

Decrypted data: [abcdefghijklmnopqrstuvwxyzabcdefΓßv∩·J8cÅ╥²²²²s]
Size: 32

答案很棒,它让我提供使用波纹管语句来计算密码长度:

CryptEncrypt(hKey, NULL, TRUE, 0, NULL, &dwBufSize, 0);

根据微软文档:

If pbData is NULL, no error is returned, and the function stores the size of the encrypted data, in bytes, in the DWORD value pointed to by pdwDataLen) :

BOOL CryptEncrypt(
  HCRYPTKEY  hKey,
  HCRYPTHASH hHash,
  BOOL       Final,
  DWORD      dwFlags,
  BYTE       *pbData,
  DWORD      *pdwDataLen,
  DWORD      dwBufLen
);

我的解决方案:

但是在我的代码中,当我将它提供给 CryptEncrypt() 时,我忘记计算 szPlainText 大小:

DWORD dwPlainSize = 0;    // initialized with 0

所以 "zero-length" 对波纹管函数没有任何意义(CryptEncrypt() 函数总是得到长度为 0 的纯文本):

CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, lstrlenA((LPCSTR)szPlainText) + 1)

我应该用下面的语句设置它的大小(我的 code 只需添加这个就可以工作):

dwPlainSize = lstrlenA((LPCSTR)szPlainText);

然后按正确的大小写传递:

CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, BUFFER_FOR_PLAINTEXT)

所以,输出如下所示:

Encrypted data : <É╙åh∩φ:bOPs  r2w~w╪c╟D╡ï╥V╟neΓßv∩·J8cÅ╥
Size : 48

Decrypted data : abcdefghijklmnopqrstuvwxyzabcdefΓßv∩·J8cÅ╥
Size : 32