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

news2024/11/17 5:34:45

项目中遇到了需要与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;
}

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

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

相关文章

防伪溯源查询系统V1.0.5

多平台&#xff08;微信小程序、H5网页&#xff09;二维码扫码输码防伪溯源查询系统&#xff0c;拥有强大的防伪码生成功能&#xff08;内置多种生成规则&#xff09;、批量导出防伪码数据、支持代理商管理端&#xff08;可批量对自己防伪码进行操作处理&#xff09;、文章资讯…

SAP Message - self-explanatory 自身说明

SAP Message 解释、创建和应用可见如下文章&#xff1a;SAP Abap】SE91 - SAP MESSAGE 消息类创建与应用-CSDN博客 SE91 SAP消息类型 - tongxiaohu - 博客园 这里主要想聊一下常用的SE91 中不常用的功能 - 自身说明 选项的作用。 以 VF - 004 为例&#xff1a; 我们都知道自…

一些超好用的 GitHub 插件和技巧

聊聊我平时使用 GitHub 时学到的一些插件、技巧。 ‍ ‍ 浏览器插件 在我的另一篇博客 浏览器插件推荐 里提到过跟 GitHub 相关的一些插件&#xff0c;这里重复下&#xff1a; Sourcegraph&#xff1a;在线打开项目&#xff0c;方便阅读&#xff0c;将 GitHub 变得和 IDE …

AI名词扫盲

本篇章主要介绍一些AI研究方向的名词以及解释&#xff0c;后续会持续补充&#xff0c;名词解释与时间顺序无关&#xff0c;欢迎各位大佬们在评论区查漏补缺。 目录 AI&#xff08;Artificial Intelligence&#xff0c;人工智能&#xff09;卷积神经网络&#xff08;CNN&#xf…

权威人工智能行业认证证书——计算机视觉工程师(中级)

随着人工智能技术的快速发展&#xff0c;越来越多的人开始关注并学习人工智能。然而&#xff0c;由于人工智能领域知识的复杂性和多样性&#xff0c;许多人往往会感到困惑&#xff0c;不知道该从何入手。这时&#xff0c;一份权威的人工智能行业证书可以帮助学习者更好地了解人…

SpringBoot3.X配置OAuth

背景 之前在学习OAuth2时&#xff0c;我就有一个疑惑&#xff0c;OAuth2中有太多的配置、服务类都标注了Deprecated&#xff0c;如下&#xff1a; 显然这些写法已经过时了&#xff0c;那么官方推荐的最新写法是什么样的呢&#xff1f;当时我没有深究这些&#xff0c;我以为我放…

Gartner 报告解读(二)| Open Telemetry可观测性解读与使用建议

上期跟大家解读了Gartner 成熟度曲线报告&#xff0c;主要分享了影响中国IT使用的4大因素--自主可控计划、AI发展趋势影响、降本增效、IT基础设施现代化程度。新来的朋友点这里&#xff0c;一键了解具体内容。 Gartner 成熟度曲线报告解读&#xff08;一&#xff09;| 2024中国…

sentinel原理源码分析系列(二)-动态规则和transport

本文是sentinel原理源码分析系列第二篇&#xff0c;分析两个组件&#xff0c;动态配置和transport 动态规则 Sentinel提供动态规则机制&#xff0c;依赖配置中心&#xff0c;如nacos&#xff0c;zookeeper&#xff0c;组件支持动态配置&#xff0c;模板类型为规则&#xff0c;支…

字节跳动青训营x豆包Marscode 技术训练营报名啦!

最近字节跳动青训营又开营了&#xff0c;作为第二次参加的我来给没有了解过的同学从几个方面简单介绍一下。 青训营是什么 青训营是字节跳动 稀土掘金 社区发起的技术系列培训 & 人才选拔项目&#xff0c;面向在校大学生&#xff0c; 课程全程免费&#xff0c;包含前端、…

mov视频怎么转换成mp4?这几种转换方法值得收藏起来!

