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

news2024/11/27 16:49:28

本篇博文讲述如何在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.coloradmin.cn/o/1659502.html

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

相关文章

Android system property运作流程源码分析

一.序 前文分析了build.prop这个系统属性文件的生成&#xff0c;每个属性都有一个名称和值&#xff0c;他们都是字符串格式。属性被大量使用在Android系统中&#xff0c;用来记录系统设置或进程之间的信息交换。属性是在整个系统中全局可见的。每个进程可以get/set属性&#x…

【ITK配准】第十六期 2D中BSpline可变形多分辨率配准样例

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享ITK配准中的2D中BSpline可变形多分辨率配准,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 2…

文件摆渡系统与传统文件交换有什么区别|好用的文件摆渡系统分享

一、文件摆渡系统与传统文件交换方式的区别 文件摆渡系统与传统文件交换方式在多个方面存在显著的区别。随着信息化和网络化的发展&#xff0c;文件交换的方式也在不断演进&#xff0c;从传统的物理介质交换到现代化的网络交换&#xff0c;文件摆渡系统作为其中的一种重要方式…

PT:pt write_change to innovus 脚本

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 #!usr/bin/perl open rf,"$ARGV[0]"; open wf,">test. tcl"; while (<rf>) { s/\[(\D[^\s\]]*)?\]/\\\[$1\\\]/g; if (/c…

CountDownLatch应用场景代码练习

目录 概念原理核心参数和方法两种应用场景实现代码应用一&#xff1a;让 主任务 等待 所有子任务执行完毕后&#xff0c;再继续执行执行结果应用二&#xff1a;让所有子任务同时执行&#xff0c;打印出发时间执行结果应用二&#xff08;扩展&#xff09;&#xff1a;让所有子任…

React:Router-2. createBrowserRouter函数式

参考文档&#xff1a;ReactRouter官网 前边的文章 BrowserRouter组件式路由 提供了组件式路由的方式&#xff0c;在react-router6.4.0及以上版本&#xff0c;提供了 createBrowserRouter 函数式路由创建方式。 一、创建路由 1. 新建router.js文件&#xff0c;使用createBrow…

WPF容器控件之dockpanel、布局控件

dockpanel 容器控件&#xff0c;对其子元素进行或者水平垂直排布&#xff0c;也可以叫停靠面板,也可以让子元素停靠到容器某一个边上&#xff0c;拉伸元素拾起充满全部的高度或者宽度&#xff0c;也可以使最后一个子元素是否铺满剩余的空间。 参数 LastChildFill最后一个子元素…

引用数据类型 栈内存 堆内存

let m { a: 10, b: 20 }; let n m; n.a 15; console.log(m.a) // 15 原因&#xff1a;基本数据类型存储在栈内存中&#xff0c;引用数据类型存储在堆内存中 &#xff0c;引用数据类型存储在堆内存中会在栈内存中创建一个指针&#xff0c;栈内存中的这个指针指向堆内存中的地…

二.Django--创建多个APP路由映射

目录 1-创建项目 2-创建多个APP 3-注册APP 4-创建"前端页面"并做路由映射 各个APP里面的views.py写视图函数等等 1-创建项目 django-admin startproject 项目名 django-admin startproject my_project 2-创建多个APP python manage.py startapp app名 pyth…

遥控挖掘机之ESP8266调试心得(1)

ESP8266调试心得 1. 前言2.遇到的问题2.1 ESP8266模块建立TCP连接时候报错2.2 指令异常问题 3. 更新ESP8266固件3. ESP8266的部分AT指令3. 连接步骤3.1 模块与电脑连接3.2.1 电脑上的设置3.2.2 ESP8266模块作为客户机&#xff08;TCP Cilent&#xff09;的设置步骤 3.2 模块与模…

云计算:数据时代的魔法城堡

