Qt/C++ 解决调用国密SM3,SM4加密解密字符串HEX,BASE64格式转换和PKCS5Padding字符串填充相关问题

项目中遇到了需要与JAVA WEB接口使用SM3,SM4加密数据对接的需求,于是简单了解了下SM3与SM4加密算法在C++环境下的实现。并使用Qt/C++还原了在线SM3国密加密工具和在线SM4国密加密解密工具网页的示例功能的实现

目录导读

  • 前言
    • SM3算法简介
    • SM4算法简介
  • 实现示例
    • 字符串HEX,BASE64格式转换
      • 使用CryptBinaryToString 函数
      • 使用QByteArray类中的静态函数
    • 使用PKCS5Padding标准填充
    • 代码示例

前言

简单介绍下SM3,SM4的C++算法

  • SM3算法简介

SM3是中华人民共和国政府采用的一种密码散列函数标准,由国家密码管理局于2010年12月17日发布。相关标准为“GM/T 0004-2012 《SM3密码杂凑算法》”。
在商用密码体系中,SM3主要用于数字签名及验证、消息认证码生成及验证、随机数生成等,其算法公开。据国家密码管理局表示,其安全性及效率与SHA-256相当。
摘要出自:在线SM3国密加密工具

  • SM3加密C++代码实现:

SM3密码杂凑算法 国密pdf文件说明 文档看着也比较头疼,这里直接借鉴使用 SM3算法C语言实现-下载中的代码,实测没任何问题,这里不贴出代码,如有需要请前往下载。

  • 参考文章:

国密SM3杂凑算法与实现
SM3算法C语言实现-下载
国密SM3算法在linux和windows平台结果不一致问题
sm3算法实现
SM3密码杂凑算法 国密pdf文件说明


SM4算法简介

SM4.0(原名SMS4.0)是中华人民共和国政府采用的一种分组密码标准,由国家密码管理局于2012年3月21日发布。相关标准为“GM/T 0002-2012《SM4分组密码算法》(原SMS4分组密码算法)”。
在商用密码体系中,SM4主要用于数据加密,其算法公开,分组长度与密钥长度均为128bit,加密算法与密钥扩展算法都采用32轮非线性迭代结构,S盒为固定的8比特输入8比特输出。
SM4.0中的指令长度被提升到大于64K(即64×1024)的水平,这是SM 3.0规格(渲染指令长度允许大于512)的128倍。
摘要出自:在线SM4国密加密解密工具

  • SM4加密C++代码实现:

同样SM4的C++代码直接使用国密SM4分组加密算法实现 (C++)中实现的SM4源码,
可以直接在国密SM4分组加密算法实现 (C++)文章末尾下载,如有需要请前往下载。

  • 参考文章:

SM4国密算法实现分析
国密SM4分组加密算法实现 (C++)


实现示例

尝试使用Qt Creator 5.13.1 MSCV2017编写一个实现和在线SM4国密加密工具一样的功能的Demo,
整个样式也是借鉴的在线SM4国密加密工具界面样式。

  • 如下图示:

在这里插入图片描述
在这里插入图片描述

示例执行程序见文章附件资源;


在线SM4国密加密工具中的功能实现其实并不复杂,
SM3,SM4的C++算法实现都可以通过上面的链接下载后直接使用,
再通过HEX,BASE64字符串格式转换字符串填充就能直接输出一致的加密解密内容

  • 字符串HEX,BASE64格式转换

在进行数据加密的时候需要将PBYTE(unsigned char*)字符串转换为HEX(十六进制) 或者 Base64格式
一开始我是打算使用CryptBinaryToStringW函数进行转换,
后来发现QByteArray类能直接转换成HEX(十六进制)Base64格式字符串

  • 使用CryptBinaryToString 函数

CryptBinaryToString 函数将字节数组转换为格式化字符串。
传入PBYTE数据字符串并指定长度,
并输出标准化的HEX(16进制)或BASE64字符串

  • 语法:
