在 OpenSUSE 13.2 上使用 memcpy 解密时出现乱码

Garbage characters during decryption with memcpy on OpenSUSE 13.2

我有一个程序在 SUSE Linux Enterprise Server 下运行了一段时间,运行良好。最近转移到OpenSUSE 13.2系统上,遇到了问题。该程序与第三方接口,数据被接收到我们的程序中,其中数据块由一些 header 信息和 AES 加密数据组成。使用 OpenSSL libcrypto 库,我们成功地连接到 SLES 下的这个系统。但是,在 OpenSUSE 下,我们始终会看到解密数据末尾包含垃圾的错误。我已经确定了问题发生的位置并进行了解决,但是在查看代码时,我不明白为什么会出现问题。

我已经创建了一个模拟问题的测试程序。测试程序在 SUSE Linux Enterprise Server 11 和 Red Hat 7.2 Enterprise Linux 下运行良好,但在使用不同版本级别的 OpenSSL 库的 OpenSUSE 13.2 上失败。在 SLES 和 Red Hat 下,解密后的数据被干净地返回。在 OpenSUSE 下,除了一些出现在数据块末尾的垃圾外,大部分数据都被干净地解密了。返回的数据块是正确的,然后包含一些垃圾,然后正确结束。我的示例程序的代码如下,但导致问题的行是 memcpy(),我将加密数据移到数据块的前面进行处理。我的示例程序中的行如下:

   // Generates Garbage
   memcpy(encbuf, encbuf+100, enclen);                 

如果我在将加密数据移动到 encbuf 的开头之前将其移动到临时缓冲区,则不会生成垃圾。

   // This does not generate garbage
   memcpy(tmpbuf, encbuf+100, enclen);                 
   memcpy(encbuf, tmpbuf, enclen);                 

我的示例程序采用定义的明文缓冲区,使用密钥和 IV 对其进行加密,然后将其解密并显示结果。精简代码如下:

#include <stdio.h>      
#include <stdlib.h>     
#include <string.h>     
#include <unistd.h>     
#include <time.h>       
#include <fcntl.h>      
#include <sys/types.h>  

#include <openssl/evp.h>

#define EVP_DECRYPT   0 
#define EVP_ENCRYPT   1

char clrbuf[100000];
char encbuf[100000];
char tmpbuf[100000];
int clrlen;         
int enclen;         

char enckey[1024];      
unsigned char enciv[16];

main()
{
   int rc;

   // Set clear text to 50 lines of text
   sprintf(clrbuf,                                                   
         "0001this is a test this is a test this is a test this is a test\n" \
         "0002this is a test this is a test this is a test this is a test\n" \
         "0003this is a test this is a test this is a test this is a test\n" \
         // etc etc etc……………….
         "0048this is a test this is a test this is a test this is a test\n" \
         "0049this is a test this is a test this is a test this is a test\n" \
         "0050this is a test this is a test this is a test this is a test\n"  

   sprintf(enckey, "this is the key this is the key ");
   sprintf(enciv, "1234567890123456");

   // Encrypt the data and simulate a 100 byte header by returning encrypted data 100 bytes into the data block
   //
   memcpy(encbuf, "Some header stuff that will need to be removed", 46);
   rc = evp_aes256_cbc(clrbuf, strlen(clrbuf), encbuf+100, &enclen, enckey, enciv, EVP_ENCRYPT);                  

   // Now remove the header by shifting the encrypted data to the start of the data block and decrypt
   // This is where doing the memcpy() as coded in OpenSUSE results in garbage at the end of clrbuf
   // but everything is returned correctly in SLES and Red Hat
   //
   // This work fines on all OSes:
   //         memcpy(tmpbuf, encbuf+100, enclen);                 
   //         memcpy(encbuf, tmpbuf, enclen);                 

   memcpy(encbuf, encbuf+100, enclen);                 
   rc = evp_aes256_cbc(encbuf, enclen, clrbuf, &clrlen, enckey, enciv, EVP_DECRYPT);

   printf("Decrypt: rc=%d  EncLen=%d  ClrLen=%d\n", rc, enclen, clrlen);
   printf("Data:\n\n<\n%s\n>\n\n", clrbuf);                             
}

/****************************************************************************/  

evp_aes256_cbc(char *InBuf, int InLen, char *OutBuf, int OutLen, char *Key, char *IV, int EncryptFlag)              
{                                                                               
   EVP_CIPHER_CTX ctx;                                                          
   int padlen;                                                                  

   EVP_CIPHER_CTX_init(&ctx);                                                   

   if (! EVP_CipherInit_ex(&ctx, EVP_aes_256_cbc(), NULL, Key, IV, EncryptFlag))
      return(0);                                                                

   if (! EVP_CipherUpdate(&ctx, OutBuf, OutLen, InBuf, InLen))
      return(0);                                              

   if (! EVP_CipherFinal_ex(&ctx, OutBuf+(*OutLen), &padlen)) 
      return(0);                                              

   *OutLen = *OutLen + padlen;                                

   EVP_CIPHER_CTX_cleanup(&ctx);                              

   return(1);                                                 
}                                                             

在 SLES 和 Red Hat 上,最终输出如下:

0046this is a test this is a test this is a test this is a test
0047this is a test this is a test this is a test this is a test
0048this is a test this is a test this is a test this is a test
0049this is a test this is a test this is a test this is a test
0050this is a test this is a test this is a test this is a test

在 OpenSUSE 上,最终输出可能如下所示:

0046this is a test this is a test this is a test this is a test
0047this is a test this is a test this is a test this is a test
0048this is a test this is a tes╧┬S_úReÅ▌
|τZk╠½çP≥ii≡w╙p▓8ª'MêÅt▒g{Y¥ΩEô¬ ⌡n}⌐*╘¿µ2└╠LS4=Qüü├;~╕Ç<╗^¿ßD0┤T.OQΣq#≈
0050this is a test this is a test this is a test this is a test

有什么想法吗?

谢谢

通过其他来源提供的答案:

由于内存重叠,应该使用 memmove() 而不是 memcpy()。

// Generates Garbage
memcpy(encbuf, encbuf+100, enclen);

重叠缓冲区和 memcpy 在 C/C++ 中是未定义的行为。请改用 memmove。听起来你有一个可以向前或向后 memcpy 的 glibc 版本。在您的情况下,您正在通过 HAS_FAST_COPY_BACKWARD.

的处理器上执行

这是该问题的经典错误报告:Strange sound on mp3 flash website。但是 Adob​​e 的任何东西都不会让您感到惊讶。他们以地球上最不安全的软件而闻名是有原因的。

此外,如果您 运行 您的二进制文件在 Valgrind 下,该工具有时会标记有问题的代码。我记得看到 Valgrind 将其标记为 Crypto++ library:

size_t ArraySink::Put2(const byte *begin, size_t length, int messageEnd, bool blocking)
{
    // Avoid passing NULL pointer to memcpy. Using memmove due to
    //  Valgrind finding on overlapping buffers.
    size_t copied = 0;
    if (m_buf && begin)
    {
        copied = STDMIN(length, SaturatingSubtract(m_size, m_total));
        memmove(m_buf+m_total, begin, copied);
    }
    m_total += copied;
    return length - copied;
}