国密商用密码SM3杂凑算法原理分析与Java实现

这篇具有很好参考价值的文章主要介绍了国密商用密码SM3杂凑算法原理分析与Java实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简介

国密SM3算法是我国自研设计的商用密码杂凑算法,是在SHA-256的基础上进行改造的,其安全性与SHA-256相当。《SM3密码杂凑算法》于2010年12月份由国家密码管理局首次发布。后于2012年发布为密码行业标准《GM/T 0004-2012 SM3密码杂凑算法》,2016年发布为国家密码杂凑算法标准《GB/T 32905-2016 信息安全技术 SM3密码杂凑算法》。

标准中阐述了SM3算法的基本算法、实现步骤及实现原理。 SM3和MD5的迭代过程类似,也采用Merkle-Damgard结构。消息分组长度为512位,杂凑输出值长度为256位。

SM3密码杂凑算法的执行过程包括3步:消息填充、迭代压缩、输出杂凑值。其中迭代压缩的过程中又包括了消息扩展。

二、算法原理

1. 填充

SM3的消息扩展步骤是使用512比特的数据分组作为输入的,所以,在开始的时候需要将输入的原始数据数据填充至512比特的倍数
假设消息m的长度为l比特,填充的规则如下:

  1. 首先将比特“1”填充到消息的尾部;
  2. 再添加k个“0”,k是满足l + 1 + k = 448 mod 512 的最小非负整数;
  3. 然后再添加一个64比特的比特串,该比特串是长度l的二进制表示,采用大端存放;
  4. 填充后的消息m'的比特长度为512的位数。国密商用密码SM3杂凑算法原理分析与Java实现

2. 迭代压缩

2.1 迭代过程

迭代的过程为先将填充后的消息m'按512比特进行分组,分成n组,其中 n=(l + k + 65) / 512
然后对分组通过压缩函数执行压缩操作,如下所示
国密商用密码SM3杂凑算法原理分析与Java实现
国密商用密码SM3杂凑算法原理分析与Java实现
其中CF是压缩函数,V(0)为256比特初始值IV,B(i)为填充后的消息分组,迭代压缩的结果为V(n)。

2.2 消息扩展

在对分组执行压缩前,需要对消息进行扩展,需要将分组扩展为132个消息字,每个消息字32比特,即4字节,在压缩函数CF中会用到。
扩展过程:

  1. 将消息分组B(i)划分成16个消息字,W(0),W(1),…,W(15)。
  2. 根据下面公式产生其它的W
    国密商用密码SM3杂凑算法原理分析与Java实现
  3. 根据下列公式循环产生64个W‘
    国密商用密码SM3杂凑算法原理分析与Java实现
2.3 压缩函数

SM3整个过程中最复杂、最核心的地方就是压缩函数, 令A、B、C、D、E、F、G、H字寄存器(字的存储为大端格式),每个字为32位,初始值存储的是IV
SS1、SS2、TT1、TT2为中间变量,压缩函数V(i+1) = CF(V(i),B(i)), 0 <= i <= n-1,压缩函数CFABCDEFGH8个消息字及分组扩展后的消息执行64轮相同的过程。
过程中还会涉及到其它一些函数,相当复杂,具体计算的过程如下:

  1. 首先将V(i)赋值给寄存器,第一次时也就是将初始IV赋值给寄存器的ABCDEFGH。
  2. from 0 to 63 循环执行压缩过程
  3. 将寄存器的ABCDEFGH再与B(i)进行异或运算得到V(i+1)
  4. 得到V(i+1)用于下一分组的压缩。
    国密商用密码SM3杂凑算法原理分析与Java实现
    国密商用密码SM3杂凑算法原理分析与Java实现

3. 得到杂凑值

当所有的分组都执行完压缩后V(n),将V(n)赋值给ABCDEFGH,输出ABCDEFGH即为256比特的杂凑值
整体的过程可如下图表示:
国密商用密码SM3杂凑算法原理分析与Java实现

三、代码实现

