国密算法Go语言实现(详解)(十) ——SM2(椭圆曲线公钥密码算法)
原创代码:https://github.com/ZZMarquis/gm
引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。
对原创代码的修改内容
- 修改了部分常量、变量、结构体属性的名称, 以便与GO语言标准包规范相统一
- 加入中文注释,解释代码逻辑
注释者及联系邮箱
Paul Lee
paul_lee0919@163.com
国标(GMT 0003.3-2012, 以下简称“国标”) 规定的SM2算法秘钥交换协议
/*
国标(GMT 0003.3-2012, 以下简称“国标”) 规定的SM2算法秘钥交换协议
*/
// ExchangeResult 为国标规定的最后推导出的秘钥交换协议的结果:
// Key 为共享秘钥,比如SM4秘钥
// S1 为校验B用户身份的可选中间参数,其哈希函数输入参数的头部为0x02
// S2 为校验A用户身份的可选中间参数,其哈希函数输入参数的头部为0x03
type ExchangeResult struct {
Key []byte
S1 []byte
S2 []byte
}
ExchangeResult 为国标规定的最后推导出的秘钥交换协议的结果, 其中:
(1) Key 为共享秘钥,比如SM4秘钥
(2) S1 为校验B用户(应答用户)身份的可选中间参数,其哈希函数输入参数的头部为0x02
(3) S2 为校验A用户(发起用户)身份的可选中间参数,其哈希函数输入参数的头部为0x03
// reduce 为国密算法中获取(x拔)的中间函数, 详见国标6.1的A4/A6和B3/B5,其中:
// 1. Lsh() 为左位移方法,将整数1左移w位,相当于获取2^w
// 2. SetBit(x, i, b) 为设定整数x第i位为b的函数,当b为1时,相当于x | (1<<i)
// 3. x拔在国标中的定义为: 2^w + (x & (2^w - 1)), 其中:
// (1) 2^w二进制表示为w位为1,后续其他位均为0,因此
// (2) 若A = 2^w - 1, 则计算结果A的第w位的值必定为0,因此
// (2) 若B = x & A, 则与运算结果B的第w位也必定为0, 因此
// (3) SetBit (reulst, w, 1) 相当于result + 2^w
// 综上,reduce的计算结果就是 (x拔) = 2^w + (x & (2^w - 1))
func reduce(x *big.Int, w int) *big.Int {
intOne := new(big.Int).SetInt64(1)
result := util.Lsh(intOne, uint(w))
result = util.Sub(result, intOne)
result = util.And(x, result)
result = util.SetBit(result, w, 1)
return result
}
reduce 为国密算法中获取(x拔)的中间函数, 详见国标6.1的A4/A6和B3/B5,其中:
- Lsh() 为左位移方法,将整数1左移w位,相当于获取2w
- SetBit(x, i, b) 为设定整数x第i位为b的函数,当b为1时,相当于x | (1<<i)
- x拔在国标中的定义为: 2w+ (x & (2w - 1)), 其中:
// (1) 2w二进制表示为: w位为1, 后续其他位均为0,因此
// (2) 若A = 2w - 1, 则计算结果A的第w位的值必定为0,因此
// (2) 若B = x & A, 则与运算结果B的第w位也必定为0, 因此
// (3) SetBit (reulst, w, 1) 相当于result + 2w
// 综上,reduce的计算结果就是 (x拔) = 2w + (x & (2w - 1)) - 另外,分析w取值,其实为基点G阶数n二进制比特位数长度的一半,x & (2w - 1) 其实是按照n位数长度一半截取x的值,再加上2w, 相当于在w位加标签“1”
// calculateU 为推导共享秘钥(曲线上关键点U)的函数,其中:
// x1 为己方临时公钥点R1的x值所对应的x拔: x1 = 2^w + (x1 & (2^w - 1))
// x2 为对方临时公钥点R2的x值所对应的x拔: x2 = 2^w + (x2 & (2^w - 1))
// tA 为己方临时私钥r1乘x1加永久私钥d1,之后对基点阶数n取模所得的tA = (d1 + x1.r1) mod n
// sm2H 为SM2曲线余因子h, 应当为曲线点个数#E(Fq)除以基点G阶数n的商,对SM2推荐曲线而言,h=1
// 关键点U = h*tA*(P2 + x2*R2) = h*tA*P2 + h*tA*x2*R2 = k1*P2 + k2*R2
// 值得注意的是,取模计算被从tA计算,挪到了k1和k2步骤,从取模运算的乘法交换律来看,结果并没有影响,
// 但可以尽量让k2模运算后的结果更小,进而降低后续步骤的运算压力。
func calculateU(w int, selfStaticPriv *PrivateKey, selfEphemeralPriv *PrivateKey, selfEphemeralPub *PublicKey,
otherStaticPub *PublicKey, otherEphemeralPub *PublicKey) (x *big.Int, y *big.Int) {
x1 := reduce(selfEphemeralPub.X, w)
x2 := reduce(otherEphemeralPub.<