BOOL CryptBinaryToStringW(
  //!指向要转换为字符串的字节数组的指针。
  [in]            const BYTE *pbBinary,
  //! pbBinary 数组中的元素数。
  [in]            DWORD      cbBinary,
  //! 指定生成的格式化字符串的格式 CRYPT_STRING_HEX or CRYPT_STRING_BASE64
  [in]            DWORD      dwFlags,
  //! 指向接收转换字符串的缓冲区的指针。 若要计算必须分配以保存返回的字符串的字符数,请将此参数设置为 NULL。 函数会将所需数量的字符(包括终止 NULL 字符)放在 pcchString 指向的值中
  [out, optional] LPWSTR     pszString,
  //! 指向 DWORD 变量的指针,该变量包含 pszString 缓冲区的大小(以 TCHAR为单位)。 如果 pszString 为 NULL,则函数计算返回字符串的长度, (包括 TCHARs 中的终止 null 字符) ,并在此参数中返回它。 如果 pszString 不为 NULL 且不够大,则该函数会将二进制数据转换为指定的字符串格式(包括终止 null 字符),但 pcchString 接收 TCHARs 的长度,不包括终止 null 字符。
  [in, out]       DWORD      *pcchString
);
  • C++代码示例:

引用头文件:

#include <Windows.h>
#include <wincrypt.h>
#include <string.h>
#include <stdio.h>
#pragma comment(lib, "Crypt32.lib")
class WinApi_Binary
{
public:
    WinApi_Binary();


    /*
     * https://learn.microsoft.com/zh-cn/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringw
        CryptBinaryToStringW
    */

    //! 将PBYTE转换成十六进制字符串
    QString TO_HEX32(const BYTE * pbBinary);

    //! 将PBYTE转换成BASE64字符串
    QString TO_BASE64(const BYTE * pbBinary);

private:
    //! CryptBinaryToString 函数将字节数组转换为格式化字符串。
    //! https://learn.microsoft.com/zh-cn/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringw
    QString ToCryptBinaryToString(const BYTE *pbBinary,DWORD cbBinary,DWORD dwFlags);
};

具体实现:

WinApi_Binary::WinApi_Binary()
{
}
QString WinApi_Binary::ToCryptBinaryToString(const BYTE *pbBinary,DWORD cbBinary,DWORD dwFlags)
{
    DWORD pcchString=0;
    QString ValString="";
    if(CryptBinaryToStringW(pbBinary,cbBinary,dwFlags,NULL,&pcchString))
    {
        if(pcchString==0)
            return ValString;

        LPWSTR pszString=new TCHAR[pcchString];
        if(CryptBinaryToStringW(pbBinary,cbBinary,dwFlags,pszString,&pcchString))
        {
            ValString=QString::fromWCharArray(pszString);
        }
        delete pszString;
        pszString=nullptr;
    }
    return ValString;

}


QString WinApi_Binary::TO_HEX32(const BYTE * pbBinary)
{
    return  ToCryptBinaryToString(pbBinary,32,CRYPT_STRING_HEX|CRYPT_STRING_NOCRLF);
}

QString WinApi_Binary::TO_BASE64(const BYTE * pbBinary)
{
    return  ToCryptBinaryToString(pbBinary,32,CRYPT_STRING_BASE64|CRYPT_STRING_NOCRLF);
}

CryptBinaryToStringW 转换字符串的使用还是过于繁琐了,使用Qt自带的QByteArray类转换字符串更快更方便;


  • 使用QByteArray类中的静态函数

使用
QByteArray::fromHex(Text.toLatin1())
QByteArray::fromBase64(Text.toLatin1())
获取base64,hex(16进制)格式数据;
使用
QByteArray((char*)DestBuffer,actualen).toHex().toUpper();
QByteArray((char*)DestBuffer,actualen).toBase64()
转换成字base64,hex(16进制)格式符串,
其中DestBuffer是PBYTE类型,actualen是字符长度;
使用QByteArray类进行字符串格式转换比较简单。直接可以转换成QString输出文本。


  • 使用PKCS5Padding标准填充

