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

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

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

一、简介

AES:略(自行百度)

CBC:略(自行百度)

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

二、实现方式

1.使用以下3个接口实现AES-CBC加解密(注意:PKCS7的填充需要自己另外实现)这3个接口在Openssl的v3.0版本以后被废弃:

int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
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-CBC加解密(推荐,v3.0以前以后得版本都可以兼容,但执行效率比上面3个接口稍差):

EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *c);
EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *c);int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *c, int pad);int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

三、实现方式:非EVP接口

描述:这种方式使用Openssl v3.0以前的旧接口实现,Pkcs7填充需要自己实现。

1.先写一个处理Pkcs7填充/去填充的工具类;

#ifndef PADDING_H
#define PADDING_H#include <QByteArray>/*** @brief 数据填充类(对齐类)* 算法数据填充模式,提供对数据进行PKCS7填充和去除填充的相关函数。*/class Padding
{
public:Padding();/*** @brief GntPadding::getPKCS7PaddedLength* 根据原始数据长度,计算进行PKCS7填充后的数据长度* @param dataLen 原始数据长度* @param alignSize 对齐字节数* @return 返回填充后的数据长度*/static int getPKCS7PaddedLength(int dataLen, int alignSize);/*** @brief Padding::PKCS7Padding* 采用PKCS7Padding方式,将in数据进行alignSize字节对齐填充。* 此函数用于加密前,对明文进行填充。* @param in 数据* @param alignSize 对齐字节数* @return 返回填充后的数据*/static QByteArray PKCS7Padding(const QByteArray &in, int alignSize);/*** @brief Padding::PKCS7UnPaddinged* 采用PKCS7Padding方式,将in数据去除填充。* 此函数用于解密后,对解密结果进一步去除填充,以得到原始数据,直接在原字节数组中剔除* (由于减少了整个字节数组的拷贝,所以比Padding::PKCS7UnPadding效率高一些)* @param in 数据字节数组* @return 无返回*/static void PKCS7UnPadding(QByteArray &in);
};#endif // PADDING_H
#include "padding.h"Padding::Padding()
{}int Padding::getPKCS7PaddedLength(int dataLen, int alignSize)
{// 计算填充的字节数(按alignSize字节对齐进行填充)int remainder = dataLen % alignSize;int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);return (dataLen + paddingSize);
}QByteArray Padding::PKCS7Padding(const QByteArray &in, int alignSize)
{// 计算需要填充字节数(按alignSize字节对齐进行填充)int remainder = in.size() % alignSize;int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);char paddingChar = static_cast<char>(paddingSize);// 进行填充QByteArray temp(in);temp.append(paddingSize, paddingChar);return temp;
}void Padding::PKCS7UnPadding(QByteArray &in)
{char paddingSize = in.at(in.size() - 1);in.chop(static_cast<int>(paddingSize));
}

2.封装aes-cbc的加解密接口

/**
* @brief AES::cbc_encrypt
* CBC模式加解密,填充模式采用PKCS7,
* 对任意长度明文进行一次加解密,根据机器内存情况,尽量不要太长。
* @param in 输入数据
* @param out 输出结果
* @param key 密钥,长度必须是16/24/32字节,否则加密失败
* @param ivec 初始向量,长度必须是16字节
* @param enc true-加密,false-解密
* @return 执行结果
*/
bool AES::cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{// 检查密钥合法性(只能是16、24、32字节)if(key.size() != 16 && key.size() != 24 && key.size() != 32){qInfo() << __FUNCTION__ << "aes cbc key size error!";return false;}if(ivec.size() != 16) // 初始向量为16字节{qInfo() << __FUNCTION__ << "aes cbc ivc size error!";return false;}if (enc){// 生成加密keyAES_KEY aes_key;if (AES_set_encrypt_key(reinterpret_cast<const unsigned char*>(key.data()), key.size() * 8, &aes_key) != 0){return false;}// 进行PKCS7填充QByteArray inTemp = Padding::PKCS7Padding(in, AES_BLOCK_SIZE);// 执行CBC模式加密QByteArray ivecTemp = ivec; // ivec会被修改,故需要临时变量来暂存out.resize(inTemp.size()); // 调整输出buf大小AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(inTemp.data()),reinterpret_cast<unsigned char*>(out.data()),static_cast<size_t>(inTemp.size()),&aes_key,reinterpret_cast<unsigned char*>(ivecTemp.data()),AES_ENCRYPT);return true;}else{// 生成解密keyAES_KEY aes_key;if (AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(key.data()), key.size() * 8, &aes_key) != 0){return false;}// 执行CBC模式解密QByteArray ivecTemp = ivec; // ivec会被修改,故需要临时变量来暂存out.resize(in.size()); // 调整输出buf大小AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(in.data()),reinterpret_cast<unsigned char*>(out.data()),static_cast<size_t>(in.size()),&aes_key,reinterpret_cast<unsigned char*>(ivecTemp.data()),AES_DECRYPT);// 解除PKCS7填充Padding::PKCS7UnPadding(out);return true;}
}