云端初探&#xff1a;定义与起源 想象一下&#xff0c;有一个巨大的、无形的仓库&#xff0c;里面存放着全世界的信息与计算能力&#xff0c;你可以随时随地按需提取&#xff0c;无需关心它的物理位置或维护细节。这就是云计算——一种基于互联网的计算方式&#xff0c;它通过…

Hive UDTF之explode函数、Lateral View侧视图

Hive UDTF之explode函数 Hive 中的 explode() 函数是一种用于处理数组类型数据的 User-Defined Table-Generating Function (UDTF)。它将数组拆分成多行&#xff0c;每个数组元素对应生成的一行数据。这在处理嵌套数据结构时非常有用&#xff0c;例如处理 JSON 格式的数据。 …

CentOS 自建gitlab仓库:安装相关工具

所需环境 Node 安装项目依赖、项目打包运行Nginx 前端项目部署&#xff08;正向代理、反向代理、负载均衡等&#xff09;Git 自动化部署时 拉取代码使用GitLab 代码仓库GitLab-Runner GitLab的CI/CD执行器 一、安装Node 检测是否已安装 常用node -v 命令检测。 如果已安装&a…

web前端学习笔记9

9. HTML5新增元素及属性 9.1 HTML5新增结构元素 HTML5引入了几个新的结构元素,极大地改善了网页的组织和结构方式。以下是HTML5中的一些关键新结构元素: 标签说明<header>页面或页面中某一个区块的页眉,通常是一些引导和导航信息<nav>可以作为页面导航的链接组&…

C# WinForm —— 12 ListBox绑定数据

ListBox加载大量数据时&#xff0c;避免窗体闪烁的方法&#xff1a; 在加载语句的前后分别加上 BeginUpdate()方法 和 EndUpdate()方法 指定一个集合为绑定的数据源 1. 首先&#xff0c;右键项目&#xff0c;添加类 2. 在新建的类文件中添加属性值信息 3. 构建初始化的对象…

算法设计课第五周(贪心法实现活动选择问题)

目录 一、【实验目的】 二、【实验内容】 三、实验源代码 一、【实验目的】 &#xff08;1&#xff09;熟悉贪心法的设计思想 &#xff08;2&#xff09;理解贪心法的最优解与正确性证明之间的关系 &#xff08;3&#xff09;比较活动选择的各种“贪心”策略&#xff0c;…

CSP-j 2022csp-j完善程序易错题

易错题 答案23&#xff1a; 对 解析23&#xff1a; 函数 g 就是把函数 f 改成递推的形式 答案24&#xff1a; 对 解析23&#xff1a; 无。 答案25&#xff1a; C 解析25&#xff1a; m n ( m - 1 ) * ( 1 2 3 4 ... n ) O(mn^2) 答案26&#xff1a; C 解析26&#x…

软件设计师笔记(二)-零碎要点

本文内容来自笔者学习zst 留下的笔记&#xff0c;都是零碎的要点&#xff0c;查缺补漏&#xff0c;若有错误请大家提出&#xff0c;希望大家都能通过&#xff0c;记得加上免费的关注&#xff01;谢谢&#xff01; 目录 1. 算法 [广度和深度优先] 2. 遍历方式 3. 四大算法 …

RuoYi-Vue-Plus (Echarts 图表)

一、echarts 图表介绍和使用 官网地址:目前echarts以及贡献给Apache Apache EChartshttps://echarts.apache.org/zh/index.htmlecharts配置项手册 Documentation - Apache EChartshttps://echarts.apache.org/z

第19讲:Ceph集群CrushMap规则定制与调优:从基础到高级应用

文章目录 1.CrushMap规则拓扑结构1.1.集群默认的CrushMap规则拓补图1.2.自定义的CrushMap规则拓补图 2.定制CrushMap规则的方法以及注意事项3.通过二进制文件编写一套CrushMap规则3.1.将系统默认的CrushMap规则导出3.2.根据需求编写CrushMap规则3.3.将编写好的规则导入到集群中…