SM3加密不需要进行对字符串进行填充,
SM4加密时需要对字符串进行PKCS5Padding标准填充,否则计算除了的加密/解密字符串会与在线SM4国密加密工具计算出来的结果不一致。

在通过SM4加密与在线SM4国密加密工具计算出来的结果对比失败N次后才发现需要进行PKCS5Padding标准字符串填充,PKCS5Padding填充规则并不复杂:

PKCS5Padding:填充的原则是,如果长度少于16个字节,需要补满16个字节,补(16-len)个(16-len)例如:
huguozhen这个节符串是9个字节,16-9= 7,补满后如:huguozhen+7个十进制的7
如果字符串长度正好是16字节,则需要再补16个字节的十进制的16。
参考出自:关于C++和JAVA,AES/ECB/PKCS5Padding 互相通信的问题

  • C++代码实现:
//! input 需要填充的字符串
//! inputLen 字符串长度
//! actualen 填充后的字符串长度
//! 返回填充后的字符串
unsigned char * PKCS5Padding(unsigned char *input,int inputLen,int& actualen)
{
    actualen=0;
    int diff=0;
    if(inputLen%16==0)
    {
        actualen=inputLen+16;
        diff=16;
    }
    else
    {
       int num= (int)ceil((double)inputLen/16.0);
        actualen=num*16;
        diff=actualen-inputLen;
    }
    qDebug()<<"[len] "<<inputLen<<" [actualen] "<<actualen<<" [diff] "<<diff;
    unsigned char * SrcBuffer=(unsigned char *)malloc(sizeof (unsigned char)*actualen);
    memset(SrcBuffer, diff, actualen);
    for(int i=0;i<inputLen;i++)
    {
        SrcBuffer[i]=input[i];
    }
    return SrcBuffer;
}

SM4加密填充,解密后同样需要移除填充的内容:

因为加密时补的是十进制1到16,解密时,需要把这部分补位的去掉,判断要解密的字符串,每个字节是不是 char>=1 && char<= 16,如果是的话,就用0来替换以前的值,直到结束。
参考出自:关于C++和JAVA,AES/ECB/PKCS5Padding 互相通信的问题

通过上述对字符串进行HEX,BASE64格式转换再PKCS5Padding填充后,
加密/解密的结果基本就和在线SM4国密加密工具一致了。


  • 代码示例

这里简单演示调用上面的SM3和SM4加密的C++的代码示例;

  • SM3加密示例

使用libsm3.sm3(Data, Lens, OutText)直接调用加密,这是上面SM3加密C++ Demo中已经实现的方法,
使用QTextCodec进行文本字符串格式转换,测试使用Utf-8和Gb2312与在线工具输出一致。
使用QByteArray 输入输出HEX(16进制)或Base64格式数据

QString Operate_Command::SM3_Encrypt(QString Text,int TextType,QString codeType,int outType)
{
    QByteArray encodedData;
    switch (TextType) {
    case 0:
    {
        //! 文本
        QTextCodec * Codec=QTextCodec::codecForName(codeType.toStdString().c_str());
        qDebug()<<"Codec: "<<Codec->name();
        encodedData = Codec->fromUnicode(Text);
        break;
    }
    case 1:
    {
        //! HEX
        encodedData=QByteArray::fromHex(Text.toLatin1());
        break;
    }
    case 2:
    {
        //! BASE64
        encodedData=QByteArray::fromBase64(Text.toLatin1());
        break;
    }
    }

    Lib_SM3 libsm3;
    //! 字符长度
    int Lens=encodedData.size();
    PBYTE Data=(PBYTE)encodedData.data();
    PBYTE OutText=new BYTE[32];
    //! SM3加密
    libsm3.sm3(Data, Lens, OutText);

   // WinApi_Binary binary;
    QString middlekey="";
    //! 加密输出格式
    switch (outType) {
    case 0:
    {
        //! 输出 HEX 十六进制
        middlekey=QByteArray((char*)OutText,32).toHex().toUpper();
        break;
    }
    case 1:
    {
        //! 输出 base64格式
        middlekey=QByteArray((char*)OutText,32).toBase64();
        break;
    }
    }

    return middlekey;
}
  • SM4加密示例

