百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术流 > 正文

国密sm4算法

citgpt 2024-10-23 09:04 15 浏览 0 评论

一、概述

国密算法定义:即国家密码局认定的国产密码算法。

通过定义我们可以知道,国密算法有两个要素:

1、国家密码局认定

  在国家密码局官网上,可以看到由其发布的标准规范。

2、密码算法

首先知道什么是密码,密码就是将正常的信息加密后变为无法正常识别的编码,可以认为是一种混淆技术。

将明文数据通过密码算法变成密文后,有三个优点:

(1)、数据保密,混淆后的密文一般无直接意义。

(2)、数据完整,混淆后的密文缺失无法正常恢复。

(3)、身份验证,数据加解密或者对照,都要有密钥或算法的信息,一般可以认为是自己人。

  严格意义上的加密是必须保证能恢复明文信息的,但是我们平时说的一些加密,又要要求不能恢复原来的信息,如账号密码里的密码,一般是要求不允许恢复原文的。这时候就出现了一个问题,加密后还能不能恢复成加密前的样子?技术上能恢复的叫加密算法,不能恢复的叫哈希算法。能恢复明文的算法里又分为两种:对称加密和非对称加密。

  对称加密:加密时使用的密钥和解密时使用的密钥为同一个。

  非对称加密:加解密时使用的密钥是一对,公开密钥和私有密钥,公开密钥用于数据加密,私有密钥用于数据解密。

哈希算法:通过算法对明文数据进行混淆,同一个明文在同一个哈希算法下混淆结果一致。

具体分类可见下表

下图是实际开发中遇到的要求:

为什么用国密算法

(1)、国密算法算法好,速度快。

(2)、支持国产。

(3)、有些企业或部门要求使用,从最近的情况来看,还是自家的比较安全。

  与DES和AES算法相似,国密SM4算法是一种分组加密算法。SM4分组密码(block cipher)算法是一种迭代分组密码算法,由加解密算法和密钥扩展算法组成。

SM4是一种Feistel结构的分组密码算法,其分组长度和密钥长度均为128bits。加密算法和密钥扩展算法迭代轮数均为32轮。SM4加解密过程的算法相同但是轮密钥的使用顺序相反。

SM4密码算法使用模2加和循环移位作为基本运算。

密钥扩展算法:SM4算法使用128位的加密密钥,并采用32轮迭代加密结构,每一轮加密使用一个32位的轮密钥,总共使用32个轮密钥。因此需要使用密钥扩展算法,从加密密钥中产生32个轮密钥。

SM4算法使用128位的加密密钥,而UUID是一个128位的二进制数,故我们可以使用UUID来当作SM4算法的密钥,注意:需要将UUID的横杠-替换成空字符串。

二、SM4加解密流程

SM4算法的加密大致流程如下:

密钥:加密密钥的长度为128比特,表示为MK = (MK0, MK1, MK2, MK3),其中MKi为32位,

轮密钥表示为(rk0, rk1, ……, rk31),其中rki为32位。

轮函数F:假设输入为(X0, X1, X2, X3),Xi 为32位,则轮函数F为:F=(X0, X1, X2, X3, rk) = X0 ⊕ T(X1 ⊕ X2 ⊕ X3 ⊕ rk)

合成置换:T函数是一个可逆变换,由一个非线性变换r和线性变换L复合而成的,即T( )=L(r( ))

非线性变换有四个并行的S盒构成的,设输入为A=(a0, a1, a2, a3),输出为B=(b0, b1, b2, b3),其中ai和bi为8位。每个S盒的输入都是一个8位的字节,将这8位的前四位对应的16进制数作为行编号,后四位对应的16进制数作为列编号,然后用相应位置中的字节代替输入的字节。下图为S盒置换表:

 

线性变换L:线形变换的输入就是S盒的输出,即C=L(B)=B ⊕ (B<<<2) ⊕ (B<<<10) ⊕ (B<<<18) ⊕ (B<<<24),线性变换的输入和输出都是32位的。

经过了32轮的迭代运算后,最后再进行一次反序变换即可得到加密的密文,即密文C=(Y0, Y1, Y2, Y3)=R(X32. X33, X34, X35)=(X35, X34, X33, X32)。

SM4算法的解密流程和加密流程一致,只不过轮密钥的使用顺序变成了(rk31, rk30, ……, rk0)

三、密钥扩展算法

