当前位置: 首页 > article >正文

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;
}

http://www.kler.cn/a/324337.html

相关文章:

  • SOLIDWORKS Toolbox:一键自动化,让紧固件与零部件管理更高效
  • IC 脚本之VIM 记录
  • 网络学习第四篇
  • Spring 4.3 源码导读
  • React Native 全栈开发实战班 - 网络与数据之网络请求基础
  • 自存 关于RestController请求传参数 前端和后端相关
  • Java线程基础
  • SQL CREATE TABLE 语句
  • TypeScript概念讲解
  • DePIN 代表项目 CESS 受邀出席国会山活动,向议员展示创新 DePIN 技术
  • 阿里rtc云端录制TypeScript版NODE运行
  • HarmonyOS安全能力介绍
  • 240927-各种卷积最清晰易懂blender动画展示
  • Spark 的 Skew Join 详解
  • Spring Boot 2.4.3 + Java 8 升级为 Java 21 + Spring Boot 3.2.0
  • ubuntu 不用每次输入sudo的四种方式
  • 基于python+django+vue的电影数据分析及可视化系统
  • 滚雪球学MySQL[6.1讲]:数据备份与恢复
  • 初始MYSQL数据库(6)—— 事务
  • 什么东西可以当做GC Root,跨代引用如何处理?
  • 【LLM】从零预训练一个tiny-llama
  • python高级用法_装饰器
  • text2sql方法:NatSQL和DIN-SQL
  • 【Redis 源码】4adlist列表.md
  • 3. 轴指令(omron 机器自动化控制器)——>MC_MoveVelocity
  • 生物信息常用编辑器:轻量/强大/可定制/跨平台支持的编辑器之神 - vim