在整个代码实现中,会将输入的字节数组消息转换成32字节的整数进行运算处理,输出杂凑值时再将整数转换为字节数组。
因为将所有的消息缓存后再填充,会占用大量内存,所以采用循环分组处理。即对输入的字节消息进行顺序处理,当够一个分组的数据后就执行消息扩展、压缩,压缩后的结果缓存起来,为下一分组使用作准备,直到没有消息输入后调用final时再进行填充操作,并处理填充后的分组数据,然后产生杂凑值。

1. 常量定义

1.1 初始化的IV

算法中规定了初始化IV为32个字节的常量,
国密商用密码SM3杂凑算法原理分析与Java实现
这里我们在类中定义一个常量数组,这里我们使用int数组来存放,因为一个字为4个字节,刚好为一个int的长度。
在整个算法过程中,我们会将输入的字节转换成4字节int进行处理,最后杂凑值输出的时候再将int转换成字节数组输出。

	// 初始的IV值
	private static final int[] IV = {0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
                                     0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E};
1.2 常量T

常量数组T在压缩的过程中会用到,算法中如下对常量T进行了定义
国密商用密码SM3杂凑算法原理分析与Java实现
我们对常量T进行定义,并在静态块中进行初始化。
因为在后面的压缩过程中,常量T(j)的值会循环向左移j,所以这里初始化的时候直接完成左移操作。

    // 常量T
    private static final int[] T = new int[64];
    
    // 初始化常量T
    static
    {
        for (int i = 0; i < 16; ++i)
        {
            int t = 0x79CC4519;
            // 循环左移i位,等价于 左移i位后按位或无符号右移32-i位
            T[i] = (t << i) | (t >>> (32 - i));  
        }
        for (int i = 16; i < 64; ++i)
        {
        	// 压缩算法中有mod32
            int n = i % 32; 
            int t = 0x7A879D8A;
            T[i] = (t << n) | (t >>> (32 - n));
        }
    }

上述代码中循环左移i位,涉及到位运算,等于是向左移动i位,然后 向右无符号右移(32-i)位,不明白的可以查阅资料。

1.3 布尔函数

布尔函数也是压缩中用到的函数,其算法如下:
国密商用密码SM3杂凑算法原理分析与Java实现
因为其函数都规定了0到15,和16到63的两种情况,所以我们将FF函数分别定义为FF0和FF1,将GG函数定义为GG0和GG1

    private int FF0(final int x, final int y, final int z) {
        return (x ^ y ^ z);
    }

    private int FF1(final int x, final int y, final int z) {
        return ((x & y) | (x & z) | (y & z));
    }

    private int GG0(final int x, final int y, final int z) {
        return (x ^ y ^ z);
    }

    private int GG1(final int x, final int y, final int z) {
        return ((x & y) | ((~x) & z));
    }
1.4 置换函数

置换函数包括P0和P1,P1在会消息扩展时使用,P0会在压缩过程中使用,定义如下:
国密商用密码SM3杂凑算法原理分析与Java实现
代码如下

    private int P0(final int x) {
        final int r9 = ((x << 9) | (x >>> (32 - 9)));
        final int r17 = ((x << 17) | (x >>> (32 - 17)));
        return (x ^ r9 ^ r17);
    }

    private int P1(final int x) {
        final int r15 = ((x << 15) | (x >>> (32 - 15)));
        final int r23 = ((x << 23) | (x >>> (32 - 23)));
        return (x ^ r15 ^ r23);
    }
1.5 其它常量

定义一个消息分组的缓存,循环存放每组的消息,以字为单位,16个字,刚好为512比特。

    // 每个分组多少个消息字
    private static final int BLOCK_SIZE = 64 / 4;
    // 消息组缓存,存储的是4字节的消息字
    private int[] intWord = new int[BLOCK_SIZE];
    // 消息分组的偏移量
    private int wordOff;

定义一个4字节数组缓存,用来处理输入的字节数组消息,当满4个字节时转换成一个消息字,放入消息分组的缓存;

    private final byte[] byteBuf = new byte[4];  // 组成一个字的buff
    private int byteBufOff;  // xBuf的偏移量

定义个变量来记录输入消息的总大小

    // 比特大小,记录输入数据长度
    private long byteCount;  

定义寄存器,用来存放ABCDEFGH的值,

	// 寄存器,存初始IV和中间结果
	private int[] V = new int[8];

2. 初始化