使用SM4 ECB PKCS5Padding加密示例:
先使用sm4_setkey_enc函数设置密钥,
在通过PKCS5Padding填充字符串,
在使用sm4_crypt_ecb函数加密内容,

sm4_setkey_enc函数sm4_crypt_ecb函数是上面SM4加密C++ Demo中已经实现的方法,只需要调用。

代码示例:

QString Operate_Command::SM4_Encrypt_ECB(QString Key,int keyType,QString Text,int TextType,QString codeType,int outType)
{
qDebug()<<" --- --- ---> ";
    QByteArray encodedData;
    switch (TextType) {
    case 0:
    {
        //! 文本
        QTextCodec * Codec=QTextCodec::codecForName(codeType.toStdString().c_str());
        encodedData = Codec->fromUnicode(Text);
        break;
    }
    case 1:
    {
        //! HEX
        encodedData=QByteArray::fromHex(Text.toLatin1());
        break;
    }
    case 2:
    {
        //! BASE64
        encodedData=QByteArray::fromBase64(Text.toLatin1());
        break;
    }
    }
    qDebug("%s",encodedData.data());

    QByteArray keyData;
    switch (keyType) {
    case 0:
    {
        //! 文本
        QTextCodec * Codec=QTextCodec::codecForName(codeType.toStdString().c_str());
        keyData = Codec->fromUnicode(Key.mid(0,16));
        break;
    }
    case 1:
    {
        //! HEX
        keyData=QByteArray::fromHex(Key.toLatin1());
        break;
    }
    case 2:
    {
        //! BASE64
        keyData=QByteArray::fromBase64(Key.toLatin1());
        break;
    }
    }
    qDebug("%s",keyData.data());

    int length=encodedData.size();
    PBYTE SrcBuffer=(PBYTE)encodedData.data();
    PBYTE keyBuffer=(PBYTE)keyData.data();

    Lib_SM4 libsm4;

    sm4_context ctx;
    //设置 加密秘钥
    libsm4.sm4_setkey_enc(&ctx,keyBuffer);
    //字符串填充
    int actualen=length;
    PBYTE ToPKCS5Pad=libsm4.PKCS5Padding(SrcBuffer,length,actualen);
    PBYTE DestBuffer=new BYTE[actualen];
    //加密
    libsm4.sm4_crypt_ecb(&ctx, 1, actualen, ToPKCS5Pad, DestBuffer); //1 为加密 0为解密

    QString middlekey="";
    //! 加密输出格式
    switch (outType) {
    case 1:
    {
        //! 输出 HEX 十六进制
        middlekey=QByteArray((char*)DestBuffer,actualen).toHex().toUpper();
        break;
    }
    case 0:
    case 2:
    {
        //! 输出 base64格式
        middlekey=QByteArray((char*)DestBuffer,actualen).toBase64();
        break;
    }
    }

    QString VStr="";
    QString SRCSTR="";
    for(int i=0;i<actualen;i++)
    {
        VStr+=QString("%1").arg(DestBuffer[i],2,16,QLatin1Char('0'));
        SRCSTR+=QString("%1 ").arg(ToPKCS5Pad[i],2,16,QLatin1Char('0'));
    }
    qDebug()<<"[VStr] "<<VStr;
    qDebug()<<"[SRCSTR] "<<SRCSTR;

    delete DestBuffer;
    DestBuffer=nullptr;
    free(ToPKCS5Pad);

    return middlekey;
}

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

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

