原创代码:https://github.com/ZZMarquis/gm
引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。
对原创代码的修改内容
- 修改了部分常量、变量、结构体属性的名称, 以便与GO语言标准包规范相统一
- 加入中文注释,解释代码逻辑
- 在SM3算法中,将常数BlockSize修改为“字节长度”而不是“字长”,与GO语言标准包相统一
注释者及联系邮箱
Paul Lee
paul_lee0919@163.com
出于兴趣和业务拓展的需要,开始对Hyperledger Fabric和GO语言进行系统的学习。目前的一个主要工作目标是对Fabric 2.1进行“国密改造”。
所幸之前已有诸位大牛完成了国密算法Go语言实现的源代码开发,借用开源代码即可,不必再重新发明轮子。只是出于律师的职业病,还是严格依据国密局公布的国家标准,对代码库一一进行核实验证。在验证代码的过程中,发现了大牛ZZMarquis开发的这个GM包,逻辑清晰、充分借用了go语言标准库的既有模块,而且可以看出,基于国密推荐的加密参数结合近世代数理论,已经对算法和代码进行了相当程度的效率优化。
将学习代码逻辑、核对国标验算过程的注释,连同源代码一并在此发布,希望对后来者能有些帮助。
const (
// Size 代表SM3哈希摘要以“字节”为计量单位核算的长度。
Size = 32
// BlockSize 代表迭代压缩前,输入消息分组时,长度相等的分组数据块以“字节”为计量单位核算的长度。
BlockSize = 64
)
以上为SM3算法的两个常数设置,出于与go语言标准库的一致性考虑,将原创代码的DigestLength改成了Size。
- Size 代表SM3哈希摘要的字节长度
- BlockSize 代表消息分组的字节长度
// gT 为SM3国密算法中的常量T(j)随下标j变化而进行左移j位运算(详见国标5.3.3获取中间变量SS1的算法)的计算结果数组,其中:
// (1) (0 <= j <= 15)时, T(j) = 0x79CC4519
// (2) (16 <= j <= 63)时, T(j) = 0x7A879D8A
// (3) 左移j位和左移(j mod 32)位的运算结果相同
// (4) 具体实现的算法见函数PrintT()
var gT = []uint32{
0x79CC4519, 0xF3988A32, 0xE7311465, 0xCE6228CB, 0x9CC45197, 0x3988A32F, 0x7311465E, 0xE6228CBC,
0xCC451979, 0x988A32F3, 0x311465E7, 0x6228CBCE, 0xC451979C, 0x88A32F39, 0x11465E73, 0x228CBCE6,
0x9D8A7A87, 0x3B14F50F, 0x7629EA1E, 0xEC53D43C, 0xD8A7A879, 0xB14F50F3, 0x629EA1E7, 0xC53D43CE,
0x8A7A879D, 0x14F50F3B, 0x29EA1E76, 0x53D43CEC, 0xA7A879D8, 0x4F50F3B1, 0x9EA1E762, 0x3D43CEC5,
0x7A879D8A, 0xF50F3B14, 0xEA1E7629, 0xD43CEC53, 0xA879D8A7, 0x50F3B14F, 0xA1E7629E, 0x43CEC53D,
0x879D8A7A, 0x0F3B14F5, 0x1E7629EA, 0x3CEC53D4, 0x79D8A7A8, 0xF3B14F50, 0xE7629EA1, 0xCEC53D43,
0x9D8A7A87, 0x3B14F50F, 0x7629EA1E, 0xEC53D43C, 0xD8A7A879, 0xB14F50F3, 0x629EA1E7, 0xC53D43CE,
0x8A7A879D, 0x14F50F3B, 0x29EA1E76, 0x53D43CEC, 0xA7A879D8, 0x4F50F3B1, 0x9EA1E762, 0x3D43CEC5}
gT[ ]为SM3国密算法中的常量Tj随下标j变化而进行左移j位运算(详见国标5.3.3获取中间变量SS1的算法)的计算结果数组,其中:
- (0 <= j <= 15)时, 左移运算的初始值T0 = 0x79CC4519
- (16 <= j <= 63)时, 左移运算的初始值T16 = 0x7A879D8A
- 由于采用32位大端方式存储,所以左移j位和左移(j mod 32)位的运算结果相同
- 具体实现的算法见下文中的函数PrintT( )
// digest 为SM3算法哈希摘要类,属于私有类,仅SM3包内可以调用
type digest struct {
v [Size / 4]uint32 // v[8] 为迭代压缩运算结果的8个“字”寄存器,其存储值为中间运算结果或最终哈希值。
inWords [BlockSize / 4]uint32 // inWords[16] 为输入消息分组数据块(512位)再次拆分所形成的16个输入消息“字”数组,是后续扩展消息、迭代压缩运算的数据基础。
endOfInWords int32 // endOfInWords 代表持续写入输入消息时,inWords[]数组非空元素的尾部序号指针。
w [68]uint32 // w[68] 代表迭代压缩过程中的消息扩展“字”数组。
inBuf [4]byte // inBuf[4] 为以“字节”为单位暂存输入消息的缓存数组,其目的旨在将输入消息凑够一个“字”的长度,即4个字节。
endOfInBuf int32 // endOfInBuf 为inBuff[]数组非空元素的尾部序号指针。
lenInBytes int64 // lenInBytes 为输入消息以“字节”为单位的长度,在“填充”过程中将折算成以“比特”为单位的长度。
}
digest 为SM3算法哈希摘要类,属于私有类,仅SM3包内可以调用。其中:
- v[8] 为迭代压缩运算结果的8个“字”寄存器,其存储值为中间运算结果或最终哈希值。
- inWords[16] 为输入消息分组数据块(512位)再次拆分所形成的16个输入消息“字”数组,是后续扩展消息、迭代压缩运算的数据基础。
- endOfInWords 代表持续写入输入消息时,inWords[]数组非空元素的尾部序号指针。
- w[68] 代表迭代压缩过程中的消息扩展“字”数组。
- inBuf [ ] 为以“字节”为单位暂存输入消息的缓存数组,其目的旨在将输入消息凑够一个“字”的长度,即4个字节。
- endOfInBuf 为inBuff[]数组非空元素的尾部序号指针。
- lenInBytes 为输入消息以“字节”为单位的长度,在“填充”过程中将折算成以“比特”为单位的长度。
// New 创建digest的实例,并根据国标(GB/T 32905-2016)规定的初始值(IV)初始化字寄存器。
func New() hash.Hash {
digest := new(digest)
digest.Reset()
return digest
}
New ( ) 函数: 创建digest的实例,并根据国标(GB/T 32905-2016)规定的初始值(IV)初始化字寄存器。
// Sum() 为GO语言hash标准接口类中Sum()方法的实现。
// 其功能是将输入消息与其哈希值连接在一个字节数组中,
// 方便数字指纹核实、数字签名等应用中同时获得输入消息和其哈希值。
func (digest *digest) Sum(b []byte) []byte {
d1 := digest
h := d1.checkSum()
return append(b, h[:]...)
}
Sum( ) 为GO语言标准库hash.Hash接口类中Sum()方法的实现。其功能是将输入消息与其哈希值连接在一个字节数组中。
// Size 方法返回SM3哈希摘要以字节为计量单位核算的长度。
// 为GO语言hash标准接口类中规定的Size()方法的实现。
func (digest *digest) Size() int {
return Size
}
Size( ) 方法返回SM3哈希摘要以字节为计量单位核算的长度。为GO语言hash标准接口类中规定的Size()方法的实现。
// BlockSize 是为了提高运算效率,给开发者提供的一个查询算法消息分组数据长度的接口。
// 尽管哈希算法可将任意长度的消息进行哈希运算,但如果输入消息的长度为分组数据块长度的整数倍,
// 则可以大大提高运算效率,降低运算次数。
// 返回的是单位消息分组以“字节”为单位核算的消息长度,与GO语言标准包SHA256的口径保持一致。
func (digest *digest) BlockSize() int {
return BlockSize
}
BlockSize( ) 是为了提高运算效率,给开发者提供的一个查询算法消息分组数据长度的接口。尽管哈希算法可将任意长度的消息进行哈希运算,但如果输入消息的长度为分组数据块长度的整数倍,则可以大大提高运算效率,降低运算次数。返回的是以“字节”为单位核算的分组长度,与GO语言标准包SHA256的口径保持一致。
// Reset 为初始化或重置哈希摘要的方法。
// v[] 的首次赋值为国标规定的初始值IV。
func (digest *digest) Reset() {
digest.lenInBytes = 0
digest.endOfInBuf = 0
for i := 0; i < len(digest.inBuf); i++ {
digest.inBuf[i] = 0
}
for i := 0; i < len(digest.inWords); i++ {
digest.inWords[i] = 0
}
for i := 0; i < len(digest.w); i++ {
digest.w[i] = 0
}
digest.v[0] = 0x7380166F
digest.v[1] = 0x4914B2B9
digest.v[2] = 0x172442D7
digest.v[3] = 0xDA8A0600
digest.v[4] = 0xA96F30BC
digest.v[5] = 0x163138AA
digest.v[6] = 0xE38DEE4D
digest.v[7] = 0xB0FB0E4E
digest.endOfInWords = 0
}
Reset( )为初始化或重置哈希摘要的方法。v[ ] 的首次赋值为国标规定的初始值IV。
(待续)