DES CBC 模式输出不正确

DES CBC mode not outputting correctly

我正在用 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] = '[=10=]';
    return 0;
}

int getSize (char * s) {
    char * t;
    for (t = s; *t != '[=10=]'; 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);

    }


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

如您所见,原始 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 也称为 0xFF)视为添加到文件中实际字符的有效数据字符。常见的习惯用法是将 getchar/getc/fgetc 的 return 存储在 有符号 int 中并与 EOF 进行比较,但这需要更多更改,所以我使用了替代方法。

  • 你没有初始化 eight_bits 这是一个局部范围的自动持续时间变量,所以它的内容是未定义的并且取决于实现通常是垃圾,这意味着试图 'append' 通过使用 strlen() 来查找结尾将无法正常工作,甚至可能会崩溃。尽管在某些实现中至少有时它可能碰巧包含零字节和 'work'。此外,在 C 中,从文件中读取(并存储在这里)的字节可能是 [=18=] 这也会使这项工作出错,尽管 if 该文件包含文本,顾名思义,它 可能 不包含任何 [=18=] 字节。

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

  • 虽然您可以单独修复以上几点,但有一个更简单的解决方案:您已经在使用 count 来计算当前块中的字节数,因此只需使用它作为下标。

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

  • 您标记为 'prints out the encrypted string' 的代码打印 plaintext 而不是 ciphertext -- 这是二进制的,应该'无论如何都不会直接打印——而且不需要,因为你的文件读取循环已经回显了每个读取的字符。但是如果你确实想打印一个(有效的以 null 结尾的)字符串,那么使用 fputs(s)[f]printf([f,]"%s",s) 甚至 fwrite(s,1,strlen(s),f).

    会更容易
  • 你的 doCBCenc 引用了 printvalueofDES_LONG ,它没有在任何地方定义,并且显然不需要它和周围的两个 printf

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

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

下面的代码修复了上面的问题,除了最后一个(这本来可以做更多的工作但收益更少)加上我删除了现在多余的例程,以及愚蠢的计时代码:Unix 已经有内置工具来测量并且比编写代码更容易 可靠地显示处理时间。我的代码'removed'在#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] = '[=10=]';
    return 0;
}

int getSize (char * s) {
    char * t;
    for (t = s; *t != '[=10=]'; 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。但是你所拥有的对于你显然拥有的要求来说并没有错。