定义初始化init()方法,对上面定义的一些变量进行初始化,

	/**
	 * 初始化方法
	 */
	protected void init() {

    byteCount = 0;
    wordOff = 0;
    byteBufOff = 0;

    // 初始化字节数组缓存
    for (int i = 0; i < byteBuf.length; i++) {
	    byteBuf[i] = 0;
    }

    // 初始化寄存器
    this.V = IV;
}

3. update方法

update方法用来接收传入的字节数组消息,并对消息进行处理。消息处理过程中,先将字节数组转换成消息字,再将消息字组成分组数据,然后对调用压缩函数,
update分为两个方法,一个是处理输入字节数组,一个是内部处理单字节。

    /**
     * 处理输入的字节数组
     * @param data  输入数据
     * @param inOff  偏移量
     * @param len  长度
     */
    public void update(byte[] data, int inOff, int len) {
        // 防止len小于0
        len = Math.max(0, len);
        int i = 0;
        // 多次update时,前一次update最后的数据可能不够一个消息字,
        // 字节缓存中还有数据,需要先进行处理
        if (byteBufOff != 0) {
            while (i < len) { // 循环处理
                byteBuf[byteBufOff++] = data[inOff + i++];
                if (byteBufOff == 4) {
                    // 处理消息
                    processWord(byteBuf, 0);
                    byteBufOff = 0;
                    break;
                }
            }
        }

        // & ~3 的目地是变成能被4整除的最大整数
        int limit = ((len - i) & ~3) + i;
        for (; i < limit; i += 4) {
            processWord(data, inOff + i);
        }

        // 剩余不够一个消息字的数据放入字节缓存中
        while (i < len) {
            byteBuf[byteBufOff++] = data[inOff + i++];
        }

        byteCount += len;
    }

    /**
     * 处理输入的单字节
     * @param in
     */
    protected void update(byte in) {
        byteBuf[byteBufOff++] = in;
        // 字节数组满了后,转换成消息字
        if (byteBufOff == byteBuf.length) {
            processWord(byteBuf, 0);
            byteBufOff = 0;
        }
        byteCount++;
    }

4. 压缩函数方法

压缩函数是整个算法的核心,压缩过程需要先进行消息扩展,处理消息扩展的方法如下

    /**
     * 消息扩展生成消息字
     * 这里没有生成W’,因为W'可以直接通过W异或得到
     */
    protected void processW() {
        for (int j = 0; j < 16; ++j)
        {
            this.W[j] = this.intWord[j];
        }
        for (int j = 16; j < 68; ++j)
        {
            int wj3 = this.W[j - 3];
            int r15 = ((wj3 << 15) | (wj3 >>> (32 - 15)));
            int wj13 = this.W[j - 13];
            int r7 = ((wj13 << 7) | (wj13 >>> (32 - 7)));
            this.W[j] = P1(this.W[j - 16] ^ this.W[j - 9] ^ r15) ^ r7 ^ this.W[j - 6];
        }
    }

压缩函数方法代码如下

    /**
     * 压缩函数
     */
    public void CF() {

        processW();

        int A = this.V[0];
        int B = this.V[1];
        int C = this.V[2];
        int D = this.V[3];
        int E = this.V[4];
        int F = this.V[5];
        int G = this.V[6];
        int H = this.V[7];

        for (int j = 0; j < 16; j++) {
            int a12 = ((A << 12) | (A >>> (32 - 12)));
            // 直接加T[j],因为前面已经mod过了
            int s1_ = a12 + E + T[j];  
            int SS1 = ((s1_ << 7) | (s1_ >>> (32 - 7)));
            int SS2 = SS1 ^ a12;
            int TT1 = FF0(A, B, C) + D + SS2 + (this.W[j] ^ this.W[j + 4]);
            int TT2 = GG0(E, F, G) + H + SS1 + this.W[j];
            D = C;
            C = ((B << 9) | (B >>> (32 - 9)));
            B = A;
            A = TT1;
            H = G;
            G = ((F << 19) | (F >>> (32 - 19)));
            F = E;
            E = P0(TT2);
        }

        for (int j = 16; j < 64; j++) {
            int a12 = ((A << 12) | (A >>> (32 - 12)));
            // 直接加T[j],因为前面已经mod过了
            int s1_ = a12 + E + T[j];  
            int SS1 = ((s1_ << 7) | (s1_ >>> (32 - 7)));
            int SS2 = SS1 ^ a12;
            int TT1 = FF1(A, B, C) + D + SS2 + (this.W[j] ^ this.W[j + 4]);
            int TT2 = GG1(E, F, G) + H + SS1 + this.W[j];
            D = C;
            C = ((B << 9) | (B >>> (32 - 9)));
            B = A;
            A = TT1;
            H = G;
            G = ((F << 19) | (F >>> (32 - 19)));
            F = E;
            E = P0(TT2);
        }

        this.V[0] ^= A;
        this.V[1] ^= B;
        this.V[2] ^= C;
        this.V[3] ^= D;
        this.V[4] ^= E;
        this.V[5] ^= F;
        this.V[6] ^= G;
        this.V[7] ^= H;

        this.wordOff = 0;
    }

