这个旨在使用 Windows CryptoAPI 使用 AES 256 位密钥解密的简单代码有什么问题?

What is wrong with this simple code meant to decrypt with AES 256 bit key using the Windows CryptoAPI?

我有一个使用 AES-256 密钥加密文件的小程序。用于加密文件的密钥是随机生成的。

加密程序是这样的:

加密的文件是一个小 test.txt 文件,其中包含字符串:"just a test"。所以文件中的原始十六进制字节是:

6A 75 73 74 20 61 20 74 65 73 74

用于十六进制格式加密的 AES-256 密钥是:

3f10e23bb1a5dfd9c8ca06195e43043386a9ba4c63c35ac518f463ba768f001b

加密文件test.enc然后有字节:

C8 B5 92 51 22 53 75 A1 34 80 EC AA 37 1C 6C BE 

问题:

如何使用 Windows CryptoAPI 的 CryptDecrypt 函数使用十六进制 AES-256 密钥编写 c/c++ 程序来解密这些字节?

我试过的:

我写了下面的解密程序(对gist here稍作修改)

#include <Windows.h>
#include <wincrypt.h>
#include <stdio.h>
#pragma comment(lib, "crypt32.lib")

#define BLOCK_LEN 128

HCRYPTPROV hCryptProv;

int wmain(int argc, wchar_t* argv[])
{
    wchar_t default_key[] = L"PxDiO7Gl39nIygYZXkMEM4apukxjw1rFGPRjunaPABs";
    wchar_t* key_str = default_key;
    size_t len = lstrlenW(key_str);



    if (!CryptAcquireContext(
        &hCryptProv,
        NULL,
        MS_ENH_RSA_AES_PROV,
        PROV_RSA_AES,
        NULL))
    {
        /*std::cout << "error acquiring context\n";
        std::cout << GetLastErrorAsString();*/
        exit(1);
    }

    HCRYPTKEY hKey;


    wchar_t* filename = argv[1];
    wchar_t* filename2 = argv[2];

    printf("Key: %S\n", key_str);
    printf("Key len: %#x\n", len);
    printf("Input File: %S\n", filename);
    printf("Output File: %S\n", filename2);
    printf("----\n");

    HANDLE hInpFile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
    if (hInpFile == INVALID_HANDLE_VALUE) {
        printf("Cannot open input file!\n");
        system("pause");
        return (-1);
    }
    printf("\nEncrypted file read.");

    HANDLE hOutFile = CreateFileW(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hOutFile == INVALID_HANDLE_VALUE) {
        printf("Cannot open output file!\n");
        system("pause");
        return (-1);
    }
    printf("\nDecryption file created.");

    DWORD dwStatus = 0;
    BOOL bResult = FALSE;
    wchar_t info[] = L"Microsoft Enhanced RSA and AES Cryptographic Provider";

    /*BOOL CryptDeriveKey(
        HCRYPTPROV hProv,
        ALG_ID     Algid,
        HCRYPTHASH hBaseData,
        DWORD      dwFlags,
        HCRYPTKEY * phKey
    );*/

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

    if (!CryptHashData(hHash, (BYTE*)key_str, len, 0)) {
        DWORD err = GetLastError();
        printf("CryptHashData Failed : %#x\n", err);
        system("pause");
        return (-1);
    }
    printf("[+] CryptHashData Success\n");

    if (!CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey)) {
        dwStatus = GetLastError();
        printf("CryptDeriveKey failed: %x\n", dwStatus);
        CryptReleaseContext(hCryptProv, 0);
        system("pause");
        return dwStatus;
    }
    printf("[+] CryptDeriveKey Success\n");


    const size_t chunk_size = BLOCK_LEN;
    BYTE chunk[chunk_size] = { 0 };
    DWORD out_len = 0;

    BOOL isFinal = FALSE;
    DWORD readTotalSize = 0;

    DWORD inputSize = GetFileSize(hInpFile, NULL);

    while (bResult = ReadFile(hInpFile, chunk, chunk_size, &out_len, NULL)) {
        if (0 == out_len) {
            break;
        }
        printf("\nFile read.");
        readTotalSize += out_len;
        if (readTotalSize == inputSize) {
            isFinal = TRUE;
            printf("\nFinal chunk set.\n");
        }

        printf("\n Now calling decryption routine...");
        if (!CryptDecrypt(hKey, NULL, isFinal, 0, chunk, &out_len)) {
            printf("[-] CryptDecrypt failed\n");
            break;
        }
        printf("CryptDecrypt succeeded.");

        DWORD written = 0;
        if (!WriteFile(hOutFile, chunk, out_len, &written, NULL)) {
            printf("writing failed!\n");
            break;
        }
        memset(chunk, 0, chunk_size);

    }
    CryptReleaseContext(hCryptProv, 0);
    CryptDestroyKey(hKey);
    CryptDestroyHash(hHash);

    CloseHandle(hInpFile);
    CloseHandle(hOutFile);
    printf("Finished. Processed %#x bytes.\n", readTotalSize);
    system("pause");
    return 0;
}

