在 fread/fwrite 期间去除 AES 填充

Strip AES padding during fread/fwrite

我正在使用 libgcrypt 来加密和解密文件。当我使用 fread 获取适当数量的字节时,我需要用 16-n 字节填充它以便它被 gcry_cipher_encrypt 正确加密。然而,解密后,空 bytes/padding 仍然存在。有什么办法可以读写16字节的块,还去掉最后的padding吗?

#include <stdio.h>
#include <gcrypt.h>

#define algo GCRY_CIPHER_AES128
#define mode GCRY_CIPHER_MODE_CBC
#define KEY_LENGTH 16
#define BLOCK_LENGTH 16

int main(){

    char IV[16];
    char *encBuffer = NULL;
    FILE *in, *out, *reopen;
    char *key = "A key goes here!";
    gcry_cipher_hd_t handle;
    int bufSize = 16, bytes;

    memset(IV, 0, 16);

    encBuffer = malloc(bufSize);

    in = fopen("in.txt", "r");
    out = fopen("out.txt", "w");

    gcry_cipher_open(&handle, algo, mode, 0);
    gcry_cipher_setkey(handle, key, KEY_LENGTH);
    gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);

    while(1){
        bytes = fread(encBuffer, 1, bufSize, in);
        if (!bytes) break;
        while(bytes < bufSize)
            encBuffer[bytes++] = 0x0;
        gcry_cipher_encrypt(handle, encBuffer, bufSize, NULL, 0);
        bytes = fwrite(encBuffer, 1, bufSize, out);
    }

    gcry_cipher_close(handle);
    fclose(in);
    fclose(out);

    gcry_cipher_open(&handle, algo, mode, 0);
    gcry_cipher_setkey(handle, key, KEY_LENGTH);
    gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);

    reopen = fopen("out.txt", "r");
    out = fopen("decoded.txt", "w");

    while(1){
        bytes = fread(encBuffer, 1, bufSize, reopen);
        if (!bytes) break;
        gcry_cipher_decrypt(handle, encBuffer, bufSize, NULL, 0);
        bytes = fwrite(encBuffer, 1, bufSize, out);
    }

    gcry_cipher_close(handle);

    free(encBuffer);

    return 0;
}

cry_cipher 似乎没有提到填充,但如果要加密的数据并不总是块大小的倍数,则填充是必要的。有点愚蠢的举动更不用说填充了,也许它总是 PKCS#7 填充。

如果有填充,通常的填充是PKCS#7(PKCS#5 是基本相同的东西)。 PKCS#7 总是至少向块大小的填充字节添加一个,填充值是数字或填充字节。解密时删除填充。

这意味着如果要加密的输入数据是块大小(AES 为 16 字节)的精确倍数,将添加一个填充块。所以加密输出中的 16 字节将是 32 字节。

这是我的评论,但我怀疑您需要改为调用 gcry_cipher_encrypt(handle, encBuffer, bytes, NULL, 0);fread returns 它从文件中读取的字节数。如果你不断地用 bufSize 字节加密,那么当你到达循环中的文件末尾时,将会有额外的字节被提供给加密函数,这些字节不是文件的一部分,除非文件是16 字节的完美倍数。加密库不知道什么是文件数据,什么不是,它会正确地加密和解密 encBuffer 缓冲区中末尾的额外 NULL。我的猜测是用户不必担心填充自己的数据,您使用的库应该在后台处理这些问题。

快速检查是用 0x20 初始化 encBuffer,然后查看您的解密文件末尾是否有额外空格

分组密码有多种操作模式。有些要求输入数据长度是块大小的倍数,因此本质上需要明文填充;有些没有。查看更多相关信息 here

如果必须使用需要填充的模式,则必须将明文长度与加密数据一起保存。最简单的方法是最后将它写入一个附加块(也加密该块!)。还有其他更复杂的方案,并不总是需要添加一个块;参见 this

我能够通过正确存储填充量然后稍后检查它来修复它,zaph. I used PKCS#7 建议以确定要写入的字节数和类型。我可以写 NULL 字节,但不会有任何区别,所以最好还是坚持标准。

#include <stdio.h>
#include <gcrypt.h>

#define algo GCRY_CIPHER_AES128
#define mode GCRY_CIPHER_MODE_CBC
#define KEY_LENGTH 16
#define BLOCK_LENGTH 16

int main(){

    char IV[16], *encBuffer = NULL;
    char *key = "A key goes here!";
    int bufSize = 16, bytes, i=0, padding;

    FILE *in, *out, *reopen;
    gcry_cipher_hd_t handle;

    memset(IV, 0, 16);
    encBuffer = malloc(bufSize);

    // Open in/out for reading and writing
    in = fopen("in.txt", "r");
    out = fopen("out.txt", "w");

    // Set handle for encryption
    gcry_cipher_open(&handle, algo, mode, 0);
    gcry_cipher_setkey(handle, key, KEY_LENGTH);
    gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);

    // Read from in, write encrypted to out
    while(1){
        bytes = fread(encBuffer, 1, bufSize, in);
        if (!bytes) break;

        // If fread grabbed less than 16 bytes, that's our final line
        // Use the byte number for padding and pad N bytes of N
        if ( bytes < BLOCK_LENGTH ){ padding = 16-bytes; }

        while(bytes < bufSize)
            encBuffer[bytes++] = padding;
        gcry_cipher_encrypt(handle, encBuffer, bytes, NULL, 0);
        bytes = fwrite(encBuffer, 1, bufSize, out);
    }

    // Close handle and i/o files
    gcry_cipher_close(handle);
    fclose(in);
    fclose(out);

    // Set handle for decryption
    gcry_cipher_open(&handle, algo, mode, 0);
    gcry_cipher_setkey(handle, key, KEY_LENGTH);
    gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);

    // Reopen outfile, open decoded file
    reopen = fopen("out.txt", "r");
    out = fopen("decoded.txt", "w");

    //Loop until EOF
    while(1){
        i=0;
        bytes = fread(encBuffer, 1, bufSize, reopen);
        if (!bytes) break;
        gcry_cipher_decrypt(handle, encBuffer, bufSize, NULL, 0);
        // Read each block and check for padding
        while ( i++ < BLOCK_LENGTH ){
            // If padding is found write 16-padding bytes
            if ( encBuffer[i] == padding ){
                bytes = fwrite(encBuffer, 1, (16-padding), out);
                return 0;
            }
        }
        // If padding isn't found, write the whole buffer
        bytes = fwrite(encBuffer, 1, bufSize, out);
    }

    // Close the handle and free the buffer
    gcry_cipher_close(handle);
    free(encBuffer);

    return 0;
}

当您写入文件并到达最后一个块时,就像您的答案一样,将其填充到完整的 16 个字节。您缺少的是,如果文件恰好以完整的 16 字节块结尾,则添加 另一个 完整的填充块——填充大小为 16——值的 16 字节连续16个。

现在当你读文件的时候,读到最后。最后一个块将是完整的 16 个字节。只看最后一个字节。无论最后一个字节是什么值,这就是您的填充。从最后一个块的末尾删除那么多字节。如果值为 16,则删除整个最后一个块。

如果不添加完整的 16 字节填充,您将永远不知道文件末尾的最后三个字节 3, 3, 3 是填充,还是重要数据。它看起来完全一样。