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

news2024/10/6 0:37:56

本篇博文讲述如何在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));
}

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

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

相关文章

牛客NC85 拼接所有的字符串产生字典序最小的字符串【中等 排序 Java/Go/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/f1f6a1a1b6f6409b944f869dc8fd3381 思路 排序后直接拼接结果Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规…

学习软考----数据库系统工程师24

关系数据库设计基础知识 函数依赖 码 多值依赖 性质

Nginx+GateWay

目录 Nginx nginx如何配置负载均衡 负载均衡有哪些策略 1、轮询&#xff08;默认&#xff09; 2、指定权重 3、ip_hash&#xff08;客户端ip绑定&#xff09; 4、least_conn&#xff08;最少连接&#xff09; 5、fair 6、url_hash Nginx为什么效率高 gateway 使用gat…

招展工作的接近尾声“2024上海国际科技创新展会”即将盛大开幕

2024上海国际科技创新展会&#xff0c;即将于6月中旬在上海新国际博览中心盛大召开。随着招展工作的接近尾声&#xff0c;目前仍有少量余位可供各企业和机构预定。这一盛大的科技展会&#xff0c;将汇聚全球智能科技领域的精英&#xff0c;共同展示最新的科技成果&#xff0c;探…

c# - - - winform程序四个角添加圆角效果

winform 给窗体四个角添加圆角效果。 在窗体 Load 事件中添加如下代码&#xff1a; // 创建了一个圆角矩形的路径&#xff0c;并将其设置为控件的形状 System.Drawing.Drawing2D.GraphicsPath path new System.Drawing.Drawing2D.GraphicsPath(); int radius 30; path.AddAr…

将文本中的unicode字符替换成汉字

背景介绍 msql workbench导出数据库表的数据 导出的json数据 [{"english_id":1, "english_word":"ambition", "homophonic":"am-\u4ffa\uff0cbi-\u5fc5,tion-\u80dc\uff1a\u4ffa\u5fc5\u80dc", "chinese":&quo…

PWM 开发舵机SG90-硬件舵机实战

1.PWM&#xff0c;英文名Pulse Width Modulation&#xff0c;是脉冲宽度调制缩写&#xff0c;它是通过对一系列脉冲的宽度进行调制&#xff0c;等效出所需要的波形&#xff08;包含形状以及幅值&#xff09;&#xff0c;对模拟信号电平进行数字编码&#xff0c;也就是说通过调节…

Kafka分级存储概念(一)

Kafka分级存储及实现原理 概述 Kafka社区在3.6版本引入了一个十分重要的特性: 分级存储,本系列文章主要旨在介绍Kafka分级存储的设计理念、设计细节以及具体的代码实现 背景:为什么要有分级存储? 场景 作为一款具有高吞吐及高性能的消息中间件,Kafka被广泛应用在大数据、…

28 - 算术运算指令

---- 整理自B站UP主 踌躇月光 的视频 文章目录 1. ALU改进2. CPU 整体电路3. 程序4. 实验结果 1. ALU改进 此前的 ALU&#xff1a; 改进后的 ALU&#xff1a; 2. CPU 整体电路 3. 程序 # pin.pyMSR 1 MAR 2 MDR 3 RAM 4 IR 5 DST 6 SRC 7 A 8 B 9 C 10 D 11 DI 1…

Unity 性能优化之图片优化(八)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、可以提前和美术商量的事1.避免内存浪费&#xff08;UI图片&#xff0c;不是贴图&#xff09;2.提升图片性能 二、图片优化1.图片Max Size修改&#x…

OpenCampass评测实战 作业

按照如下教程文档操作即可&#xff1a;https://aicarrier.feishu.cn/wiki/NxUOwnLuvi0clykyzj7ccSHPndb

基于52单片机的AS608指纹密码锁电路原理图+源程序+PCB实物制作

目录 1、前言 2、实物图 3、PCB图 4、原理图 5、程序 资料下载地址&#xff1a;基于52单片机的AS608指纹密码锁电路原理图源程序PCB实物制作 1、前言 这是一个基于AS608STC89C52单片机的指纹识别和键盘密码锁。 里面包括程序&#xff0c;原理图&#xff0c;pcb图和实…

拉普拉斯丨独家冠名2024年度ATPV技术分论坛,助力产业科技持续创新

为了进一步促进行业技术交流&#xff0c;推进光伏行业发展及标准建设的进程&#xff0c;针对高效电池&#xff0c;领跑组件&#xff0c;新产品认证及应用等技术专题及国内外光伏标准的最新进程&#xff0c;由中国绿色供应链联盟光伏专委会&#xff08;ECOPV&#xff09;指导的2…

Linux安装Python3.9环境

大家好&#xff0c;今天给大家分享一下在Linux系统中安装Python3环境&#xff0c;Linux系统中自带的Python2尽量不要删除&#xff0c;删除后可能会导致系统出现问题。 关于Linux常用命令&#xff0c;可以参考&#xff1a;作为测试人员的Linux常用命令 一、下载Python3安装包 …

笔试强训Day17 字符串 前缀和

BC45 小乐乐改数字 题目链接&#xff1a;小乐乐改数字_牛客题霸_牛客网 (nowcoder.com) 思路&#xff1a; 水题一道 注意前导0. AC code&#xff1a; #include <iostream> #include<string> using namespace std; string a,b; int main() {cin >> a;for…

【Linux系列】tail查询使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Springboot集成Mybatispuls操作mysql数据库-03

MyBatis-Plus&#xff08;简称MP&#xff09;是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强而不做改变。它支持所有MyBatis原生的特性&#xff0c;因此引入MyBatis-Plus不会对现有的MyBatis构架产生任何影响。MyBatis-Plus旨在简化开发、提高效率&#xff0c;…

yum常用命令与lrzsz的在线安装

yum命令 yum&#xff08; Yellow dog Updater, Modified&#xff09;是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器。 基于 RPM 包管理&#xff0c;能够从指定的服务器自动下载 RPM 包并且安装&#xff0c;可以自动处理依赖性关系&#xff0c;并且一次安装…

python数据分析所需要的语法基础

Python语言基础——语法基础 前言语法基础变量标识符数据类型输入与输出代码缩进与注释 总结 前言 对于学过C语言的人来说&#xff0c;python其实很简单。学过一种语言&#xff0c;学习另一种语言&#xff0c;很显然的能感觉到&#xff0c;语言大体上都是相通的。当然&#xf…

nacos开启登录开关启动报错“Unable to start embedded Tomcat”

nacos 版本&#xff1a;2.3.2 2.2.2版本之前的Nacos默认控制台&#xff0c;无论服务端是否开启鉴权&#xff0c;都会存在一个登录页&#xff1b;在之后的版本关闭了默认登录页面&#xff0c;无需登录直接进入控制台操作。在这里我们可以在官网可以看到相关介绍 而我现在所用的…