可以看到上面的代码调用了两个for循环,是因为布尔函数FF、GG调用不同,没有直接在一个循环里面用if判断是为了提升性能。

5. final方法

当处理完输入的数据后,调用final来进行填充并处理最后的分组,然后输出杂凑值,同时将一些变量恢复初始化。

    /**
     * 杂凑结束
     * @param out  最终的杂凑值
     * @return
     */
    public int doFinal(byte[] out) {
        long bitLength = (byteCount << 3);

        // 最后进行填充,先填充1
        update((byte) 128);

        // 填充后不够一个字长度时,继续补0至一个字
        while (this.byteBufOff != 0) {
            update((byte)0);
        }

        // 输入字节数组消息总长度填充
        processLength(bitLength);

        // 处理最后的分组
        CF();

        // 转换成字节并输出给out
        intToBigEndian(V, out, 0);

        // 初始值恢复原样
        init();

        return DIGEST_LENGTH;
    }

处理数据长度的填充方法如下

    /**
     * 处理数据长度填充
     * @param bitLength
     */
    protected void processLength(long bitLength) {
        // wordOff=15,说明当前块不够填充64字节的长度了
        if (this.wordOff > (BLOCK_SIZE - 2)) {
            this.intWord[wordOff++] = 0;  // 填充为0
            CF();
        }

        // 填充0 直到剩两个消息字
        while (this.wordOff < (BLOCK_SIZE - 2)) {
            this.intWord[wordOff++] = 0;
        }

        // 填充64字节的长度
        this.intWord[wordOff++] = (int) (bitLength >>> 32);
        this.intWord[wordOff++] = (int) (bitLength);
    }

四、测试

完成后进行简单测试,测试代码如下

    // 测试
    public static void main(String[] args) {
        SM3_1 sm3Test = new SM3_1();
        byte[] plain = hexStringToBytes("FC7D61FD1D392EB692C5C7E0723CA637ADDA");
        byte[] plain1 = hexStringToBytes("FC");
        sm3Test.update(plain, 0, plain.length);
        sm3Test.update(plain1, 0, plain1.length);
        byte[] out = new byte[32];
        sm3Test.doFinal(out);
        System.out.println(bytes2HexString(out));
    }

测试出来的杂凑结果和网上其它工具杂凑值结果一致,说明算法实现正确。
国密商用密码SM3杂凑算法原理分析与Java实现
国密商用密码SM3杂凑算法原理分析与Java实现

以上就是国密SM3杂凑算法的原理介绍和Java代码实现,希望对大家有帮助!如有错误,欢迎大家指正!
需要完整代码的可以私信发邮箱。

参考资料
1.https://www.oscca.gov.cn/sca/xxgk/2010-12/17/content_1002389.shtml
2.BC包的SM3算法
3.https://zhuanlan.zhihu.com/p/129692191文章来源地址https://www.toymoban.com/news/detail-445117.html

到了这里,关于国密商用密码SM3杂凑算法原理分析与Java实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 国密算法SM2,SM3,SM4-java实现原理

    SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法,基于ECC。其签名速度与秘钥生成速度都快于RSA,非对称加密,该算法已公开 SM3是中华人民共和国政府采用的一种密码散列函数标准,由国家密码管理局于2010年12月17日发布。SM3主要用数字签名及验证、消息认

    2024年02月13日
    浏览(39)
  • 新手入门 | 掌握国密算法:新手指南: SM2 / SM3 / SM4密码算法详解

    在密码学领域,有多种加密与签名算法,它们在信息安全领域发挥着举足轻重的作用。如今,随着互联网的快速发展,网络安全已经成为各类信息系统完整性、可用性、保密性的重要保障,越来越多的国产密码算法得到了广泛的应用与关注。在本文中,我们将重点介绍三个经

    2024年02月12日
    浏览(145)
  • java 国密算法工具类(支持SM2 SM3 SM4)

    前言 工具采用BC库实现,支持前后端加解密,前端建议使用sm-crypto 引入pom依赖 基本使用如下 国密SM2算法 国密SM3算法 国密SM4算法使用

    2024年02月13日
    浏览(49)
  • sm3国密算法怎么玩?原理看不懂,但是急着用怎么办?

    看到这篇文章的读者,估计都会急着想知道如何使用SM3国密算法的, 好吧,我们废话不多说,直接开门见山了。 关于加密算法的一些简单的理论知识,我直接附在下方了,可以自行查看。 https://download.csdn.net/download/skyksksksksks/87490417 接下来简要说明一下国密算法,SM3国密算法

    2023年04月23日
    浏览(40)
  • 使用 Java Bouncy Castle实现国密算法SM4、SM3以及SM2的加密

    国密算法的实现借助了Java库函数 Bouncy Castle,加密库安装使用教程请参考链接 SM4,又称为商密算法,是一种分组密码算法,于2012年由中国密码技术研究中心(中国密码学会成员)发布,目前已成为我国国家密码算法,并在多个领域得到了广泛的应用。SM4算法采用了32轮迭代结

    2024年02月16日
    浏览(63)
  • 国密SM2/SM3算法

    分类 1、SM1是一种分组加密算法 对称加密算法中的分组加密算法,其分组长度、秘钥长度都是128bit,算法安全保密强度跟 AES 相当,但是算法不公开,仅以IP核的形式存在于芯片中,需要通过加密芯片的接口进行调用。 采用该算法已经研制了系列芯片、智能IC卡、智能密码钥匙

    2024年02月05日
    浏览(64)
  • 国密算法SM2、SM3的使用

    1. SM2是非对称加密算法         它是基于椭圆曲线密码的公钥密码算法标准,其秘钥长度256bit,包含数字签名、密钥交换和公钥加密,用于替换RSA/DH/ECDSA/ECDH等国际算法。可以满足电子认证服务系统等应用需求,由国家密码管理局于2010年12月17号发布。 2.SM3是一种密码杂凑

    2024年02月06日
    浏览(46)
  • JAVA集成国密SM3

    国密算法概述:https://blog.csdn.net/qq_38254635/article/details/131801527 SM3杂凑算法 SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。 SecretCommon.java Utils.java 测试类:Test.java 使用方法参考测试类即可。 SignatureCommon.java SignatureConstant.java SignatureStrategy.java SignatureS

    2024年02月07日
    浏览(37)
  • 国密算法概述、及算法的集成应用(sm2、sm3、sm4)

    由于项目的需求,需要集成国密加解密,于是对国密又温故知新了一遍。同时整理了一下国密的一些算法。 本文主要从国密相关算法的简介、应用系统的集成、工具类的封装等方面入手,对国密进行深入的学习。 为了保障商用密码的安全性,国家密码局制定了一系列密码标

    2024年02月14日
    浏览(71)
  • Go实现国密算法SM2、SM3、SM4

    SM2椭圆曲线公钥密码算法 Public key cryptographic algorithm SM2 based on elliptic curves 遵循的SM2标准号为: GM/T 0003.1-2012、GM/T 0003.2-2012、GM/T 0003.3-2012、GM/T 0003.4-2012、GM/T 0003.5-2012、GM/T 0009-2012、GM/T 0010-2012 依赖包: github.com/tjfoc/gmsm/sm2 SM3密码杂凑算法 - SM3 cryptographic hash algorithm 遵循的SM

    2024年02月15日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包