相关文章

慢病中医药膳养生食疗管理微信小程序、基于微信小程序的慢病中医药膳养生食疗管理系统设计与实现、中医药膳养生食疗管理微信小程序的开发与应用(源码+文档+定制)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

计算机网络:计算机网络体系结构 —— 专用术语总结

文章目录 专用术语实体协议服务服务访问点 SAP 服务原语 SP 协议数据单元 PDU服务数据单元 SDU 专用术语 实体 实体是指任何可以发送或接收信息的硬件或软件进程 对等实体是指通信双方处于相同层次中的实体&#xff0c;如通信双方应用层的浏览器进程和 Web 服务器进程。 协…

docker 部署 Seatunnel 和 Seatunnel Web

docker 部署 Seatunnel 和 Seatunnel Web 说明&#xff1a; 部署方式前置条件&#xff0c;已经在宿主机上运行成功运行文件采用挂载宿主机目录的方式部署SeaTunnel Engine 采用的是混合模式集群 编写Dockerfile并打包镜像 Seatunnel FROM openjdk:8 WORKDIR /opt/seatunne…

【在Linux世界中追寻伟大的One Piece】System V共享内存

目录 1 -> System V共享内存 1.1 -> 共享内存数据结构 1.2 -> 共享内存函数 1.2.1 -> shmget函数 1.2.2 -> shmot函数 1.2.3 -> shmdt函数 1.2.4 -> shmctl函数 1.3 -> 实例代码 2 -> System V消息队列 3 -> System V信号量 1 -> Sy…

基于两分支卷积和 Transformer 的轻量级多尺度特征融合超分辨率网络 !

当前的单图像超分辨率&#xff08;SISR&#xff09;算法有两种主要的深度学习模型&#xff0c;一种是基于卷积神经网络&#xff08;CNN&#xff09;的模型&#xff0c;另一种是基于Transformer的模型。前者利用不同卷积核大小的卷积层堆叠来设计模型&#xff0c;使得模型能够更…

OpenFeign微服务部署

一.开启nacos 和redis 1.查看nacos和redis是否启动 docker ps2.查看是否安装nacos和redis docker ps -a3.启动nacos和redis docker start nacos docker start redis-6379 docker ps 二.使用SpringSession共享例子 这里的两个例子在我的一个博客有创建过程&#xff0c…

rtmp协议转websocketflv的去队列积压

websocket server的优点 websocket server的好处&#xff1a;WebSocket 服务器能够实现实时的数据推送&#xff0c;服务器可以主动向客户端发送数据 1 不需要客户端不断轮询。 2 不需要实现httpserver跨域。 在需要修改协议的时候比较灵活&#xff0c;我们发送数据的时候比较…

Linux云计算 |【第四阶段】RDBMS1-DAY3

主要内容&#xff1a; 子查询&#xff08;单行单列、多行单列、单行多列、多行多列&#xff09;、分页查询limit、联合查询union、插入语句、修改语句、删除语句 一、子查询 子查询就是指的在一个完整的查询语句之中&#xff0c;嵌套若干个不同功能的小查询&#xff0c;从而一…

安宝特案例 | 某知名日系汽车制造厂,借助AR实现智慧化转型

案例介绍 在全球制造业加速数字化的背景下&#xff0c;工厂的生产管理与设备维护效率愈发重要。 某知名日系汽车制造厂当前面临着设备的实时监控、故障维护&#xff0c;以及跨地域的管理协作等挑战&#xff0c;由于场地分散和突发状况的不可预知性&#xff0c;传统方式已无法…

大模型部署——NVIDIA NIM 和 LangChain 如何彻底改变 AI 集成和性能

DigiOps与人工智能 人工智能已经从一个未来主义的想法变成了改变全球行业的强大力量。人工智能驱动的解决方案正在改变医疗保健、金融、制造和零售等行业的企业运营方式。它们不仅提高了效率和准确性&#xff0c;还增强了决策能力。人工智能的价值不断增长&#xff0c;这从它处…