四、实现方式:EVP接口

描述:这种方式使用Openssl的evp接口实现,内置各种填充不需要自己实现,支持大型文件分段读取分段加解密,使用简易性和灵活性都很高,执行效率和资源消耗比非evp接口差一些。

/**
* @brief AES::evp_cbc_encrypt
* CBC模式加解密,填充模式采用PKCS7,
* 对任意长度明文进行一次加解密,根据机器内存情况,尽量不要太长。
* @param in 输入数据
* @param out 输出结果
* @param key 密钥,长度必须是16/24/32字节,否则加密失败
* @param ivec 初始向量,长度必须是16字节
* @param enc true-加密,false-解密
* @return 执行结果
*/
bool AES::evp_cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{const EVP_CIPHER *cipher = nullptr;switch (key.size()){case 16:cipher = EVP_aes_128_cbc();break;case 24:cipher = EVP_aes_192_cbc();break;case 32:cipher = EVP_aes_256_cbc();break;default:{qInfo() << __FUNCTION__ << "aes cbc key size error!";return false;}}if(ivec.size() != 16){qInfo() << __FUNCTION__ << "aes cbc ivc size error!";return false;}// 创建 EVP cipher 上下文EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();// 初始化 cipher 上下文(初始化为默认值)EVP_CIPHER_CTX_init(ctx);// 加解密实际输出长度中间标记int out_update_len = 0;int out_final_len = 0;// openssl接口执行结果返回值int ret = 0;// 加密if(enc){// 设置 cipher 上下文,设置秘钥、初始向量ret = EVP_EncryptInit_ex(ctx, cipher, nullptr, reinterpret_cast<const unsigned char*>(key.data()), reinterpret_cast<const unsigned char*>(ivec.data()));if(ret != 1){qInfo() << __FUNCTION__ << "EVP_EncryptInit_ex() error!";EVP_CIPHER_CTX_free(ctx);return false;}// 加密填充方式 PKCS7填充(会自动兼容PKCS5)ret = EVP_CIPHER_CTX_set_padding(ctx, EVP_PADDING_PKCS7);if(ret != 1){qInfo() << __FUNCTION__ << "EVP_CIPHER_CTX_set_padding() error! set PKCS7 Padding error!";EVP_CIPHER_CTX_free(ctx);return false;}// 密文输出缓冲区设置大小(未加密数据的大小+一个加密填充块大小),不一定会完全使用。out.resize(in.size() + AES_BLOCK_SIZE);// 对数据进行加密out_update_len = out.size();ret = EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(out.data()), &out_update_len, reinterpret_cast<const unsigned char*>(in.data()), in.size());if(ret != 1){qInfo() << __FUNCTION__ << "EVP_EncryptUpdate() error!";EVP_CIPHER_CTX_free(ctx);return false;}// 完成加密过程并获取最终结果out_final_len = 0;ret = EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(out.data()) + out_update_len, &out_final_len);if(ret != 1){qInfo() << __FUNCTION__ << "EVP_EncryptFinal_ex() error!";EVP_CIPHER_CTX_free(ctx);return false;}}else // 解密{// 设置 cipher 上下文,设置秘钥、初始向量ret = EVP_DecryptInit_ex(ctx, cipher, nullptr, reinterpret_cast<const unsigned char*>(key.data()), reinterpret_cast<const unsigned char*>(ivec.data()));if(ret != 1){qInfo() << __FUNCTION__ << "EVP_DecryptInit_ex() error!";EVP_CIPHER_CTX_free(ctx);return false;}// 解密填充方式 PKCS7填充(会自动兼容PKCS5)ret = EVP_CIPHER_CTX_set_padding(ctx, EVP_PADDING_PKCS7);if(ret != 1){qInfo() << __FUNCTION__ << "EVP_CIPHER_CTX_set_padding() error! set PKCS7 Padding error!";EVP_CIPHER_CTX_free(ctx);return false;}// 明文输出缓冲区设置大小(未解密数据的大小),不一定会完全使用。out.resize(in.size());out.fill(0);// 对数据进行解密out_update_len = out.size();ret = EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(out.data()), &out_update_len, reinterpret_cast<const unsigned char*>(in.data()), in.size());if(ret != 1){qInfo() << __FUNCTION__ << "EVP_DecryptUpdate() error!";EVP_CIPHER_CTX_free(ctx);return false;}// 完成解密过程并获取最终结果out_final_len = 0;ret = EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(out.data()) + out_update_len, &out_final_len);if(ret != 1){qInfo() << __FUNCTION__ << "EVP_DecryptFinal_ex() error!";EVP_CIPHER_CTX_free(ctx);return false;}}// 总的实际加解密数据长度为前面更新部分的长度加上最终部分的长度out_update_len += out_final_len;// 去除输出缓冲区末尾多余的长度out.chop(out.size() - out_update_len);// 释放 cipher 上下文EVP_CIPHER_CTX_free(ctx);return true;
}

五、测试结果:

加密:

// 点击了加密按钮,触发加密流程
void AESTestWidget::on_btnEncrypt_clicked()
{//加密明文字符串QByteArray encryptText;QByteArray encryptTextBase64;QByteArray key(ui->leEncryptKey->text().toUtf8());QByteArray ivec(ui->leEncryptIV->text().toUtf8());// 加密(将明文加密为二进制数据)//AES::cbc_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, ivec, true);AES::evp_cbc_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, ivec, true);// 对加密后的二进制数据进行base64编码并显示encryptTextBase64 = encryptText.toBase64();ui->teCiphertextBase64->setText(QString::fromUtf8(encryptTextBase64));// 显示密文二进制数据的十六进制字符串ui->teCiphertextHex->setText(QString::fromUtf8(encryptText.toHex()));}

解密:

// 点击了解密按钮,触发解密流程
void AESTestWidget::on_btnDecrypt_clicked()
{//解密base64编码的密文字符串QByteArray decryptText;QByteArray key(ui->leDecryptKey->text().toUtf8());QByteArray ivec(ui->leDecryptIV->text().toUtf8());// 解密(先将base64编码的密文转为二进制字节数据再解密)//AES::cbc_encrypt(QByteArray::fromBase64(ui->teCiphertext2Decrypt->toPlainText().toUtf8()),decryptText, key, ivec, false);AES::evp_cbc_encrypt(QByteArray::fromBase64(ui->teCiphertext2Decrypt->toPlainText().toUtf8()),decryptText, key, ivec, false);qDebug() << "解密结果:" << decryptText;ui->tePlaintextByDecrypt->setText(QString::fromUtf8(decryptText));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/670163.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

根据相同的key 取出数组中最后一个值

数组中有很多对象 , 需根据当前页面的值current 和 数组中的key对比 拿到返回值 数据结构如下 之前写法 const clickedItem routeList.find(item > item.key current) // current是当前页 用reduce遍历数组返回最后一个值 const clickedItem routeList.reduce((lastIte…

OpenCV 入门(七)—— 身份证识别

OpenCV 入门系列&#xff1a; OpenCV 入门&#xff08;一&#xff09;—— OpenCV 基础 OpenCV 入门&#xff08;二&#xff09;—— 车牌定位 OpenCV 入门&#xff08;三&#xff09;—— 车牌筛选 OpenCV 入门&#xff08;四&#xff09;—— 车牌号识别 OpenCV 入门&#xf…

ABAP 第二代增强-采购申请子屏幕增强

文章目录 第二代增强-采购申请子屏幕增强需求实现过程创建项目运行效果客户屏幕的PBO全局变量获取数据更新数据运行效果查询底表修改数据 第二代增强-采购申请子屏幕增强 需求 实现过程 创建项目 运行效果 客户屏幕的PBO 全局变量 *&------------------------------------…

社交媒体数据恢复:新浪微博

当我们在使用新浪微博时&#xff0c;可能会遇到一些意外情况&#xff0c;如误删微博、账号出现问题等。这时&#xff0c;我们需要进行数据恢复。本文将详细介绍如何在新浪微博中进行数据恢复。 首先&#xff0c;我们需要了解新浪微博的数据恢复功能。根据微博的帮助中心&#…

安卓跑马灯效果

跑马灯效果 当一行文本的内容太多&#xff0c;导致无法全部显示&#xff0c;也不想分行展示时&#xff0c;只能让文字从左向右滚动显示&#xff0c;类 似于跑马灯。电视在播报突发新闻时经常在屏幕下方轮播消息文字&#xff0c;比如“ 快讯&#xff1a;我国选手 *** 在刚刚结束…

1W 3KVDC 隔离 稳压单输出 DC/DC 电源模块——TPV-SAR系列

TPV-SAR系列产品是专门针对PCB上分布式电源系统中需要与输入电源隔离且输出精度要求较高的电源应用场合而设计。该产品适用于&#xff1b;1&#xff09;输入电源的电压变化≤5%&#xff1b;2&#xff09;输入输出之前要求隔离电压≥3000VDC&#xff1b;3&#xff09;对输出电压…

LeetCode 面试经典150题 228.汇总区间

题目&#xff1a; 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区…

oracle 8i系统检查

oracle 8i系统检查 set echo on spool d:\bk\1.txt select sysdate from dual; --版本信息 select * from v$version; --安装的产品 col PARAMETER for a50; col value for a10; select * from v$option order by 2; --用户信息 set linesize 100 set pagesize 100 COL USE…

Python实现2048游戏

提供学习或者毕业设计使用,功能基本都有,不能和市场上正式游戏相提比论,请理性对待! 在这篇博客中,我们将使用 Python 和 Pygame 库来编写经典的 2048 游戏。2048 是一个益智类游戏,通过在 4x4 网格上滑动方块并合并它们来创建一个新的数字,直到获得数字 2048 或者无法继…

有什么方便的教学口语软件?6个软件教你快速练习口语

有什么方便的教学口语软件&#xff1f;6个软件教你快速练习口语 以下是六个方便实用的教学口语软件&#xff0c;它们可以帮助您快速练习口语&#xff1a; AI外语陪练: 这是一款知名的语言学习软件&#xff0c;提供多种语言的口语练习课程。它采用沉浸式的学习方法&#xff0…

Python语言在地球科学中地理、气象、气候变化、水文、生态、传感器等数据可视化到常见数据分析方法的使用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;Python能够运行在Linux、Windows、Macintosh、AIX操作系统上及不同平台&#xff08;x86和arm&#xff09;&#xff0c;Python简洁的语法和对动态输入的支持&#xff0c;再加上解释性语言的本质&…

百科词条创建机构有哪些?

在互联网时代&#xff0c;百度百科作为我国最大的中文百科全书&#xff0c;已经成为人们获取知识、查询信息的重要途径。随着百度百科影响力的不断扩大&#xff0c;越来越多的人和企业试图通过创建企业词条来提升自身知名度&#xff0c;企业和个人为了在百度百科上占据一席之地…