密钥参量:轮密钥由加密密钥生成。FK=(FK0, FK1, FK2, FK3)为系统参数,以及固定参数CK=(CK0, CK1, ……,  CK31),其中FKi和CKi均为32位并用于密钥扩展算法。

系统参数FK的具体取值如下:

FK0=(A3B1BAC6), FK1=(56AA3350), FK2=(677D9197), FK3=(B27022DC)

固定参数CK的具体取值如下:

密钥扩展方法:设(K0, K1, K2, K3)=(MK0⊕FK0, MK1⊕FK1, MK2⊕FK2, MK3⊕FK3)

则rki=Ki+4=Ki⊕T‘(Ki+1⊕Ki+2⊕Ki+3⊕CKi)

其中T’()是将原来的T()中的线形变换L()替换成L'(B)=B⊕(B<<<13)⊕(B<<<23)

四、Sm4Utils工具类

代码如下:

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;/**
 * 国密sm4算法
 * author: dongliyuan */public class Sm4Utils {    static {        // 防止内存中出现多次BouncyCastleProvider的实例
        if(null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }    private static final String ENCODING = "UTF-8";    private static final String ALGORITHM_NAME = "SM4";    //加密算法/分组加密模式/分组填充方式    //PKCS5Padding-以8个字节为一组进行分组加密    //定义分组加密模式使用:PKCS5Padding
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";    //128-32位16进制:256-64位16进制
    public static final int DEFAULT_KEY_SIZE = 128;    public static final String CCBID = "7bc1b525c0964716bc2f1dbc97e316be";    /**
     * 生成密钥
     * 建议使用org.bouncycastle.util.encoders.Hex将二进制转成HEX字符串
     * @return 密钥16位
     * @throws Exception     */
    public static byte[] generateKey() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(DEFAULT_KEY_SIZE, new SecureRandom());        return kg.generateKey().getEncoded();
    }    /**
     * 生成ECB暗号
     * @param algorithmName 算法名称
     * @param mode 模式
     * @param key
     * @return
     * @throws Exception     */
    private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);        return  cipher;
    }    /**
     * 加密
     * @param hexKey 16进制字符串
     * @param paramStr 待加密字符
     * @return 加密后的结果
     * @throws Exception     */
    public static String encryptEcb(String hexKey, String paramStr) throws Exception {
        String cipherText = "";        //16进制字符串 ---> byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);        //String ---> byte[]
        byte[] srcData = paramStr.getBytes(ENCODING);        //加密后的数组
        byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);        //byte[] ---> hexString
        cipherText = ByteUtils.toHexString(cipherArray);        return cipherText;
    }    /**
     * 加密模式Ecb
     * @param key
     * @param data
     * @return
     * @throws Exception     */
    public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);        return cipher.doFinal(data);
    }    /**
     * sm4解密
     * @param hexKey 16进制秘钥
     * @param cipherText 16进制的加密字符串(忽略大小写)
     * @return 解密后的字符串
     * @throws Exception     */
    public static String decryptEcb(String hexKey, String cipherText) throws Exception {        //用于接收解密后的字符串
        String decryptStr = "";        //hexString ---> byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);        // hexString --->byte[]
        byte[] cipherData = ByteUtils.fromHexString(cipherText);        //解密
        byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);        //byte[] ---> String
        decryptStr = new String(srcData);        return  decryptStr;
    }    /**
     * 解密
     * @param key
     * @param cipherText
     * @return
     * @throws Exception     */
    public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);        return cipher.doFinal(cipherText);
    }    public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {        //hexString -->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);        //将16进制字符串转换成数组
        byte[] cipherData = ByteUtils.fromHexString(cipherText);        //解密
        byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);        //将原字符串转成成byte[]
        byte[] srcData = paramStr.getBytes(ENCODING);        //判断2个数组是否一致
        return Arrays.equals(decryptData, srcData);
    }    public static void main(String[] args) throws Exception {
        System.out.println(encryptEcb(CCBID,"pts"));
        System.out.println(encryptEcb(CCBID,"ptS#1234"));
    }
}

使用工具类

我们可以使用UUID来作为SM4算法的密钥,只不过要将横杠-替换为空字符串,代码如下:

String salt = UUID.randomUUID().toString().replace("-", "");

自定义密钥:ad07f399bc438b4777bba85bfa05ca28

