【问题标题】:DES CBC mode not outputting correctlyDES CBC 模式输出不正确
【发布时间】:2022-01-09 13:05:51
【问题描述】:

我正在用 C 编写一个项目,以在带有 OpenSSL 的 DES 框架代码之上实现 CBC 模式。我们不允许使用自动执行 CBC 模式的函数,因为我们必须自己实现它。我得到了输出,但我有结果文件,我的输出与预期的结果不完全匹配。我还一直在弄清楚如何填充文件以确保所有块的大小相同,这可能是我没有收到正确输出的原因之一。任何帮助,将不胜感激。到目前为止,这是我对骨架代码的修改:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/des.h>
#include <sys/time.h>
#include <unistd.h>

#define ENC 1
#define DEC 0

DES_key_schedule key;

int  append(char*s, size_t size, char c) {
    if(strlen(s) + 1 >= size) {
        return 1;
    }
    int len = strlen(s);
    s[len] = c;
    s[len+1] = '\0';
    return 0;
}

int getSize (char * s) {
    char * t;
    for (t = s; *t != '\0'; t++)
        ;
    return t - s;
}

void strToHex(const_DES_cblock input, unsigned char *output) {
    int arSize = 8;
    unsigned int byte;
    for(int i=0; i<arSize; i++) {
        if(sscanf(input, "%2x", &byte) != 1) {
            break;
        }
        output[i] = byte;
        input += 2;
    }

}

void doBitwiseXor(DES_LONG *xorValue, DES_LONG* data, const_DES_cblock roundOutput) {
    DES_LONG temp[2];
    memcpy(temp, roundOutput, 8*sizeof(unsigned char));
    for(int i=0; i<2; i++) {
        xorValue[i] = temp[i] ^ data[i];
    }
}

void doCBCenc(DES_LONG *data, const_DES_cblock roundOutput, FILE *outFile) {

    DES_LONG in[2];
    doBitwiseXor(in, data, roundOutput);

    DES_encrypt1(in,&key,ENC);

    printf("ENCRYPTED\n");
    printvalueOfDES_LONG(in);

    printf("%s","\n");
    fwrite(in, 8, 1, outFile);

    memcpy(roundOutput, in, 2*sizeof(DES_LONG));
}

int main(int argc, char** argv)
{
        const_DES_cblock cbc_key = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};
        const_DES_cblock IV = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};

        // Initialize the timing function
        struct timeval start, end;
        gettimeofday(&start, NULL);

        int l;
        if ((l = DES_set_key_checked(&cbc_key,&key)) != 0)
            printf("\nkey error\n");

        FILE *inpFile;
        FILE *outFile;
        inpFile = fopen("test.txt", "r");
        outFile = fopen("test_results.txt", "wb");

        if(inpFile && outFile) {
        unsigned char ch;

        // A char array that will hold all 8 ch values.
        // each ch value is appended to this.
        unsigned char eight_bits[8];

        // counter for the loop that ensures that only 8 chars are done at a time.
        int count = 0;

        while(!feof(inpFile)) {
                // read in a character
                ch = fgetc(inpFile);

                // print the character
                printf("%c",ch);

                // append the character to eight_bits
                append(eight_bits,1,ch);

                // increment the count so that we only go to 8.
                count++;
                
                const_DES_cblock roundOutput;
                // When count gets to 8
                if(count == 8) {
                    // for formatting
                    printf("%s","\n");

                    // Encrypt the eight characters and store them back in the char array.
                    //DES_encrypt1(eight_bits,&key,ENC);
                    doCBCenc(eight_bits, roundOutput, outFile);

                    // prints out the encrypted string
                    int k;
                    for(k = 0; k < getSize(eight_bits); k++){
                        printf("%c", eight_bits[k]);

                    }

                    // Sets count back to 0 so that we can do another 8 characters.
                    count = 0;

                    // so we just do the first 8. When everything works REMOVE THE BREAK.
                    //break;
                }
            }

        } else {
            printf("Error in opening file\n");
        }

        fclose(inpFile);
        fclose(outFile);

         // End the timing
        gettimeofday(&end, NULL);
 
        // Initialize seconds and micros to hold values for the time output
        long seconds = (end.tv_sec - start.tv_sec);
        long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
 
        // Output the time
        printf("The elapsed time is %d seconds and %d microseconds\n", seconds, micros);

    }


