当前位置:   article > 正文

OpenSSL实现AES的ECB和CBC加解密,可一次性加解密任意长度的明文字符串或字节流(QT C++环境)_qt openssl aes

qt openssl aes

本篇博文讲述如何在Qt C++的环境中使用OpenSSL实现AES-ECB/CBC-Pkcs7加/解密,可以一次性加解密一个任意长度的明文字符串或者字节流,但不适合分段读取加解密的(例如,一个4GB的大型文件需要加解密,要分段读取,每次读取10MB,就加解密10MB,这种涉及全文件填充,而不是每个10MB片段填充具有较复杂的上下文处理,本文不探讨这种)

Qt中的QByteArray比较好用,所以我本篇文章不使用标准C++的unsigned char数组,而是用QByteArray代替,所以要依赖Qt的环境,如果你不用Qt就想办法把QByteArray改回unsigned char数组。

一、简介

AES:略(自行百度)

ECB:略(自行百度)

CBC:略(自行百度)

PKCS7:填充方式,AES支持多种填充方式:如NoPadding、PKCS5Padding、ISO10126Padding、PKCS7Padding、ZeroPadding。PKCS7兼容PKCS5,目前PKCS7比较通用。

二、实现方式

1.使用以下4个接口实现AES的ECB和CBC加解密,注意:PKCS7的填充需要自己另外实现,想要对大型文件分段读取整体加解密的话非常麻烦,要自己想办法处理上下文衔接问题,这4个接口在Openssl的v3.0版本以后被标记为废弃接口,但还可以使用:

  1. int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
  2. int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
  3. void AES_ecb_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key, const int enc);
  4. void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,size_t length, const AES_KEY *key, unsigned char *ivec, const int enc);

2.使用Openssl的EVP接口实现AES的ECB和CBC加解密,EVP接口内置各种填充不需要我们自己实现了,另外利用其自带的上下文结构特性,可以进行大型文件分段读取整体加解密,使用简易性和灵活性都很高,但是执行效率和资源消耗比非evp接口差一些。(推荐,v3.0前后的版本都可以兼容,但执行效率比上面4个接口稍差):

  1. EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);
  2. void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *c);
  3. EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *c);
  4. int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *c, int pad);
  5. int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
  6. int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
  7. int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
  8. int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
  9. int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
  10. int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

三、实现Pkcs7填充/去除填充

描述:使用Openssl v3.0以前的旧接口实现,Pkcs7填充/去除填充需要自己实现。