这只是告诉我 CryptDecrypt 失败了。所以我猜密钥没有以正确的格式指定。我不知道如何使用十六进制格式的 AES-256 密钥来解密数据。密钥目前在程序中以 base64 格式硬编码,但我猜这是不正确的。

我做的另一件事是我使用了CryptoTester tool to specify the AES key that I have in hex format and it is able to actually successfully decrypt the file. Also, this online decryption tool is also able to use the key to decrypt the data as shown here。所以我知道我有正确的十六进制密钥和所有内容并且文件可以解密,但是我如何重写上面的程序才能正确解密文件?

请注意,此处使用或显示的所有键都只是示例。

如何更正此程序以使用上面的 AES-256 密钥成功解密数据?

简单的演示程序

这是一个小的 C 程序,它使用您的密钥和随附的加密数据再次解密原始文本。我试着让它变得简约。

为了简单起见,它没有从文件系统中读取文件,而是将C程序中的数据定义为十六进制字符串。

结果

当你运行这个程序时,控制台输出如下:

decrypted result: 'just a test'

C代码

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

void error(const char* what) {
    fprintf(stderr, "%s failed with last error 0x%x\n", what, GetLastError());
    exit(1);
}

#define AES_KEY_SIZE 32
typedef struct {
    BLOBHEADER hdr;
    DWORD dwKeySize;
    BYTE rgbKeyData[AES_KEY_SIZE];
} AES256KEYBLOB;

BYTE *hex2byte(const char *hex) {
    int len = strlen(hex) / 2;
    BYTE* bytes = malloc(len);
    if (bytes == NULL) { 
        error("malloc");  
        return NULL; 
    }
    unsigned char val[2];

    for (int i = 0; i < len; i++) {
        sscanf_s(&hex[i * 2], "%2hhx", &val);
        bytes[i] = val[0];
    }
    return bytes;
}

int main() {
    BYTE *key = hex2byte("3F10E23BB1A5DFD9C8CA06195E43043386A9BA4C63C35AC518F463BA768F001B");

    AES256KEYBLOB aes256KeyBlob;
    aes256KeyBlob.hdr.bType = PLAINTEXTKEYBLOB;
    aes256KeyBlob.hdr.bVersion = CUR_BLOB_VERSION;
    aes256KeyBlob.hdr.reserved = 0;
    aes256KeyBlob.hdr.aiKeyAlg = CALG_AES_256;
    aes256KeyBlob.dwKeySize = AES_KEY_SIZE;
    memcpy(aes256KeyBlob.rgbKeyData, key, AES_KEY_SIZE);

    HCRYPTPROV hProv;
    if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
        error("CryptAcquireContext");
    }

    HCRYPTKEY hKey;
    if (!CryptImportKey(hProv, (BYTE*)& aes256KeyBlob, sizeof(AES256KEYBLOB), 0, CRYPT_EXPORTABLE, &hKey)) {
        CryptReleaseContext(hProv, 0);
        error("CryptImportKey");
    }

    const char *encodedHex = "C8B59251225375A13480ECAA371C6CBE";
    DWORD numBytes = strlen(encodedHex) / 2;
    BYTE *encoded = hex2byte(encodedHex);

    if (CryptDecrypt(hKey, 0, TRUE, 0, encoded, &numBytes)) {
        printf("decrypted result: '");
        for (DWORD i = 0; i < numBytes; i++) {
            printf("%c", encoded[i]);
        }
        printf("'\n");
    } else {
        CryptDestroyKey(hKey);
        CryptReleaseContext(hProv, 0);
        error("CryptDecrypt");
    }


    free(key);
    free(encoded);

    CryptDestroyKey(hKey); 
    CryptReleaseContext(hProv, 0);
    return 0;
}

微软文档

此处记录了 KEYBLOB 结构: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/jj650836(v%3Dvs.85)

而在这里您将找到有关 BLOBHEADER 结构的信息: https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/ns-wincrypt-publickeystruc