【问题讨论】:

    标签: c encryption cryptography des cbc-mode


    【解决方案1】:

    你的加密至少有一半是正确的,但你还有很多实际或潜在的其他错误。

    如您所见,原始 CBC 模式只能加密块大小的倍数,DES 64 位或 8 字节(在大多数现代计算机上以及所有可以使用 OpenSSL 的地方)。在某些应用程序中,这是可以的;例如,如果数据(总是)是 MD5 或 SHA-256 或 SHA-512 哈希、GUID 或 IPv6(二进制)地址,则它是块倍数。但是大多数应用程序希望至少处理任何长度的字节,因此他们需要使用某种方案来填充加密并在解密最后一个块时取消填充(最后一个块之前的所有块都已经具有正确的大小)。为此开发了许多不同的方案,因此您需要知道使用哪个方案。我认为这是一项学校作业(因为没有真正的客户会设置如此愚蠢和浪费的要求组合),这应该已经指定或明确留作选择。今天非常常见的一种填充方案(尽管不是用于单 DES,因为它已损坏、不安全、过时且常见)是由 PKCS5 定义并由 PKCS7 推广的一种填充方案,称为PKCS5, PKCS7, or PKCS5/7 padding ,所以我以它为例。

    除此之外:

    • 您尝试在执行fgetc(inpFile) 之前测试feof(inpFile)。这在 C 中不起作用。它会导致您的代码将 EOF 的低 8 位(实际上在所有实现中为 255 aka 0xFF)视为添加到文件中实际字符的有效数据字符。常见的习惯用法是将getchar/getc/fgetc 的返回值存储在已签名 int 中并与EOF 进行比较,但这需要更多更改,因此我使用了替代方法。

    • 你没有初始化 eight_bits 这是一个局部范围的自动持续时间变量,所以它的内容是未定义的,并且取决于实现通常是垃圾,这意味着试图通过使用 strlen() 来“附加”到它寻找结尾将无法正常工作,甚至可能崩溃。尽管在某些实现中至少有时它可能碰巧包含零字节,并且“工作”。此外,在 C 中,从文件中读取(并存储在此处)的字节可能是 \0,这也会使这项工作出错,尽管 if 该文件包含文本,正如其名称所暗示的那样,它可能不包含任何\0字节。

    • 一旦你填写了eight_bits,你就将'off-the-end'写入不存在的元素[8]。从技术上讲,这是未定义的行为,任何事情都可能发生,传统上在 Usenet 上表示为nasal demons。另外,在main 完成第一个块后,它不会更改eight_bits 中的任何内容,因此所有对append 的进一步调用都会发现它已满并丢弃新字符。

    • 虽然您可以单独解决上述问题,但可以使用一个非常简单的解决方案:您已经在使用count 来计算当前块中的字节数,因此只需将其用作下标。

    • roundOutput 也是循环中未初始化的局部/自动变量,然后用作 CBC 步骤的前一个块,可能带有垃圾或错误值。而且您根本不需要使用 IV。您应该在循环之前分配它(因此它在所有迭代中都保留其值)并将其初始化为 IV,然后对于循环中的每个块,您的 doCBCenc 可以正确地将其异或到新块,然后留下加密的新块块下次使用。

    • 标记为“打印出加密字符串”的代码打印的是 plaintext 而不是 ciphertext -- 这是二进制的,无论如何都不应该直接打印 -- 并且不需要因为您的文件读取循环已经回显了每个读取的字符。但是,如果您确实想打印一个(有效的以 null 结尾的)字符串,则使用 fputs(s)[f]printf([f,]"%s",s) 甚至 fwrite(s,1,strlen(s),f) 会更容易。

    • 您的doCBCenc 有一个对printvalueofDES_LONG 的引用,它没有在任何地方定义,并且显然不需要它以及两个周围的printf

    • 你应该使用强制转换将第一个参数转换为doCBCenc——这不是严格要求的,但它是一种很好的风格,如果你不这样做,一个好的编译器(比如我的)会抱怨

    • 最后,当发生错误时,您通常会打印一条消息然后继续运行,这将永远无法正常工作,并且可能会产生掩盖问题并使其难以修复的症状。

    下面的代码修复了上面的问题,除了最后一个(这将是更多的工作,但收益更少)加上我删除了现在多余的例程,以及愚蠢的计时代码:Unix 已经有内置工具来测量和显示处理时间比编写代码更容易。我“删除”的代码在#if 0 下供参考,我在#else#if 1 下添加的代码除了演员表。 PKCS5/7 填充的逻辑在#if MAYBE 下,因此可以选择或不选择。有些人认为使用 sizeof(DES_block) 或定义宏而不是魔术 8 会更好,但我没有打扰 - 特别是因为它需要进行并非真正必要的更改。

    // SO70209636
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <openssl/des.h>
    #include <sys/time.h>
    #include <unistd.h>
    
    #define ENC 1
    #define DEC 0
    
    DES_key_schedule key;
    
    #if 0
    int  append(char*s, size_t size, char c) {
        if(strlen(s) + 1 >= size) {
            return 1;
        }
        int len = strlen(s);
        s[len] = c;
        s[len+1] = '\0';
        return 0;
    }
    
    int getSize (char * s) {
        char * t;
        for (t = s; *t != '\0'; t++)
            ;
        return t - s;
    }
    
    void strToHex(const_DES_cblock input, unsigned char *output) {
        int arSize = 8;
        unsigned int byte;
        for(int i=0; i<arSize; i++) {
            if(sscanf(input, "%2x", &byte) != 1) {
                break;
            }
            output[i] = byte;
            input += 2;
        }
    
    }
    #endif
    
    void doBitwiseXor(DES_LONG *xorValue, DES_LONG* data, const_DES_cblock roundOutput) {
        DES_LONG temp[2];
        memcpy(temp, roundOutput, 8*sizeof(unsigned char));
        for(int i=0; i<2; i++) {
            xorValue[i] = temp[i] ^ data[i];
        }
    }
    
    void doCBCenc(DES_LONG *data, const_DES_cblock roundOutput, FILE *outFile) {
    
        DES_LONG in[2];
        doBitwiseXor(in, data, roundOutput);
    
        DES_encrypt1(in,&key,ENC);
    #if 0
        printf("ENCRYPTED\n");
        printvalueOfDES_LONG(in);
        printf("%s","\n");
    #endif
        fwrite(in, 8, 1, outFile);
    
        memcpy(roundOutput, in, 2*sizeof(DES_LONG));
    }
    
    int main(int argc, char** argv)
    {
            const_DES_cblock cbc_key = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};
            const_DES_cblock IV = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};
    #if 0
            // Initialize the timing function
            struct timeval start, end;
            gettimeofday(&start, NULL);
    #endif
            int l;
            if ((l = DES_set_key_checked(&cbc_key,&key)) != 0)
                printf("\nkey error\n");
    #if 1
            DES_cblock roundOutput; // must be outside the loop
            memcpy (roundOutput, IV, 8); // and initialized
    #endif
    
            FILE *inpFile;
            FILE *outFile;
            inpFile = fopen("test.txt", "r");
            outFile = fopen("test.encrypt", "wb");
    
            if(inpFile && outFile) {
            unsigned char ch;
    
            // A char array that will hold all 8 ch values.
            // each ch value is appended to this.
            unsigned char eight_bits[8];
    
            // counter for the loop that ensures that only 8 chars are done at a time.
            int count = 0;
    
    #if 0
            while(!feof(inpFile)) {
                    // read in a character
                    ch = fgetc(inpFile);
    #else
            while( ch = fgetc(inpFile), !feof(inpFile) ){
    #endif
                    // print the character
                    printf("%c",ch);
    #if 0
                    // append the character to eight_bits
                    append(eight_bits,1,ch);
                    // increment the count so that we only go to 8.
                    count++;
    #else
                    eight_bits[count++] = ch;
    #endif
                    
    #if 0
                    const_DES_cblock roundOutput;
    #endif
                    // When count gets to 8
                    if(count == 8) {
                        // for formatting
                        printf("%s","\n");
    
                        // Encrypt the eight characters and store them back in the char array.
                        //DES_encrypt1(eight_bits,&key,ENC);
                        doCBCenc((DES_LONG*)eight_bits, roundOutput, outFile);
    #if 0
                        // prints out the encrypted string
                        int k;
                        for(k = 0; k < getSize(eight_bits); k++){
                            printf("%c", eight_bits[k]);
    
                        }
    #endif
                        // Sets count back to 0 so that we can do another 8 characters.
                        count = 0;
    
                        // so we just do the first 8. When everything works REMOVE THE BREAK.
                        //break;
                    }
                }
    #if MAYBE
                memset (eight_bits+count, 8-count, 8-count); // PKCS5/7 padding
                doCBCenc((DES_LONG*)eight_bits, roundOutput, outFile);
    #endif
            } else {
                printf("Error in opening file\n");
            }
    
            fclose(inpFile);
            fclose(outFile);
    #if 0
             // End the timing
            gettimeofday(&end, NULL);
            // Initialize seconds and micros to hold values for the time output
            long seconds = (end.tv_sec - start.tv_sec);
            long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
            // Output the time
            printf("The elapsed time is %d seconds and %d microseconds\n", seconds, micros);
    #endif
    }
    

    PS:我个人不会将fwrite 放在doCBCenc 中;我只会做加密,让调用者做任何合适的 I/O,在某些情况下可能fwrite。但是对于你显然有的要求,你所拥有的并没有错。

    【讨论】:

    • 这是一个非常全面的答案,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-06
    • 2021-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多