国密算法Go语言实现(详解)(八) ——SM2(椭圆曲线公钥密码算法)
原创代码:https://github.com/ZZMarquis/gm
引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。
对原创代码的修改内容
- 修改了部分常量、变量、结构体属性的名称, 以便与GO语言标准包规范相统一
- 加入中文注释,解释代码逻辑
注释者及联系邮箱
Paul Lee
paul_lee0919@163.com
// xor 函数是将国标3-6.1.A5和3-6.1.A6两步结合到一起的异或函数,
// 其计算结果返回调用来源函数kdf(),
// 从而可在计算中间变量t的同时异或、拼接获得C2:
// (1) data 为输入明文消息M
// (2) kdfOut[] 为秘钥派生函数输出缓存buf[]
// (3) dRemaining 为KDF()函数中标注输入消息数组encData[]每次调用xor()时,
// 阶段性“读”动作读取的字节数组元素个数
func xor(data []byte, kdfOut []byte, dRemaining int) {
for i := 0; i != dRemaining; i++ {
data[i] ^= kdfOut[i]
}
}
// kdf 为SM2公钥加密算法中调用秘钥派生函数的操作步骤(国标4-6.1.A5):
// (1) 按照哈希摘要字节长度创设缓存切片buf[]
// (2) 以公钥P的k倍点坐标(c1x, c1y)和输入明文消息M(长度为klen位)为输入参数
// (3) 按照国标4-5.4.3定义的秘钥派生函KDF()和国标第4-6.1.A5规定的算法推算中间变量t
// (4) t=KDF(c1x||c1y, klen), 该算法核心是迭代调用Hash(c1x||c1y||ct),其中:
// (a) ct为32位整数计数器, 从1起算
// (b) 调用次数为klen/v向上取整次
// (c) v代表哈希摘要的位数长度(SM3为256位)
// (d) 最后一次调用若明文M剩余长度小于v, 则取有值的字节
// (5) C2=M^t, 即通过xor()在计算中间变量t的过程中将中间结果与M的对应字节进行异或运算
func kdf(digest hash.Hash, c1x *big.Int, c1y *big.Int, encData []byte) {
// 4个字节为32位字长
bufSize := 4
if bufSize < digest.Size() {
// SM3哈希算法的摘要长度为32字节(256位),所以,此处取值将为32
bufSize = digest.Size()
}
buf := make([]byte, bufSize)
// 输入消息的字节数组长度,根据国标第2部分5.4.3定义,其值应小于(2^32-1)*v
// 鉴于SM3的哈希值长度v为256位,所以,klen应当小于(2^32-1)*2^8
encDataLen := len(encData)
// 加密算法中,(c1x, c1y)为公钥P的k倍点(k为加密过程中产生随机整数)
c1xBytes := c1x.Bytes()
c1yBytes := c1y.Bytes()
// encData[]元素序号“读”指针
off := 0
// 32位计数器
ct := uint32(0)
for off < encDataLen {
digest.Reset()
digest.Write(c1xBytes)
digest.Write(c1yBytes)
ct++
binary.BigEndian.PutUint32(buf, ct)
//ct为32位计数器,占4个字节,所以Write()方法仅需要读取到buf[:4]
digest.