我对以下代码(使用已知密钥解密文件)有问题:

#include <stdio.h>
#include <stdlib.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(&hex[i * 2], "%2hhx", &val);
        bytes[i] = val[0];
    }
    return bytes;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <input_file> <output_file>\n", argv[0]);
        return 1;
    }

    BYTE *key = hex2byte("c2ecf7a4f3d1620f18cd38379269948fcd3aaf4fdce6d50d310464ea823a");

    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");
    }

    DWORD numBytes = 0;
    if (!CryptEncrypt(hKey, 0, TRUE, 0, NULL, &numBytes, 0)) {
        error("CryptEncrypt");
    }

    FILE *input_file = fopen(argv[1], "rb");
    if (!input_file) {
        perror("Error opening input file");
        return 1;
    }

    FILE *output_file = fopen(argv[2], "wb");
    if (!output_file) {
        perror("Error opening output file");
        fclose(input_file);
        return 1;
    }

    BYTE blk[numBytes];
    while (fread(blk, 1, numBytes, input_file) == numBytes) {

        if (!CryptDecrypt(hKey, 0, TRUE, 0, blk, &numBytes)) {
            //error("CryptDecrypt");
        }

        fwrite(blk, 1, numBytes, output_file);
    }

    free(key);
    CryptDestroyKey(hKey);
    CryptReleaseContext(hProv, 0);
    fclose(input_file);
    fclose(output_file);

    return 0;
}

这段代码可以工作,但我必须注释错误判断行:

//error("CryptDecrypt");

我try 按照其他堆栈溢出解决方案中的建议增加缓冲区大小,但无济于事. 我不太关心,因为代码可以工作,但我希望了解其中的原因以及如何避免它. 请注意,如果我使用:

CryptDecrypt(hKey, 0, FALSE, 0, blk, &numBytes)

我没有得到错误,但它只对第一个块起作用.

请不要将此标记为副本,因为所有解决方案都不起作用.

推荐答案

由于加密代码没有发布,目前还不清楚密文是如何生成的.测试表明,只有在使用ECB模式的情况下(用CBC生成的密文解密失败),才能用张贴的代码进行解密.

要进行正确且无错误消息的解密,需要对发布的代码进行以下更改:

  • 当前代码使用默认模式,即带有Zero-IV(16 0x00字节)的CBC.因此,需要明确切换到欧洲央行模式:

    // Set ECB mode
    DWORD dwMode = CRYPT_MODE_ECB;
    if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE*)&dwMode, 0)) {
        error("CryptSetKeyParam");
    }
    
  • 此外,密文的大小被错误地确定.这是因为CryptEncrypt()用于确定,这实际上意在确定加密期间的密文大小(由于填充,密文通常比明文大),而不是用于解密. 密文大小可以通过文件大小来确定,例如

    // Get ciphertext size
    fseek(input_file, 0, SEEK_END);
    DWORD ctSize = ftell(input_file);
    fseek(input_file, 0, SEEK_SET);
    

    调试发布的代码显示,由于错误使用CryptEncrypt(),numBytes对应于块大小(即16字节),从而使数据被逐个块地读取和解密.可能并不打算使用16字节的块大小,而是使用更大的块大小或读取整个文件.

  • 尽管文件是逐块读取和解密的,但所有块的最终标志都设置为TRUE.必须对此进行更改,以便将所有块的最终标志设置为FALSE,仅将最后一个块的最终标志设置为TRUE,例如,如以下代码所示:

    // Read chunks
    const DWORD chunkSize = 64; // multiple  of 16
    BYTE buffer[chunkSize];
    DWORD dataLen = chunkSize;
    DWORD read = 0;
    while (read < ctSize) {
        dataLen = fread(buffer, 1, chunkSize, input_file);
        read += dataLen;
        if (!CryptDecrypt(hKey, 0, !(read < ctSize) , 0, buffer, &dataLen)) { // final flag = FALSE except for the last chunk
            error("CryptDecrypt");
        }
        fwrite(buffer, 1, dataLen, output_file);
    }
    

这样,解密就可以正确执行,并且不会再显示错误消息.


为什么解密仍能与问题中发布的代码一起工作(错误消息除外)?毕竟,默认情况下使用的是零-IV的CBC模式,这实际上与ECB模式下生成的数据不兼容.

The reason is: As already stated above, the ciphertext is read and decrypted block by block in the posted code. The final flag is set to TRUE for all blocks. A TRUE sets the IV back to the initial state (i.e. to a Zero-IV). In addition, encrypting a single block in CBC mode with a Zero-IV results in the same ciphertext as encrypting the same block in ECB mode. For this reason, every block is successfully decrypted.
On the other hand, a TRUE for the final flag also means an unpadding is performed. This generally fails (as only the last block is padded), which leads to an error with the error code 0x80090005 (Error: Bad Data).
The overall result is an error message with otherwise successful decryption.

但是,如果您现在try 通过将所有块的final标志设置为FALSE来避免取消填充,则不再触发错误消息,但由于IV不再重置,因此密文被错误地解密.为了再次正确解密密文,必须将模式设置为ECB.为了确保对最后一个块进行解填充,最后一个块的final标志必须设置为TRUE.这反过来又导致了上面的解决方案(块大小为16).

C++相关问答推荐

在Windows上构建无聊的SSL x64

如何将已分配的数组(运行时已知的大小)放入 struct 中?

如何在Visual Studio代码中关闭此函数名称显示功能?

我可以在C中声明不同长度数组的数组而不带变量名吗?

以下声明和定义之间的区别

插座打开MacOS组件

如何在下面的C代码中正确管理内存?

如何在C中只对字符串(包含数字、单词等)中的数字进行重复操作?

OpenSSL:如何将吊销列表与SSL_CTX_LOAD_VERIFY_LOCATIONS一起使用?

在下面的C程序中,.Ap0是如何解释的?

共享目标代码似乎不能在Linux上的进程之间共享

意外的C并集结果

try 判断长整数是否为素数

按字典顺序打印具有给定字符的所有可能字符串

atoi函数最大长-长误差的再创造

既然我们在 if 中将 int 的值更改为 10,为什么在第二个 fork 后,子进程及其创建的子进程都会打印 33 ?

如何向 execl 创建的后台程序提供输入?

为什么实现文件中的自由函数默认没有内部链接?

OpenGL 中的非渐变 colored颜色 变化

计算 e^x 很好.但不是 x 的罪