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)
    {
        // 生成加密key
        AES_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
    {
        // 生成解密key
        AES_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));
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

常见通信协议

1、串口&#xff1a;&#xff08;串行异步全双工&#xff0c;先发低位&#xff09; 因为是异步的&#xff0c;所以没有时钟线&#xff0c;因为是全双工&#xff0c;所以有两条数据传输线&#xff0c;实现数据的收发。 帧格式 起始位1位&#xff0c;数据位8位&#xff0c;校验…

【C++】stack、queue和priority_queue的模拟实现

在本篇博客中&#xff0c;作者将会讲解STL中的stack、queue和priority_queue的模拟实现&#xff0c;同时还会带大家了解一下deque这个容器。 一.什么是适配器 STL中一共有6大组件&#xff1a;容器&#xff0c;适配器&#xff0c;空间配置器&#xff0c;仿函数&#xff0c;迭代器…

控制台调试 hover 后才出现的元素

调试 hover后才出现的元素 打开开发者工具&#xff0c;鼠标放在hover时才出现的元素上&#xff0c;然后点击右键&#xff1b; 不要选中任何选项&#xff0c;将鼠标移动到开发者工具的调试面板中&#xff1b; 按下N键&#xff0c;此时悬浮的元素不会消失&#xff0c;定位成功。…

Java注解介绍

注解&#xff08;Annotation&#xff09;是Java提供的一种元数据形式&#xff0c;它可以被添加到Java代码的各种元素上&#xff0c;如类、方法、变量、参数等。注解的作用主要包括&#xff1a; 1. 代码文档&#xff1a;注解可以用于生成文档&#xff0c;提高代码的可读性。 2.…

前端之深拷贝

前提&#xff1a; 就是在实际开发中&#xff0c;我有一个编辑的弹窗&#xff0c;可以查看和编辑&#xff0c;因为弹窗里面是一个步骤条&#xff0c;点击下一步就要向对应的接口发送请求&#xff0c;考虑到就比如我点击下一步&#xff0c;此次表箱信息其实不需要修改&#xff0…

大模型_DISC-MedLLM基于Baichuan-13B-Base医疗健康对话

文章目录 DISC-MedLLM介绍概述数据集部署推理流程 DISC-MedLLM 介绍 DISC-MedLLM 是一个专门针对医疗健康对话式场景而设计的医疗领域大模型&#xff0c;由复旦大学数据智能与社会计算实验室 (Fudan-DISC) 开发并开源。 该项目包含下列开源资源: DISC-Med-SFT 数据集 (不包…

智慧园区综合物业管理平台解决方案PPT(130页精品)

我们对智慧园区的理解 智慧园区&#xff0c;是通过信息技术和各类资源的整合&#xff0c;充分降低企业运营成本&#xff0c;提高工作效率&#xff0c;加强各类园区创新、服务和管理能力&#xff0c;为园区铸就一套超强的软实力。智慧园区的实现是多技术融合、多系统融合、多领域…

初识C语言——第十三天

关键字2&#xff1a; static 修饰局部变量&#xff0c;改变了局部变量的生命周期&#xff08;本质上是改变了变量的存储类型&#xff09; static修饰全局变量&#xff0c;使得这个全局变量只能在自己所在的源文件&#xff08;.c)内部可以使用&#xff0c;其他源文件不能使用 …

全方位了解 Meta Llama 3

本文将为您提供 Llama 3 的全面概览&#xff0c;从其架构、性能到未来的发展方向&#xff0c;让您一文了解这一革命性大语言模型的所有要点。 Meta Llama 发展历程 Llama 1 Llama 是由 Meta(FaceBook) AI 发布的一个开源项目&#xff0c;允许商用&#xff0c;影响力巨大。Lla…

基于springboot+vue+Mysql的在线动漫信息平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

Qt | QLCDNumber 类(LCD 数字),LCD 表示液晶显示屏

01、上节回顾 Qt 基础教程合集02、QLCDNumber 1、QLCDNumber 类用于显示类似于 LCD 显示屏上的字符(见右图) ​ 2、QLCDNumber 类是 QFrame 类的直接子类,因此 QLCDNumber 以使用从 QFrame 类继承而来的边框效果 3、QLCDNumber 可显示的符号有:0,1,2,3,4,5,6,7,8,…

ue引擎游戏开发笔记(33)——武器与角色的匹配,将新武器装备到角色身上

1.需求分析&#xff1a; 武器能出现在世界中&#xff0c;完成了第一步&#xff0c;下一步需要角色和武器适配&#xff0c;即不论角色跑动&#xff0c;射击等&#xff0c;武器和角色都相匹配&#xff0c;将武器装备到角色身上。 2.操作实现&#xff1a; 1.首先先把角色原有的武…

【数据结构】--- 深入剖析二叉树(中篇)--- 认识堆堆排序Topk

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构之旅 文章目录 &#x1f3e0; 初识堆 &#x1f4d2; 堆的概念 &#x1f4d2; 堆的性质 &#x1f3e0; 向上调整算法 && 向下调整算…

vector的oj题

1.只出现1次的数字 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 方法&#xff1a;…

【Stable Diffusion】三句话,让Ai帮你画18万张图

本文介绍Stable Diffusion的快速上手&#xff0c;本地部署&#xff0c;以及更多有趣的玩法展示。 在 DALL-E 2 和 Imagen 之后&#xff0c;AI绘图领域又一个热乎的深度学习模型出炉——Stable Diffusion 。8月份发布的 Stable Diffusion 更加高效且轻量&#xff0c;可以在消费…

第六节课《Lagent AgentLego 智能体应用搭建》

PDF链接&#xff1a;https://pan.baidu.com/s/1JFtvBWgEGFWJq8pHafvIUg?pwd6666 提取码&#xff1a;6666 Lagent & AgentLego 智能体应用搭建_哔哩哔哩_bilibili https://github.com/InternLM/Tutorial/blob/camp2/agent/README.md InternStudio 一、为什么需要agent…

网页html版面分析-- BeauifulSoup(python 文档解析提取)

介绍 BeauifulSoup 是一个可以从HTML或XML 文件中提取数据的python库&#xff1b;它能通过转换器实现惯用的文档导航、查找、修改文档的方式。 BeauifulSoup是一个基于re开发的解析库&#xff0c;可以提供一些强大的解析功能&#xff1b;使用BeauifulSoup 能够提高提取数据的效…

R语言Rstudio突然无法启动?如何解决

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

由于找不到msvcp120.dll,无法继续执行代码的5种解决方法

在操作计算机的过程中&#xff0c;您或许会遇到这样一种情形&#xff1a;当试图启动某个软件应用程序时&#xff0c;系统突然弹出一个错误提示框&#xff0c;明确指出“找不到msvcp120.dll”&#xff0c;它会导致程序无法正常启动或运行。为了解决这个问题&#xff0c;我总结了…

作为全栈工程师,如何知道package.json中需要的依赖分别需要什么版本去哪里查询?

作为前端工程师&#xff0c;当你需要确定package.json中依赖的具体版本时&#xff0c;可以通过以下方法来查询&#xff1a; NPM 官网查询&#xff1a; 访问 npm 官网&#xff0c;在搜索框中输入你想查询的包名。在包的页面上&#xff0c;你可以看到所有发布过的版本号&#xff…