目录
国密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步:消息填充、迭代压缩、输出杂凑值。其中迭代压缩的过程中又包括了消息扩展。
SM3的消息扩展步骤是使用512比特的数据分组作为输入的,所以,在开始的时候需要将输入的原始数据数据填充至512比特的倍数。
假设消息m的长度为l比特,填充的规则如下:
l + 1 + k = 448 mod 512 的最小非负整数;64比特的比特串,该比特串是长度l的二进制表示,采用大端存放;m'的比特长度为512的位数。
迭代的过程为先将填充后的消息m'按512比特进行分组,分成n组,其中 n=(l + k + 65) / 512
然后对分组通过压缩函数执行压缩操作,如下所示


其中CF是压缩函数,V(0)为256比特初始值IV,B(i)为填充后的消息分组,迭代压缩的结果为V(n)。
在对分组执行压缩前,需要对消息进行扩展,需要将分组扩展为132个消息字,每个消息字32比特,即4字节,在压缩函数CF中会用到。
扩展过程:
B(i)划分成16个消息字,W(0),W(1),…,W(15)。W
64个W‘
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,压缩函数CF对ABCDEFGH8个消息字及分组扩展后的消息执行64轮相同的过程。
过程中还会涉及到其它一些函数,相当复杂,具体计算的过程如下:
V(i)赋值给寄存器,第一次时也就是将初始IV赋值给寄存器的ABCDEFGH。ABCDEFGH再与B(i)进行异或运算得到V(i+1)V(i+1)用于下一分组的压缩。

当所有的分组都执行完压缩后V(n),将V(n)赋值给ABCDEFGH,输出ABCDEFGH即为256比特的杂凑值。
整体的过程可如下图表示:

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

这里我们在类中定义一个常量数组,这里我们使用int数组来存放,因为一个字为4个字节,刚好为一个int的长度。
在整个算法过程中,我们会将输入的字节转换成4字节int进行处理,最后杂凑值输出的时候再将int转换成字节数组输出。
// 初始的IV值
private static final int[] IV = {0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E};
常量数组T在压缩的过程中会用到,算法中如下对常量T进行了定义

我们对常量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)位,不明白的可以查阅资料。
布尔函数也是压缩中用到的函数,其算法如下:

因为其函数都规定了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));
}
置换函数包括P0和P1,P1在会消息扩展时使用,P0会在压缩过程中使用,定义如下:

代码如下
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);
}
定义一个消息分组的缓存,循环存放每组的消息,以字为单位,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];
定义初始化init()方法,对上面定义的一些变量进行初始化,
/**
* 初始化方法
*/
protected void init() {
byteCount = 0;
wordOff = 0;
byteBufOff = 0;
// 初始化字节数组缓存
for (int i = 0; i < byteBuf.length; i++) {
byteBuf[i] = 0;
}
// 初始化寄存器
this.V = IV;
}
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++;
}
压缩函数是整个算法的核心,压缩过程需要先进行消息扩展,处理消息扩展的方法如下
/**
* 消息扩展生成消息字
* 这里没有生成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判断是为了提升性能。
当处理完输入的数据后,调用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代码实现,希望对大家有帮助!如有错误,欢迎大家指正!
需要完整代码的可以私信发邮箱。
参考资料
1.https://www.oscca.gov.cn/sca/xxgk/2010-12/17/content_1002389.shtml
2.BC包的SM3算法
3.https://zhuanlan.zhihu.com/p/129692191
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO