C++/Qt AES-256-CBC/ECB 加密解密

代码基于Qt4+,后台是https。关于openssl https证书,Qt Network模块怎么使用https都暂时不讨论。重点看看OpenSSL模块的 AES-256-CBC/ECB加密解密。

我的想法是把数据交互过程中的json数据,用对称加密算法再加密一次。 防止openssl被破解后json就成了裸数据。

其实里面的坑很多,尤其是跟后台php/java进行json数据传输的时候,字符串编码方式什么的。也不是什么技术难题,很多时候就是

你不知道到底出错在那儿,调试了一两天。本人很少写博客,后续可以陆续写下安卓,iOS下的加密解密的情况,以及更服务器的json数据交互。

代码参考了下别人的c++代码(具体出处找不着了,若有不允许转载的情况,请私信本人,尊重原作者的工作)。转换成了Qt实现,结果没什么问题。

#include <openssl/aes.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>


#define AES_BLOCK_SIZE 16

const char *aes_encrypt_password=(const char *)"01234567890123456789012345678901";

const QString aes_encrypt_private_key_str="01234567890123456789012345678901";

static unsigned char key[EVP_MAX_KEY_LENGTH]={0}, iv[EVP_MAX_IV_LENGTH]={0};





第一种比较简单,ECB加密方式没CBC安全性高.

QString TestAES::aes_ecb_EncodeAES( const QString& password, const QString& data )
{

    AES_KEY aes_key;
    if(AES_set_encrypt_key((const unsigned char*)password.toLatin1().data(), password.length() * 8, &aes_key) < 0)
    {
        qDebug()<<"AES_set_encrypt_key failed";
        return "";
    }
    QString strRet;
    QString data_bak = data;
    int data_length = data_bak.length();
    int padding = 0;
    if (data_bak.length() % AES_BLOCK_SIZE > 0)
    {
        padding =  AES_BLOCK_SIZE - data_bak.length() % AES_BLOCK_SIZE;
    }
    data_length += padding;
    while (padding > 0)
    {
        data_bak += '\0';
        padding--;
    }
    for(int i = 0; i < data_length/AES_BLOCK_SIZE; i++)
    {
        QString str16 = data_bak.mid(i*AES_BLOCK_SIZE, AES_BLOCK_SIZE);
        unsigned char out[AES_BLOCK_SIZE];
        memset(out, 0, AES_BLOCK_SIZE);
        AES_encrypt((const unsigned char*)str16.toLatin1().data(), out, &aes_key);
        strRet += QString::fromLatin1((const char*)out,AES_BLOCK_SIZE);
    }
    return strRet;

}
QString TestAES::aes_ecb_DecodeAES( const QString& strPassword, QString& strData )
{
    AES_KEY aes_key;
    if(AES_set_decrypt_key((const unsigned char*)strPassword.toLatin1().data(), strPassword.length() * 8, &aes_key) < 0)
    {
        qDebug()<<"AES_set_decrypt_key failed";
        return "";
    }
    QString strRet;
    for(int i = 0; i < strData.length()/AES_BLOCK_SIZE; i++)
    {
        QString str16 = strData.mid(i*AES_BLOCK_SIZE, AES_BLOCK_SIZE);
        unsigned char out[AES_BLOCK_SIZE];
        memset(out, 0, AES_BLOCK_SIZE);
        AES_decrypt((const unsigned char*)str16.toLatin1().data(), out, &aes_key);
        strRet += QString::fromLatin1((const char*)out,AES_BLOCK_SIZE);
    }
    return strRet;
}





第二种就是常用的CBC模式


void TestAES::aes256_handleErrors(void)
{
    ERR_print_errors_fp(stderr);
    abort();
}

//初始化256位key 和向量IV.
int TestAES::aes256_aes_init(const char *keydata, unsigned int keydata_len, unsigned char *key, unsigned char *iv)
{
   // salt说白了就是一个随机数,salt与passwd串联,然后计算其hash值来防御dictionary attacks 和预计算的rainbow table 攻击。
   // 在openssl 的enc 命令中,通过salt 与passwd 来生加密(解密)密钥和初始向量IV。
  const unsigned char *salt = (const unsigned char *)"SALT201701";

  // openssl中调用 EVP_BytesToKey通过passwd和salt来生成Key 和IV
  if(!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, (unsigned char *) keydata, keydata_len, 5, key, iv))
  {
      fprintf(stderr, "EVP_BytesToKey failed\n");
      return 1;
  }
  return 0;
}