创造一个Pkcs7填充/去填充的工具类WJPPadding,封装Pkcs7填充/去除填充的静态接口:

  1. #ifndef WJPPADDING_H
  2. #define WJPPADDING_H
  3. #include <QByteArray>
  4. /**
  5. * @brief 数据填充类(对齐类)
  6. * 算法数据填充模式,提供对数据进行PKCS7填充和去除填充的相关函数。
  7. */
  8. class WJPPadding
  9. {
  10. public:
  11. WJPPadding();
  12. /**
  13. * @brief 根据原始数据长度,计算PKCS7填充后的数据长度
  14. * @param dataLen 原始数据长度
  15. * @param alignSize 对齐字节数
  16. * @return 返回填充后的数据长度
  17. */
  18. static int getPKCS7PaddedLength(int dataLen, int alignSize);
  19. /**
  20. * @brief 采用PKCS7Padding方式,将in数据进行alignSize字节对齐填充,填充后输出到out
  21. * 此函数用于加密前,对明文进行填充。
  22. * @param in 原始数据
  23. * @param out 填充后的数据
  24. * @param alignSize 对齐字节数(对于aes加解密,一般都是16 Byte)
  25. * @return 无返回
  26. */
  27. static void PKCS7Padding(const QByteArray &in, QByteArray &out, int alignSize);
  28. /**
  29. * @brief 采用PKCS7Padding方式,将in数据去除填充。
  30. * 此函数用于解密后,对解密结果进一步去除填充,以得到原始数据,直接在原数据中剔除
  31. * @param in 需要去除填充的数据
  32. * @return 无返回
  33. */
  34. static void PKCS7UnPadding(QByteArray &in);
  35. };
  36. #endif // WJPPADDING_H
  1. #include "wjppadding.h"
  2. WJPPadding::WJPPadding()
  3. {
  4. }
  5. int WJPPadding::getPKCS7PaddedLength(int dataLen, int alignSize)
  6. {
  7. // 计算填充的字节数(按alignSize字节对齐进行填充)
  8. int remainder = dataLen % alignSize;
  9. int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);
  10. return (dataLen + paddingSize);
  11. }
  12. void WJPPadding::PKCS7Padding(const QByteArray &in, QByteArray &out, int alignSize)
  13. {
  14. // 计算需要填充的字节数(按alignSize字节对齐进行填充)
  15. int remainder = in.size() % alignSize;
  16. int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);
  17. char paddingChar = static_cast<char>(paddingSize);// 填充字节数转字符
  18. // 进行填充
  19. out.reserve(in.size()); // 预留拷贝空间
  20. out = in; //拷贝原始数据
  21. out.append(paddingSize, paddingChar);// 尾部填充上面计算得到的填充字符
  22. }
  23. void WJPPadding::PKCS7UnPadding(QByteArray &in)
  24. {
  25. // 读取尾部最后一个元素,得到填充字符
  26. char paddingSize = in.at(in.size() - 1);
  27. // 填充字符转填充字节数,并在尾部剔除填充字节数所指的字节
  28. in.chop(static_cast<int>(paddingSize));
  29. }

四、实现加解密接口

描述:以下封装的接口,没有evp前缀的使用Openssl v3.0以前的旧接口实现,有evp前缀的就是使用Openssl的EVP接口实现。

创造一个自己的AES加解密类WJPAES,封装加解密的静态接口:

  1. #ifndef WJPAES_H
  2. #define WJPAES_H
  3. #include <QObject>
  4. #include "openssl/types.h"
  5. class WJPAES
  6. {
  7. public:
  8. WJPAES();
  9. /**
  10. * @brief ECB模式加解密,填充模式采用PKCS7,
  11. * 对任意长度明文进行加一次解密,根据机器内存情况,尽量不要太长。
  12. * @param in 输入数据
  13. * @param out 输出结果
  14. * @param key 密钥,长度必须是16/24/32字节,否则加密失败
  15. * @param enc true-加密,false-解密
  16. * @return 执行结果
  17. */
  18. static bool ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const bool &enc);
  19. static bool evp_ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const bool &enc);
  20. /**
  21. * @brief CBC模式加解密,填充模式采用PKCS7,
  22. * 对任意长度明文进行一次加解密,根据机器内存情况,尽量不要太长。
  23. * @param in 输入数据
  24. * @param out 输出结果
  25. * @param key 密钥,长度必须是16/24/32字节,否则加密失败
  26. * @param ivec 初始向量,长度必须是16字节
  27. * @param enc true-加密,false-解密
  28. * @return 执行结果
  29. */
  30. static bool cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const bool &enc);
  31. static bool evp_cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const bool &enc);
  32. /**
  33. * @brief 通用所有模式加解密,填充模式采用PKCS7,
  34. * 对任意长度明文进行一次加解密,根据机器内存情况,尽量不要太长。
  35. * @param in 输入数据
  36. * @param out 输出结果
  37. * @param key 密钥,长度必须是16/24/32字节,否则加密失败
  38. * @param ivec 初始向量,长度必须是16字节,如果是ECB可以传一个空的
  39. * @param enc true-加密,false-解密
  40. * @param cipher 加解密模式(ECB-128、ECB-192、ECB-256、CBC-128、CBC-192....)
  41. * @return 执行结果
  42. */
  43. static bool evp_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const bool &enc, const EVP_CIPHER *cipher);
  44. };
  45. #endif // WJPAES_H

