【问题标题】:Wrong ciphertext length in c libgcryptc libgcrypt中的密文长度错误
【发布时间】:2020-12-09 01:50:46
【问题描述】:

所以,我正在尝试加密和解密字符串,在 arch 上使用 libgcrypt 库(版本 1.8.7),此时我尝试了 2 种模式:CBC 和 GCM(不确定 GCM,所以让我们解决 CBC首先),但出现了同样的问题。

我填充了字符串,然后逐块对其进行加密。有时,顺便说一句,这会发生混乱,gcry_cipher_encrypt 函数返回错误的字节数(5、7、11...),但如果我理解正确,输出应该是 16 字节(128 位)。解密也会发生同样的事情,我以完全相同的方式逐块进行。我在整个加密或解密过程中使用相同的 GCRY 处理程序,感觉就像我真的错过了一些东西......这是一个示例,仅在 CBC 模式下加密,以便更容易找到问题。

代码:

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

// Define cipher details
#define GCRY_CIPHER GCRY_CIPHER_AES256
#define GCRY_C_MODE GCRY_CIPHER_MODE_CBC


char * encrypt_block(gcry_cipher_hd_t handler, unsigned char * key, unsigned char * input) {
    size_t key_length = 32;
    size_t blk_length = 16;

    // Encryption result variable
    unsigned char * enc = (char *) calloc(16, sizeof(char));

    // Error variable
    gcry_error_t err = 0;

    // Set key
    err = gcry_cipher_setkey(handler, key, key_length);

    if (err) {
        printf("Couldn't set the key!\n%s\n%s\n", gcry_strsource(err), gcry_strerror(err));
        exit(-1);
    }

    // Start encryption process
    err = gcry_cipher_encrypt(handler, enc, blk_length, input, blk_length);
    
    if (err) {
        printf("Couldn't encrypt!\n%s\n%s\n", gcry_strsource(err), gcry_strerror(err));
        exit(-1); 
    }

    if (strlen(enc) != 16) {
        printf("\n\nCORRUPTED BLOCK!\n\n");
    }

    // Printing the block result
    printf("\nENC BLOCK:\t%d\t", strlen(enc));
    for (unsigned short int i = 0; i < strlen(enc); ++i) {
        printf("%X ", enc[i]);
    }
    printf("\n");

    return enc;
}


int main() {
    // Creating basic variables
    unsigned char * input = (char *) calloc(2048, sizeof(char));
    unsigned char * key   = (char *) calloc(32, sizeof(char));
    unsigned char * iv    = (char *) calloc(16, sizeof(char));

    // Taking user input
    printf("Input (2048 max): ");
    scanf(" %[^\n]", input);

    printf("Key (32 max): ");
    scanf(" %[^\n]", key);

    printf("RAW DATA:\n\tinput:  %d\t%s\n\tkey:    %d\t%s\n\n", strlen(input), input, strlen(key), key);

    // Create GCRY handler
    gcry_cipher_hd_t handler;
    gcry_error_t err = 0;

    // Initialize cipher handler
    err = gcry_cipher_open(&handler, GCRY_CIPHER, GCRY_C_MODE, 0);

    if (err) {
        printf("Couldn't initialize the cipher!\n%s\n%s\n", gcry_strsource(err), gcry_strerror(err));
        exit(-1);
    }

    // Add padding to the input
    if ((strlen(input) % 16) != 0) {
        for (unsigned short int i = 0; i < (((strlen(input) / 16) * 16) - strlen(input)); ++i) {
            strcat(input, "X");
        }
    }

    // Add padding to the key
    if (strlen(key) < 32) {
        for (unsigned short int i = strlen(key); i < 32; ++i) {
            key[i] = 0x0058;
        }
    }

    // Generate random IV
    char charset[] = "abcdefghijklmnopqrstuvwxyz0123456789";
    unsigned short int iv_size = 16;
    
    for (unsigned short int i = 0; i < iv_size; ++i) {
        unsigned short int index = rand() % (unsigned short int) (sizeof charset - 1);
        iv[i] = charset[index];
    }

    // Set the IV
    err = gcry_cipher_setiv(handler, iv, 16);

    if (err) {
        printf("Couldn't set the IV!\n%s\n%s\n", gcry_strsource(err), gcry_strerror(err));
        exit(-1);
    }

    printf("ENC DATA:\n\tinput:  %d\t%s\n\tkey:    %d\t%s\n\tiv:     %d\t%s\n\n", strlen(input), input, strlen(key), key, strlen(iv), iv);

    // Create encryption variables
    unsigned char * input_buffer = (char *) calloc(16, sizeof(char));
    unsigned char * enc_buffer   = (char *) calloc(16, sizeof(char));
    unsigned char * out          = (char *) calloc(strlen(input), sizeof(char));

    // Start encryption process block by block
    for (unsigned short int i = 0; i < (strlen(input) / 16); ++i) {
        // Create a new block
        for (unsigned short int j = 0; j < 16; ++j) {
            input_buffer[j] = input[(i * 16) + j];
        }
        printf("\nENC INPUT:\t%d\t%s\n", strlen(input_buffer), input_buffer);
        
        // Check if this is a final round
        if (i == ((strlen(input) / 16) - 1)) {
            err = gcry_cipher_final(handler);
        }
        
        // Start encrypting the block
        enc_buffer = encrypt_block(handler, key, input_buffer);

        // Adding up the block to the out result
        strcat(out, enc_buffer);

        memset(input_buffer, 0, 16);
        memset(enc_buffer, 0, 16);
    }

    // Print the encryption result
    printf("\n\nENC RESULT:\n\t%d\n\t", strlen(out));
    for (unsigned short int i = 0; i < strlen(out); ++i) {
        printf("%X ", out[i]);
    }
    printf("\n");

    gcry_cipher_close(handler);
}