mov视频怎么转换成mp4&#xff1f;MOV格式&#xff0c;作为苹果专属的产物&#xff0c;它在非苹果体系下的兼容性常常受限&#xff0c;导致用户可能在非苹果软件平台上遭遇播放难题&#xff0c;甚至无法顺利加载视频内容&#xff0c;而且&#xff0c;MOV格式以其独特的压缩技术…

sentinel原理源码分析系列(三)-启动和初始化

本文是sentinel原理源码分析系列第三篇&#xff0c;分析sentinel启动和初始化 启动/初始化 sentinel初始化分两块&#xff0c;静态初始和适配器(包括aop) 静态初始 1. Root EntranceNode 如果我们用一栋楼类比资源调用&#xff0c;root EntranceNode好比一栋楼的大门&…

干货 | 2024制造业数字化现状调查白皮书(免费下载)

导读&#xff1a;在这本白皮书中&#xff0c;我们询问了制造商有关数字化转型的工作情况、2024 年的优先事项和可持续性。研究结果清楚地表明&#xff0c;在数字化方面处于领先地位的制造商转型项目比那些没有规划或刚刚起步的项目实现的价值要大得多。 加入知识星球或关注下方…

windows11下vscode配置lua环境

一、lua插件的安装&#xff1a; 建议安装sumneko下的lua插件&#xff1a; 安装luadebug&#xff1a; 二、运行lua配置 安装code runner插件&#xff1a; 配置code runner 配置lua运行环境&#xff1a; 运行code&#xff0c;直接run code即可&#xff1a;

学习记录:js算法(四十八):另一棵树的子树

文章目录 另一棵树的子树我的思路网上思路 总结 另一棵树的子树 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree …

图文深入理解Oracle Network配置管理(一)

List item 本篇图文深入介绍Oracle Network配置管理。 Oracle Network概述 Oracle Net 服务 Oracle Net 监听程序 <oracle_home>/network/admin/listener.ora <oracle_home>/network/admin/sqlnet.ora建立网络连接 要建立客户机或中间层连接&#xff0c;Oracle…

【嵌入式软件-数据结构与算法】01-数据结构

摘录于老师的教学课程~~(*๓╰╯๓)~~内含链表、队列、栈、循环队列等详细介绍~~ 基础知识系列 有空再继续更~~~ 目录 【链表】 一、单链表 1、存储结构&#xff1a;带头结点的单链表 2、单链表结点类型的定义 3、创建单链表 1&#xff09;头插法 2&#xff09;尾插法 …

上交所服务器崩溃:金融交易背后的技术隐患暴露杭州BGP高防服务器43.228.71.X

一、上交所宕机事件始末 2024 年 9 月 27 日&#xff0c;上交所交易系统突发崩溃&#xff0c;这一事件犹如一颗巨石投入平静的湖面&#xff0c;引起了轩然大波。当天上午&#xff0c;众多投资者反馈券商交易出现延迟问题&#xff0c;随后上交所发布了《关于股票竞价交易出现异常…

产品管理 - 互联网产品(3) : 迭代管理

1、需求文档的每一个迭代版本号&#xff0c;都需要标识出来 根据软件文档的配置标准&#xff1a; 上线时&#xff1a;X.Y 修改时&#xff1a;X.YZ 草稿时&#xff1a;0.XY 2、每一个项目干系人&#xff0c;都可以访问到最新版本的需求。 所有角色必须要有统的一认知。这是需求…

【一文读懂】通信卫星频段探秘:从L到Ka的全面介绍(增加Q/V和UHF频段)

一、引言 背景介绍&#xff1a; 卫星通信&#xff0c;作为现代通信技术的关键一环&#xff0c;利用人造卫星作为中继&#xff0c;实现了全球范围内的即时通信。这一技术极大地拓宽了通信的边界&#xff0c;对现代社会产生了深远影响。 在广播电视领域&#xff0c;卫星通信让…

Linux——K8s pod调度

rc/rsdeployment statefulset daemonsetjob | cronjob 访问pod中的应用&#xff1a;在pod已经处于running状态之下&#xff0c;客户端的请求如何到达pod中的应用&#xff1f; K8S 平台本身的网络架构设计&#xff1a; coredns 属于K8S核心组件&#xff0c;提供K8S集群…