int TestAES::aes256_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,unsigned char *iv, unsigned char *ciphertext)
{
  EVP_CIPHER_CTX *ctx;

  int len;

  int ciphertext_len;

  // 创建和初始化上下文
  //该函数初始化一个EVP_CIPHER_CTX结构体,只有初始化后该结构体才能在下面介绍的函数中使用。操作成功返回1,否则返回0。
  if(!(ctx = EVP_CIPHER_CTX_new())){
    aes256_handleErrors();
  }

  // int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,ENGINE *impl, unsigned char *key, unsigned char *iv);
  // 该函数采用ENGINE参数impl的算法来设置并初始化加密结构体。其中,参数ctx必须在调用本函数之前已经进行了初始化。
  // 参数type通常通过函数类型来提供参数,如EVP_des_cbc函数的形式,即我们上一章中介绍的对称加密算法的类型。
  // 如果参数impl为NULL,那么就会使用缺省的实现算法。参数key是用来加密的对称密钥,iv参数是初始化向量(如果需要的话)。
  // 在算法中真正使用的密钥长度和初始化密钥长度是根据算法来决定的。在调用该函数进行初始化的时候,除了参数type之外,
  // 所有其它参数可以设置为NULL,留到以后调用其它函数的时候再提供,这时候参数type就设置为NULL就可以了。
  // 在缺省的加密参数不合适的时候,可以这样处理。操作成功返回1,否则返回0。
  // IV向量大多数情况下是跟block大小一样,AES这里是128位。
  if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)){
    aes256_handleErrors();
  }

  // int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,int *outl, unsigned char *in, int inl);
  // 该函数执行对数据的加密。该函数加密从参数in输入的长度为inl的数据,并将加密好的数据写入到参数out里面去。
  // 可以通过反复调用该函数来处理一个连续的数据块。写入到out的数据数量是由已经加密的数据的对齐关系决定的,
  // 理论上来说,从0到(inl+cipher_block_size-1)的任何一个数字都有可能(单位是字节),
  // 所以输出的参数out要有足够的空间存储数据。写入到out中的实际数据长度保存在outl参数中。操作成功返回1,否则返回0。
  if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)){
    aes256_handleErrors();
  }
  ciphertext_len = len;


  // int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,int *outl);
  // 该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,
  // 它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。
  // 加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。
  // 该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,
  // 那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,
  // 这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。
  // PKCS padding标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。
  // 无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。
  // 比如,如果块长度是9,要加密的数据长度是11,那么5个值为5的字节就应该增加在数据的后面。
  if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)){
      aes256_handleErrors();
  }
  ciphertext_len += len;

  // 释放
  EVP_CIPHER_CTX_free(ctx);

  return ciphertext_len;
}


int TestAES::aes256_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,unsigned char *iv, unsigned char *plaintext)
{
  EVP_CIPHER_CTX *ctx;

  int len;

  int plaintext_len;

  //该函数初始化一个EVP_CIPHER_CTX结构体,只有初始化后该结构体才能在下面介绍的函数中使用。操作成功返回1,否则返回0。
  if(!(ctx = EVP_CIPHER_CTX_new())){
     aes256_handleErrors();
  }

  //【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】
  // 这三个函数是上面三个函数相应的解密函数。这些函数的参数要求基本上都跟上面相应的加密函数相同。
  // 如果padding功能打开了,EVP_DecryptFinal会检测最后一段数据的格式,如果格式不正确,该函数会返回错误代码。
  // 此外,如果打开了padding功能,EVP_DecryptUpdate函数的参数out的长度应该至少为(inl+cipher_block_size)字节
  // 但是,如果加密块的长度为1,则其长度为inl字节就足够了。三个函数都是操作成功返回1,否则返回0。
  // 需要注意的是,虽然在padding功能开启的情况下,解密操作提供了错误检测功能,但是该功能并不能检测输入的数据或密钥是否正确,
  // 所以即便一个随机的数据块也可能无错的完成该函数的调用。如果padding功能关闭了,那么当解密数据长度是块长度的整数倍时,操作总是返回成功的结果。
  if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)){
    aes256_handleErrors();
  }

  if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)){
    aes256_handleErrors();
  }
  plaintext_len = len;

  //如果解密数据是分组长度16的整数倍,EVP_DecryptFinal_ex会调用失败而且解密数据不正确
  //因此当解密数据为16的整数倍时,不执行EVP_DecryptFinal_ex,解密结果正确
  if(len % 16 != 0)
  {
      if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)){
          aes256_handleErrors();
      }
      plaintext_len += len;
  }

  EVP_CIPHER_CTX_free(ctx);

  return plaintext_len;
 }


发布了33 篇原创文章 · 获赞 14 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/chenchao_shenzhen/article/details/72467093
今日推荐