输出:

Input (2048 max): This string is made for testing the program
Key (32 max): hey my password
RAW DATA:
    input:  43  This string is made for testing the program
    key:    15  hey my password

ENC DATA:
    input:  48  This string is made for testing the programXXXXX
    key:    32  hey my passwordXXXXXXXXXXXXXXXXX
    iv:     16  t8jhfhkm7bo5ohxw


ENC INPUT:  16  This string is m

ENC BLOCK:  16  2 BF AA A0 1 7C A8 77 DA 4A 5A 72 29 EB FA F6 

ENC INPUT:  16  ade for testing 

ENC BLOCK:  16  41 BA CE 61 8A E3 F4 89 8A 46 50 2 47 5 11 A4 

ENC INPUT:  16  the programXXXXX


CORRUPTED BLOCK!


ENC BLOCK:  12  AE D6 92 D2 5A AF 85 CB 57 2 1B 93 


ENC RESULT:
    44
    2 BF AA A0 1 7C A8 77 DA 4A 5A 72 29 EB FA F6 41 BA CE 61 8A E3 F4 89 8A 46 50 2 47 5 11 A4 AE D6 92 D2 5A AF 85 CB 57 2 1B 93 

我真的很抱歉这个烂摊子,只是我在这一点上发疯了,看起来解决方案很简单,但我就是不明白。

【问题讨论】:

    标签: c encryption aes libgcrypt


    【解决方案1】:

    strlen 不会告诉您有关输出缓冲区长度的任何信息。实际上,您的输出缓冲区始终是相同的长度。无需测试它的长度,因为 libgcrypt 无法修改它的长度。

    如果您想了解strlen 为何返回“混乱”值,您需要了解strlen 的用途。 strlen 旨在对 C 样式(以空结尾)字符串进行操作,而不是对任意字节。 C 中的字符串存储为以字符结尾的字符数组'\0' (0x00) 字符。这是空终止符。这就是确定 C 字符串长度的方法。

    // example implementation to explicate the concept
    size_t strlen(const char *s) {
        size_t i = 0;
        while (s[i] != '\0')
            ++i;
        return i;
    }
    

    当您将strlen 应用于任意字节时,结果是荒谬的。您的二进制密文完全有可能包含字节 0x00anywhere。它可能出现在开头或中间的任何地方。它可能会出现好几次。或者它永远不会出现,在这种情况下你会得到一个致命的分段错误。无论0x00 碰巧首先出现在您的密文中,那将是strlen 假设它结束的地方。这种行为看起来很“混乱”,因为加密会产生看似随机的数据,所以0x00 在该数据中的分布也是看似随机的。

    PS:您无需在每次加密块时都重置密钥。

    【讨论】:

    • 完全忘记了...谢谢!
    • @y0u4r3w3 我还建议使用 pkcs#7 约定而不是 Xs 进行填充。如果你的字符串实际上以 'X' 结尾,你就会遇到问题。
    • 当然,这只是示例的快速操作
    猜你喜欢
    • 1970-01-01
    • 2020-11-03
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 2013-01-11
    • 2013-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多