首页 > 编程语言 >国密sm4算法

国密sm4算法

时间:2023-10-30 11:12:33浏览次数:41  
标签:加密 String sm4 算法 国密 static 密钥 byte

一、概述

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

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

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),X为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 构建。

查看node和npm配置情况

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

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

npm install --save miniprogram-sm-crypto

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中对要返回前端的响应体进行加密。

 

标签:加密,String,sm4,算法,国密,static,密钥,byte
From: https://www.cnblogs.com/zwh0910/p/16575185.html

相关文章

  • LeetCode每日算法2—两数相加
    题目描述给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。您可以假设除了数字0之外,这两个数都不会以0开头。示例输入:(2......
  • 10.30算法
    无重复字符的最长子串给定一个字符串s,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1:输入:s="abcabcbb"输出:3解释:因为无重复字符的最长子串是"abc",所以其长度为3。示例2:输入:s="bbbbb"输出:1解释:因为无重复字符的最长子串是"b",所以其长度......
  • 基于深度学习的自动驾驶汽车语义分割与场景标注算法研究。
    自动驾驶汽车是当前研究的热点领域之一,其中基于深度学习的语义分割与场景标注算法在自动驾驶汽车的视觉感知中具有重要作用。本文将围绕自动驾驶汽车的语义分割与场景标注算法展开研究。一、研究背景随着人工智能技术的不断发展,自动驾驶汽车逐渐成为汽车产业的重要发展方向。在......
  • 【基础算法】枚举
    一、枚举思想枚举法,也称穷举法,是指在解决问题的时候穷举每一种可能的情况,最终得到符合要求的答案。枚举法的效率并不高,但适用于一些没有明显规律可循的场景。枚举的算法思想:在解决某些问题时,可能没有办法按一定规律从众多候选答案中找到正确的解。这时,可将所有候选答案逐一列出,......
  • 数据结构与算法-cnblog
    数据结构与算法课程笔记树与二叉树树的深度与高度高度就可以理解为深度看层数:如果根结点第0,层数=深度=高度-1如果根结点第1,层数=深度=高度深度定义是从上往下的,高度定义是从下往上的......
  • 重新学习算法_Day1 & 2807
    一段时间前看了《复杂度分析》和《数据结构》这两章今天又重看了一遍做了些笔记虽然感觉这些东西不用记而且感觉算法这种东西不适合记笔记但是还是写了今天把《数组和链表》看了想用Python做一道链表的题2807但是很遗憾超时了但是逻辑应该是没问题的But本来想回去把超......
  • SSA-BiLSTM麻雀算法优化双向长短期记忆神经网络的数据分类预测,多输入单输出模型
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,代码获取、论文复现及科研仿真合作可私信。......
  • 二分算法习题汇总
    一、复制书稿题目描述现在要把\(m\)本有顺序的书分给\(k\)个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数......
  • 文心一言 VS 讯飞星火 VS chatgpt (123)-- 算法导论10.4 4题
    四、用go语言,对于一个含n个结点的任意有根树,写出一个O(n)时间的过程,输出其所有关键字该树以左孩子右兄弟表示法存储。文心一言:在计算机科学中,左孩子右兄弟表示法是一种用于表示树状结构的方法,其中每个节点都有两个指针:一个指向其第一个孩子(左孩子),另一个指向其下一个兄弟(右兄弟......
  • 文心一言 VS 讯飞星火 VS chatgpt (123)-- 算法导论10.4 4题
    四、用go语言,对于一个含n个结点的任意有根树,写出一个O(n)时间的过程,输出其所有关键字该树以左孩子右兄弟表示法存储。文心一言:在计算机科学中,左孩子右兄弟表示法是一种用于表示树状结构的方法,其中每个节点都有两个指针:一个指向其第一个孩子(左孩子),另一个指向其下一个兄弟(右兄弟)。......