2

我正在使用 libgcrypt 加密和解密文件。当我使用 fread 接收适当数量的字节时,我需要用 16-n 字节填充它,以便它正确地被gcry_cipher_encrypt. 然而,在解密后,空字节/填充仍然存在。有什么方法可以读取和写入 16 字节块并在最后剥离填充?

#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;
}
4

5 回答 5

1

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

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

于 2016-03-07T05:40:38.133 回答
1

我能够通过正确存储填充量然后稍后检查它来修复它,由zaph建议。我使用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;
}
于 2016-03-07T15:38:17.270 回答
0

cry_cipher 似乎没有提及填充,但如果要加密的数据并不总是块大小的倍数,则填充是必要的。一种 Bozo 移动更不用说填充,也许它总是 PKCS#7 填充。

如果有填充,通常的填充是PKCS#7(PKCS#5 是必不可少的)。PKCS#7 总是向填充的块大小字节添加至少 1,并且填充值是数量或填充字节。解密时,填充被删除。

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

于 2016-03-07T04:44:49.153 回答
0

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

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

于 2016-03-07T04:52:11.753 回答
0

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

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

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

于 2016-03-09T06:44:41.380 回答