Html 转为 MarkDown

在 RAG 中,通常需要将 HTML 转为 Markdown,有很多第三方 API 都支持 HTML 的转换,本文使用一个代码文档的例子 https://www.joinquant.com/help/api/help#name:Stock,将聚宽 API 转为 Markdown。本文通过两种方式进行实现,使用收费和开源的解决方案。聚宽 API 格式转为 Ma…

【Linux】几种常见配置文件介绍

配置文件目录 linux 系统中有很多配置文件目录 /etc/systemd/system /lib/systemd/system /usr/lib/systemd/system 【结果就是这个目录配置文件是源头】 这三者有什么样的关系呢&#xff1f; 以下是网络上找的资料汇总&#xff0c;并加了一些操作验证。方便后期使用 介…

鸿蒙NEXT开发环境搭建(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

Linux 进程的基本概念及描述

目录 0.前言 1. 什么是进程 1.1 进程的定义与特性 1.2 进程与线程的区别 2.描述进程 2.1 PCB (进程控制块) 2.2 task_struct 3.查看进程 3.1 查看进程信息 3.1.1 /proc 文件系统 3.1.2 ps 命令 3.1.2 top 和 htop 命令 3.2 获取进程标识符 3.2.1使用命令获取PID 3.2.2 使用C语言…

中原台球展,2025郑州台球展会,中国台球产业链发展大会

阳春三月&#xff0c;万物复苏&#xff0c;商机无限&#xff1b;品牌宣传正当季&#xff0c;产品招商正当时&#xff0c;新品发布好时期。抓住台球发展的这波财富机遇&#xff0c;借助壹肆柒郑州台球展这个超级平台&#xff0c;将品牌和产品快速打造成为覆盖全国市场的顶流。20…

数据治理003-数据域

数据仓库是面向主题&#xff08;数据综合、归类并进行分析利用的抽象&#xff09;的应用。 数据仓库模型设计除横向的分层外&#xff0c;通常也需要根据业务情况进行纵向划分数据域。数据域是联系较为紧密的数据主题的集合&#xff0c;通常是根据业务类别、数据来源、数据用途…

InternLM + LlamaIndex RAG 实践

llamaindexInternlm2 RAG实践 参考教程 正式介绍检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09;技术以前&#xff0c;大家不妨想想为什么会出现这样一个技术。 给模型注入新知识的方式&#xff0c;可以简单分为两种方式&#xff0c;一种…

线性代数(持续更新)

一.矩阵及其计算 1.矩阵的概念 矩阵就是一个数表 元素全是0&#xff0c;是零矩阵&#xff0c;用0来表示 当mn时&#xff0c;称为n阶矩阵&#xff08;方阵&#xff09; 只有一行的叫行矩阵&#xff0c;只有一列的叫列矩阵 只有对角线有元素的叫做对角矩阵&#xff0c;用dia…

(Linux驱动学习 - 4).Linux 下 DHT11 温湿度传感器驱动编写

DHT11的通信协议是单总线协议&#xff0c;可以用之前学习的pinctl和gpio子系统完成某IO引脚上数据的读与写。 一.在设备树下添加dht11的设备结点 1.流程图 2.设备树代码 &#xff08;1&#xff09;.在设备树的 iomuxc结点下添加 pinctl_dht11 &#xff08;2&#xff09;.在根…

HuggingChat macOS 版现已发布

Hugging Face 的开源聊天应用程序 Hugging Chat&#xff0c;现已推出适用于 macOS 的版本。 主要特点 Hugging Chat macOS 版本具有以下亮点: 强大的模型支持: 用户可以一键访问多个顶尖的开源大语言模型&#xff0c;包括 Qwen 2.5 72B、Command R、Phi 3.5、Mistral 12B 等等&…