public class Sm4Test {    public static void main(String[] args) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("userNo","zhangsan");
        jsonObject.put("passwd","1234");
        try {
            System.out.println(Sm4Util.encryptEcb("ad07f399bc438b4777bba85bfa05ca28",JSONObject.toJSONString(jsonObject)));
            System.out.println(Sm4Util.decryptEcb("ad07f399bc438b4777bba85bfa05ca28","9a0fd3c7ad5e41681766171c08522db37065b4327a915de2dfb03d321d8df87190bbfc67ce38b9a028b8e066bff46c53"));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

结果如下:

9a0fd3c7ad5e41681766171c08522db37065b4327a915de2dfb03d321d8df87190bbfc67ce38b9a028b8e066bff46c53
{"userNo":"zhangsan","passwd":"1234"}

五、微信小程序国密算法实现库sm-crypto

1、sm-crypto安装

使用此组件需要依赖小程序基础库 2.2.1 以上版本,同时依赖开发者工具的 npm 构建。

微信小程序使用npm引入三方包详解

(1)、创建package.json文件

若没有package.json文件,直接在小程序项目根文件夹下,使用终端输入如下命令初始化环境:

npm init

注:使用该命令需要电脑安装好node环境;初次init直接一路回车即可,在package name: 输入包名 sm-crypto

查看node和npm配置情况

 若发现无法查找命令node和npm,证明还未安装nodejs,可以参考下面博客链接进行安装配置node安装进

输入yes

(2)、修改project.private.config.json配置

project.private.config.json文件配置会覆盖project.config.json文件配置,需要修改project.private.config.json里面的setting,初学者可以直接删除setting。

(3)、使用以下指令进行安装,打开终端,cd到小程序项目的根目录,在终端里执行以下命令:

npm install --save miniprogram-sm-crypto

(4)、构建 npm 

点击 工具 -> 构建 npm,构建完成,项目根目录多出miniprogram_npm文件夹

构建完成后,即可引入组件。

2、加密

引入:

const sm4 = require('miniprogram-sm-crypto').sm4

对请求体进行加密

var data = {
            userNo: userno,
            passwd: password
        };var encryptData = sm4.encrypt(JSON.stringify(data), app.globalData.key)

后台返回数据后解密

let decryptData = sm4.decrypt(res.data.result, app.globalData.key);

完整代码:


formSubmit: function(e) {        var obj = e.detail.value;        var userno = obj.userno.trim();        var password = obj.password.trim();        var data = {
            userNo: userno,
            passwd: password
        };        var encryptData = sm4.encrypt(JSON.stringify(data), app.globalData.key)        if (userno == '' || password == '') {
            wx.showToast({
                title: '请输入账号和密码',
                icon: 'none',
                duration: 2000
            })
        } else {
            wx.showLoading({
                title: '登录中...',
            });
            wx.request({
                method: 'POST',
                url: app.globalData.serverApi + "/login",
                header: {                    'content-type': 'application/json',                    'appKey': 'app_key'
                },
                data: {
                    applyData: encryptData
                },
                success(res) {
                    wx.hideLoading();                    if (res.data.code == 0) {                        //登录成功                        wx.showToast({
                            title: res.data.message,
                            icon: 'success',
                            duration: 2000
                        })                        let decryptData = sm4.decrypt(res.data.result, app.globalData.key);                        //存到缓存
                        wx.setStorageSync('userInfo', JSON.parse(decryptData));                        //跳转到首页                        wx.reLaunch({
                            url: '/pages/codeActive/index'
                        })
                    } else {                        //登录失败                        wx.showToast({
                            title: res.data.message,
                            icon: 'none',
                            duration: 2000
                        })
                    }
                }
            })
        }
    }

后台使用上面的Sm4Utils工具类,在RequestBodyAdvice中对请求体进行解密,在ResponseBodyAdvice中对要返回前端的响应体进行加密。


相关推荐

js中arguments详解

一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...

firewall-cmd 常用命令

目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...

epel-release 是什么

EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...

FullGC详解  什么是 JVM 的 GC
FullGC详解 什么是 JVM 的 GC

前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...

2024-10-26 08:50 citgpt

使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
跨域(CrossOrigin)

1.介绍  1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。    通俗讲...

微服务架构和分布式架构的区别

1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...

深入理解与应用CSS clip-path 属性
深入理解与应用CSS clip-path 属性

clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...

2024-10-25 11:51 citgpt

HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
Request.ServerVariables 大全

Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...

python操作Kafka

目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...

Runtime.getRuntime().exec详解

Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...

promise.all详解 promise.all是干什么的
promise.all详解 promise.all是干什么的

promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...

2024-10-24 16:21 citgpt

Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解

取消回复欢迎 发表评论: