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

本篇博文讲述如何在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版本以后被标记为废弃接口,但还可以使用:

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_ecb_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key, const int enc);
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个接口稍差):

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);

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

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

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

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

四、实现加解密接口

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

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

#ifndef WJPAES_H
#define WJPAES_H#include <QObject>#include "openssl/types.h"class WJPAES
{
public:WJPAES();/*** @brief ECB模式加解密,填充模式采用PKCS7,* 对任意长度明文进行加一次解密,根据机器内存情况,尽量不要太长。* @param in 输入数据* @param out 输出结果* @param key 密钥,长度必须是16/24/32字节,否则加密失败* @param enc true-加密,false-解密* @return 执行结果*/static bool ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const bool &enc);static bool evp_ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const bool &enc);/*** @brief CBC模式加解密,填充模式采用PKCS7,* 对任意长度明文进行一次加解密,根据机器内存情况,尽量不要太长。* @param in 输入数据* @param out 输出结果* @param key 密钥,长度必须是16/24/32字节,否则加密失败* @param ivec 初始向量,长度必须是16字节* @param enc true-加密,false-解密* @return 执行结果*/static bool cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const bool &enc);static bool evp_cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const bool &enc);/*** @brief 通用所有模式加解密,填充模式采用PKCS7,* 对任意长度明文进行一次加解密,根据机器内存情况,尽量不要太长。* @param in 输入数据* @param out 输出结果* @param key 密钥,长度必须是16/24/32字节,否则加密失败* @param ivec 初始向量,长度必须是16字节,如果是ECB可以传一个空的* @param enc true-加密,false-解密* @param cipher 加解密模式(ECB-128、ECB-192、ECB-256、CBC-128、CBC-192....)* @return 执行结果*/static bool evp_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const bool &enc, const EVP_CIPHER *cipher);
};#endif // WJPAES_H

详细实现见源码:

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

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

五、测试结果:

1.加密:

// 点击了加密按钮,触发加密流程
void AESTestWidget::on_btnEncrypt_clicked()
{//加密字符串QByteArray encryptText;QByteArray encryptTextBase64;QByteArray key(ui->leEncryptKey->text().toUtf8());QByteArray ivec(ui->leEncryptIV->text().toUtf8());// 加密(将明文加密为二进制数据)if(m_mode == "ECB"){//WJPAES::ecb_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, true);WJPAES::evp_ecb_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, true);}else if(m_mode == "CBC"){WJPAES::cbc_encrypt(ui->tePlaintext2Encrypt->toPlainText().toUtf8(), encryptText, key, ivec, true);//WJPAES::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()));}

CBC加密Demo:

key:H321jDtfnet@1279

初始向量:H321jDtfnet@1279

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

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

2.解密:

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

CBC解密Demo:

key:H321jDtfnet@1279

初始向量:H321jDtfnet@1279

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

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

Demo下载地址:

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

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

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

相关文章

基于无监督学习算法的滑坡易发性评价的实施(k聚类、谱聚类、Hier聚类)

基于无监督学习算法的滑坡易发性评价的实施 1. k均值聚类2. 谱聚类3. Hier聚类4. 基于上述聚类方法的易发性实施本研究中的数据集和代码可从以下链接下载: 数据集实施代码1. k均值聚类 K-Means 聚类是一种矢量量化方法,最初来自信号处理,旨在将 N 个观测值划分为 K 个聚类,…

我悟了!24年软考架构就这100道母题,历史重复率90%

距离软考考试的时间越来越近了&#xff0c;趁着这两周赶紧准备起来 今天给大家整理了——系统架构设计师100道经典母题&#xff0c;有PDF&#xff0c;可打印&#xff0c;每天刷几道。 一、计算机系统基础&#xff08;12&#xff09; 1. 计算机采用分级存储体系的主要目的是为了…

深度学习笔记001

目录 一、批量规范化 二、残差网络ResNet 三、稠密连接网络&#xff08;DenseNet&#xff09; 四、循环神经网络 五、信息论 六、梯度截断 本篇blog仅仅是本人在学习《动手学深度学习 Pytorch版》一书中做的一些笔记&#xff0c;感兴趣的读者可以去官网http://zh.gluon.a…

中小学校活动向媒体投稿报道宣传有哪些好方法

作为一所中小学校的教师,我肩负着向外界展示学校风采、宣传校园文化活动的重要使命。起初,每当学校举办特色活动或取得教学成果时,我都会满怀热情地撰写新闻稿,希望通过媒体的平台让更多人了解我们的故事。然而,理想丰满,现实骨感,我很快发现,通过电子邮件向媒体投稿的过程充满…

技术速递|Python in Visual Studio Code 2024年4月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布 Visual Studio Code 的 Python 和 Jupyter 扩展 2024 年 4 月发布&#xff01; 此版本包括以下公告&#xff1a; 改进了 Flask 和 Django 的调试配置流程Jupyter Run Dependent Cells with Pylance 的模块和导入分析Hatch 环境发…

Vue+OpenLayers7入门到实战:OpenLayers解析通过fetch请求的GeoJson格式数据,并叠加要素文字标注,以行政区划边界为例

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上通过fetch请求geojson数据,然后通过OpenLayers解析为Feature要素叠加到图层上,并且通过动态设置标注方式显示要素属性为文字标注。 本章还是以行政区划边界为例,这个…

网工常用工具——Xshell

今天给各位介绍一下&#xff0c;Xshell工具 Xshell是一款功能强大的终端模拟器&#xff0c;主要用于Windows操作系统&#xff0c;用于远程访问和管理服务器&#xff0c;允许用户通过SSH&#xff08;Secure Shell&#xff09;协议安全地连接到远程Linux/Unix服务器或其他支持SS…

Linux的并发与竞争

文章目录 一、并发二、竞争三、保护内容是什么四、解决并发与竞争的几种常用方法1.原子操作原子整型API函数原子位操作 API 函数 2.自旋锁自旋锁格式如下&#xff1a;自旋锁 API 函数自旋锁的使用注意事项 3.信号量信号量 API 函数信号量格式如下&#xff1a; 4.互斥体API函数如…

【LeetCode刷题记录】994. 腐烂的橘子

994 腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直…

[C/C++] -- 搜索迷宫路径

DFS&#xff08;深度优先搜索&#xff09;和BFS&#xff08;广度优先搜索&#xff09;是两种常用的图遍历算法&#xff0c;它们在搜索图或树中的节点时有着不同的策略和特点。 深度优先搜索 (DFS): 在DFS中&#xff0c;从起始节点开始&#xff0c;沿着一条路径尽可能深地搜索&a…

健康知识集锦

页面 页面代码 <% layout(/layouts/default.html, {title: 健康知识管理, libs: [dataGrid]}){ %> <div class"main-content"><div class"box box-main"><div class"box-header"><div class"box-title"&g…

triton之语法学习

一 基本语法 1 torch中tensor的声明 x = torch.tensor([[1,2, 1, 1, 1, 1, 1, 1],[2,2,2,2,2,2,2,2]],device=cuda) 声明的时候有的时候需要指出数据的类型,不然在kernel中数据类型无法匹配 x = torch.tensor([1,2,1,1,1,1,1,1],dtype = torch.int32,device=cuda) 2 idx id…

2024年数据安全软件排行榜:哪款守护神最值得信赖?

在数字化时代&#xff0c;数据安全已成为企业和个人不可或缺的一部分。为了确保数字资产的安全&#xff0c;市场上涌现出了众多数据安全软件。以下是数据安全软件Top 10&#xff0c;它们以卓越的性能和功能&#xff0c;为您的数字世界提供坚实的保障。 一、数据安全系统排排行…

word-快速入门

1、熟悉word界面 2、word排版习惯 3、排版文本基本格式 1、word界面 选项卡 功能组 点击功能组右下角小三角可以开启完整功能组&#xff0c;获得启动器 软件右上角有功能显示折叠按钮 2、排版好习惯 &#xff08;1&#xff09;随时保存 &#xff08;2&#xff09;规范文件命…

替换spring-boot中的组件版本

spring-boot是一个用于简化开发的框架&#xff0c;引入spring-boot后会自动包含spring框架&#xff0c;通过引入xxx-start来完成指定组件的功能。比如&#xff1a; spring-boot-starter-web(嵌入 Tomcat 和 web 开发需要的 servlet 和 jsp 支持)spring-boot-starter-data-jpa(…

AI赋能EasyCVR视频汇聚/视频监控平台加快医院安防体系数字化转型升级

近来&#xff0c;云南镇雄一医院发生持刀伤人事件持续发酵&#xff0c;目前已造成2人死亡21人受伤。此类事件在医院层出不穷&#xff0c;有的是因为医患纠纷、有的是因为打架斗殴。而且在每日大量流动的人口中&#xff0c;一些不法分子也将罪恶的手伸到了医院&#xff0c;实行扒…

使用golang实现k-means

k-means聚类算法 K-Means是一种无监督算法&#xff0c;其目标是将数据进行分类。分类个数要求已知。 k-means流程 随机确定K个点作为质心、找到离每个点最近的质心&#xff0c;将这个点分配到这个质心代表的簇里再对每个簇进行计算&#xff0c;以点簇的均值点作为新的质心如…

LeetCode343:整数拆分

题目描述 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 代码 动态规划 class Solution { public:int integerBreak(int n) {/*dp[i]&#xff1a;表示对…

vue3组件插槽

Index.vue: <script setup> import { ref, onMounted } from vue import Child from ./Child.vue import ./index.cssonMounted(() > {}) </script><template><div class"m-home-wrap"><Child>插槽</Child><div class&qu…

使用flutter开发一个U盘文件管理APP,只解析图片文件

今天教大家用flutter撸一个U盘文件管理APP,需求是这样的: 当我在Android设备上插入U盘后,我能在APP中打开U盘的文件目录,并且能进入对应目录的下一级目录,如果下级目录下有图片文件,我就对这个图片文件进行解析,并展示出来。 需求了解后,先上个效果图: 效果图看完后,…