赞
踩
原创代码:https://github.com/ZZMarquis/gm
引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。
对原创代码的修改内容
注释者及联系邮箱
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.Write(buf[:4]) // 循环写入哈希值H(c1x || c2y || ct)到缓存数组buf[] tmp := digest.Sum(nil) copy(buf[:bufSize], tmp[:bufSize]) xorLen := encDataLen - off if xorLen > digest.Size() { xorLen = digest.Size() } xor(encData[off:], buf, xorLen) off += xorLen } }
kdf( ) 为SM2公钥加密算法中调用秘钥派生函数的操作步骤(国标4-6.1.A5):
// notEncrypted 为国标3-6.1.A5中判断中间变量t是否为全0比特串的判断函数。
// 如果C2与输入消息M每个字节都相等,就意味着在3-6.1.A6进行异或计算(C2=M^t)时,中间变量t所有字节均为0。
// 此时,应当重新选择随机数k,重启加密计算流程。
func notEncrypted(encData []byte, in []byte) bool {
encDataLen := len(encData)
for i := 0; i != encDataLen; i++ {
if encData[i] != in[i] {
return false
}
}
return true
}
notEncrypted( ) 为国标3-6.1.A5中判断中间变量t是否为全0比特串的判断函数。如果C2与输入消息M每个字节都相等,就意味着在3-6.1.A6进行异或计算(C2=M^t)时,中间变量t所有字节均为0。此时,应当重新选择随机数k,重启加密计算流程。
// Encrypt 为SM2加密函数: // (1) 输入参数为: 公钥PB点(pub.X, pub.Y), 明文消息字节数组 in[], 密文类别标识 cipherTextType // (2) 生成随机数k, k属于区间[1,N-1] // (3) 利用标准包elliptic的方法CurveParams.ScalarBaseMult()生成倍点C1=kG=(c1x, c1y) // (4) 由于SM2推荐曲线为素数域椭圆曲线,其余因子h=1,此时,点S=[h]PB就是公钥PB点,不可能为无穷远点O, // 所以,国标4-6.1.A3被省略 // (5) 利用标准包elliptic的方法CurveParams.ScalarBaseMult()生成倍点kPB=(kPBx, kPBy) // (6) 调用改进后的秘钥派生函数kdf(), 生成C2 func Encrypt(pub *PublicKey, in []byte, cipherTextType CipherTextType) ([]byte, error) { c2 := make([]byte, len(in)) copy(c2, in) var c1 []byte digest := sm3.New() var kPBx, kPBy *big.Int for { // 利用标准库crypto/rand获取随机数k k, err := nextK(rand.Reader, pub.Curve.N) if err != nil { return nil, err } kBytes := k.Bytes() // 利用标准库elliptic的方法CurveParams.ScalarBaseMult()计算倍点C1=kG=(c1x, c1y) c1x, c1y := pub.Curve.ScalarBaseMult(kBytes) // 将公钥曲线与C1点的坐标参数序列化。 c1 = elliptic.Marshal(pub.Curve, c1x, c1y) // 利用标准库elliptic的方法CurveParams.ScalarMult()计算倍点kPB=(kPBx, kPBy) kPBx, kPBy = pub.Curve.ScalarMult(pub.X, pub.Y, kBytes) // 利用改造后的秘钥派生函数推算C2 kdf(digest, kPBx, kPBy, c2) // 若中间变量t全部字节均为0则重启加密运算(详见国标4-6.1.A5) if !notEncrypted(c2, in) { break } } // 推算C3=Hash(kPBx || M || kPBy),详见国标4-6.1.A7 digest.Reset() digest.Write(kPBx.Bytes()) digest.Write(in) digest.Write(kPBy.Bytes()) c3 := digest.Sum(nil) // 根据密文格式标识的选择输出密文(C1C3C2新国准,或C1C2C3旧国标) c1Len := len(c1) c2Len := len(c2) c3Len := len(c3) result := make([]byte, c1Len+c2Len+c3Len) if cipherTextType == C1C2C3 { copy(result[:c1Len], c1) copy(result[c1Len:c1Len+c2Len], c2) copy(result[c1Len+c2Len:], c3) } else if cipherTextType == C1C3C2 { copy(result[:c1Len], c1) copy(result[c1Len:c1Len+c3Len], c3) copy(result[c1Len+c3Len:], c2) } else { return nil, errors.New("unknown cipherTextType:" + string(cipherTextType)) } return result, nil }
Encrypt( ) 为SM2加密函数:
// Decrypt 为SM2算法利用私钥解密(国标4-7.1)的函数: // (1) 读取C1 // (2) 反序列化同时校验C1点是否位于私钥曲线上 // (3) 校验S点(S=[h]C1)是否为无穷远点O // (4) 私钥推算倍点[d]C1 // (5) 采用改造后的kdf()函数,计算并获取解密后的明文消息M'=C2^t // (6) 计算u=Hash(c1x || M' || c2y)并与C3诸位比较 // (7) 返回解密后的明文消息M' func Decrypt(priv *PrivateKey, in []byte, cipherTextType CipherTextType) ([]byte, error) { // 根据算法字长读取C1 c1Len := ((priv.Curve.BitSize+7)/8)*2 + 1 c1 := make([]byte, c1Len) copy(c1, in[:c1Len]) // 读取C1点坐标(c1x, c1y),并校验是否位于曲线上(标准库方法elliptic.Unmarshal()内部调用) c1x, c1y := elliptic.Unmarshal(priv.Curve, c1) // 校验S点是否为无穷远点(SM2推荐曲线h为1,S点即为C1点, 本步骤可忽略) sx, sy := priv.Curve.ScalarMult(c1x, c1y, sm2H.Bytes()) if util.IsEcPointInfinity(sx, sy) { return nil, errors.New("[h]C1 at infinity") } // 根据私钥(priv.D)和曲线计算倍点[priv.D]C1=(c1x, c1y) c1x, c1y = priv.Curve.ScalarMult(c1x, c1y, priv.D.Bytes()) // 根据密文格式,分别读取C2和C3 digest := sm3.New() c3Len := digest.Size() c2Len := len(in) - c1Len - c3Len c2 := make([]byte, c2Len) c3 := make([]byte, c3Len) if cipherTextType == C1C2C3 { copy(c2, in[c1Len:c1Len+c2Len]) copy(c3, in[c1Len+c2Len:]) } else if cipherTextType == C1C3C2 { copy(c3, in[c1Len:c1Len+c3Len]) copy(c2, in[c1Len+c3Len:]) } else { return nil, errors.New("unknown cipherTextType:" + string(cipherTextType)) } // 采用改造后的kdf()函数,计算并获取解密后的明文消息M'=C2^t(国标4-7.1.B4-B5) kdf(digest, c1x, c1y, c2) // 计算u=Hash(c1x || M' || c2y)(国标4-7.1.B6-1) digest.Reset() digest.Write(c1x.Bytes()) digest.Write(c2) digest.Write(c1y.Bytes()) newC3 := digest.Sum(nil) // 将u与C3逐位比较(国标4-7.1.B6-2) if !bytes.Equal(newC3, c3) { return nil, errors.New("invalid cipher text") } // 返回明文消息M' return c2, nil }
Decrypt( ) 为SM2算法利用私钥解密(国标4-7.1)的函数:
(未完待续)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。