详细实现见源码:

https://download.csdn.net/download/wu10188/89276012

已测试过接口,开箱即用。

五、测试结果:

1.加密:

  1. // 点击了加密按钮,触发加密流程
  2. void AESTestWidget::on_btnEncrypt_clicked()
  3. {
  4. //加密字符串
  5. QByteArray encryptText;
  6. QByteArray encryptTextBase64;
  7. QByteArray key(ui->leEncryptKey->text().toUtf8());
  8. QByteArray ivec(ui->leEncryptIV->text().toUtf8());
  9. // 加密(将明文加密为二进制数据)
  10. if(m_mode == "ECB")
  11. {
  12. //WJPAES::ecb_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, true);
  13. WJPAES::evp_ecb_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, true);
  14. }
  15. else if(m_mode == "CBC")
  16. {
  17. WJPAES::cbc_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, ivec, true);
  18. //WJPAES::evp_cbc_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, ivec, true);
  19. }
  20. // 对加密后的二进制数据进行base64编码
  21. encryptTextBase64 = encryptText.toBase64();
  22. ui->teCiphertextBase64->setText(QString::fromUtf8(encryptTextBase64));
  23. ui->teCiphertextHex->setText(QString::fromUtf8(encryptText.toHex()));
  24. }

CBC加密Demo:

key:H321jDtfnet@1279

初始向量:H321jDtfnet@1279

明文:床前明月光,疑是地上霜。 举头望明月,低头思故乡。-liBai

加密后得到的密文(base64):UG7NZA2BjdZirj0R8scTpFHbzobdQnmNEQjDoZCi2tallU7wxLJPQ0q4SViZIb3gwt+BxpkOrp3nwZeSWWG95hF6atwh+ZLdjustYDQxA9A=

2.解密:

  1. // 点击了解密按钮,触发解密流程
  2. void AESTestWidget::on_btnDecrypt_clicked()
  3. {
  4. //解密base64字符串
  5. QByteArray decryptText;
  6. QByteArray key(ui->leDecryptKey->text().toUtf8());
  7. QByteArray ivec(ui->leDecryptIV->text().toUtf8());
  8. // 解密(先将base64编码的密文转为二进制字节数据再解密)
  9. if(m_mode == "ECB")
  10. {
  11. WJPAES::ecb_encrypt(QByteArray::fromBase64(ui->teCiphertext2Decrypt->toPlainText().toUtf8()),decryptText, key, false);
  12. //WJPAES::evp_ecb_encrypt(QByteArray::fromBase64(ui->teCiphertext2Decrypt->toPlainText().toUtf8()),decryptText, key, false);
  13. }
  14. else if(m_mode == "CBC")
  15. {
  16. //WJPAES::cbc_encrypt(QByteArray::fromBase64(ui->teCiphertext2Decrypt->toPlainText().toUtf8()),decryptText, key, ivec, false);
  17. WJPAES::evp_cbc_encrypt(QByteArray::fromBase64(ui->teCiphertext2Decrypt->toPlainText().toUtf8()),decryptText, key, ivec, false);
  18. }
  19. qDebug() << "解密结果:" << decryptText;
  20. ui->tePlaintextByDecrypt->setText(QString::fromUtf8(decryptText));
  21. }

CBC解密Demo:

key:H321jDtfnet@1279

初始向量:H321jDtfnet@1279

密文(base64):UG7NZA2BjdZirj0R8scTpFHbzobdQnmNEQjDoZCi2tallU7wxLJPQ0q4SViZIb3gwt+BxpkOrp3nwZeSWWG95hF6atwh+ZLdjustYDQxA9A=

解密后得到的明文:床前明月光,疑是地上霜。 举头望明月,低头思故乡。-liBai

Demo下载地址:

https://download.csdn.net/download/wu10188/89276012

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/990932
推荐阅读
  

闽ICP备14008679号