国密算法Go语言实现(详解)(九) ——SM2(椭圆曲线公钥密码算法)


原创代码:https://github.com/ZZMarquis/gm

引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。

对原创代码的修改内容

  1. 修改了部分常量、变量、结构体属性的名称, 以便与GO语言标准包规范相统一
  2. 加入中文注释,解释代码逻辑
  3. 将PublicKey设置为PrivateKey的成员属性,与GO语言标准包规范统一

注释者及联系邮箱

Paul Lee
paul_lee0919@163.com

// sm2Signature 代表SM2算法的数字签名类。
type sm2Signature struct {R, S *big.Int
}// MarshalCipher 为SM2算法密文对象序列化公共函数:
// (1) 将字节数组中保存的SM2密文对象截取出来
// (2) 将截取出来的数据赋值给SM2密文对象的各相关属性
// (3) 将SM2密文对象序列化为符合ASN.1标准DER编码规则的密文字节串
// (4) SM2密文对象的具体规范请见国标(GB/T 35276-2017)
func MarshalCipher(in []byte, cipherTextType CipherTextType) ([]byte, error) {// 将椭圆曲线的位数转化为字节数byteLen := (sm2P256V1.Params().BitSize + 7) >> 3c1x := make([]byte, byteLen)c1y := make([]byte, byteLen)// 将in[]按C1,C2,C3长度进行拆分c2Len := len(in) - (1 + byteLen*2) - sm3.Sizec2 := make([]byte, c2Len)c3 := make([]byte, sm3.Size)pos := 1// 拆分获取c1x, c1ycopy(c1x, in[pos:pos+byteLen])pos += byteLencopy(c1y, in[pos:pos+byteLen])pos += byteLennc1x := new(big.Int).SetBytes(c1x)nc1y := new(big.Int).SetBytes(c1y)// 根据新旧国标的格式标识拆分C2和C3if cipherTextType == C1C2C3 {copy(c2, in[pos:pos+c2Len])pos += c2Lencopy(c3, in[pos:pos+sm3.Size])result, err := asn1.Marshal(sm2CipherC1C2C3{nc1x, nc1y, c2, c3})if err != nil {return nil, err}return result, nil} else if cipherTextType == C1C3C2 {copy(c3, in[pos:pos+sm3.Size])pos += sm3.Sizecopy(c2, in[pos:pos+c2Len])result, err := asn1.Marshal(sm2CipherC1C3C2{nc1x, nc1y, c3, c2})if err != nil {return nil, err}return result, nil} else {return nil, errors.New("unknown cipherTextType:" + string(cipherTextType))}
}

MarshalCipher( ) 为SM2算法密文对象序列化公共函数:

  • (1) 将字节数组中保存的SM2密文对象截取出来
  • (2) 将截取出来的数据赋值给SM2密文对象的各相关属性
  • (3) 将SM2密文对象序列化为符合ASN.1标准DER编码规则的密文字节串
  • (4) SM2密文对象的具体规范请见国标(GB/T 35276-2017)
// UnmarshalCipher 为SM2算法密文对象反序列化公共函数:
// (1) 将符合ASN.1标准DER编码规则的密文字节串反序列化为SM2密文对象
// (2) 将SM2密文对象的各相关属性的值读出来并按规范存入字节数组
// (3) SM2密文对象的具体规范请见国标(GB/T 35276-2017)
func UnmarshalCipher(in []byte, cipherTextType CipherTextType) (out []byte, err error) {if cipherTextType == C1C2C3 {cipher := new(sm2CipherC1C2C3)_, err = asn1.Unmarshal(in, cipher)if err != nil {return nil, err}c1x := cipher.X.Bytes()c1y := cipher.Y.Bytes()c1xLen := len(c1x)c1yLen := len(c1y)c2Len := len(cipher.C2)c3Len := len(cipher.C3)result := make([]byte, 1+c1xLen+c1yLen+c2Len+c3Len)pos := 0result[pos] = UnCompresspos += 1copy(result[pos:pos+c1xLen], c1x)pos += c1xLencopy(result[pos:pos+c1yLen], c1y)pos += c1yLencopy(result[pos:pos+c2Len], cipher.C2)pos += c2Lencopy(result[pos:pos+c3Len], cipher.C3)return result, nil} else if cipherTextType == C1C3C2 {cipher := new(sm2CipherC1C3C2)_, err = asn1.Unmarshal(in, cipher)if err != nil {return nil, err}c1x := cipher.X.Bytes()c1y := cipher.Y.Bytes()c1xLen := len(c1x)c1yLen := len(c1y)c2Len := len(cipher.C2)c3Len := len(cipher.C3)result := make([]byte, 1+c1xLen+c1yLen+c2Len+c3Len)pos := 0result[pos] = UnCompresspos += 1copy(result[pos:pos+c1xLen], c1x)pos += c1xLencopy(result[pos:pos+c1yLen], c1y)pos += c1yLencopy(result[pos:pos+c3Len], cipher.C3)pos += c3Lencopy(result[pos:pos+c2Len], cipher.C2)return result, nil} else {return nil, errors.New("unknown cipherTextType:" + string(cipherTextType))}
}

UnmarshalCipher( ) 为SM2算法密文对象反序列化公共函数:

  • (1) 将符合ASN.1标准DER编码规则的密文字节串反序列化为SM2密文对象
  • (2) 将SM2密文对象的各相关属性的值读出来并按规范存入字节数组
  • (3) SM2密文对象的具体规范请见国标(GB/T 35276-2017)
// getZ 为SM2签名算法的第1步预处理函数,即,以签名方身份标识和公钥信息为基础获取Z值:
// (1) 首2个字节存储用户ID的比特长度ENTL
// (2) 之后存储用户ID的字节串
// (3) 之后顺次存储a, b, XG, YG四个椭圆曲线定义参数
// (4) 之后顺次存储签名方公钥PA点的坐标XA和YA
// (5) 输入参数的接口类hash.Hash,将由SM3算法具体实现,详见调用来源
// (6) 具体算法见国标2-5.5
func getZ(digest hash.Hash, curve *P256V1Curve, pubX *big.Int, pubY *big.Int, userID []byte) []byte {digest.Reset()userIDLen := uint16(len(userID) * 8)var userIDLenBytes [2]bytebinary.BigEndian.PutUint16(userIDLenBytes[:], userIDLen)digest.Write(userIDLenBytes[:])if userID != nil && len(userID) > 0 {digest.Write(userID)}digest.Write(curve.A.Bytes())digest.Write(curve.B.Bytes())digest.Write(curve.Gx.Bytes())digest.Write(curve.Gy.Bytes())digest.Write(pubX.Bytes())digest.Write(pubY.Bytes())return digest.Sum(nil)
}

getZ( ) 为SM2签名算法的第1步预处理函数,即,以签名方身份标识和公钥信息为基础获取Z值:

  • (1) 首2个字节存储用户ID的比特长度ENTL
  • (2) 之后存储用户ID的字节串
  • (3) 之后顺次存储a, b, XG, YG四个椭圆曲线定义参数
  • (4) 之后顺次存储签名方公钥PA点的坐标XA和YA
  • (5) 输入参数的接口类hash.Hash,将由SM3算法具体实现,详见调用来源
  • (6) 具体算法见国标2-5.5
// calculateE 为SM2签名算法的第2步预处理函数,即,以Z值和带签名消息为基础获取哈希值H:
// (1) 将第1步预处理获得的Z值写入SM3哈希函数
// (2) 将拟签名消息M写入SM3哈希函数
// (3) 获取哈希值H
// (4) 输入参数的接口类hash.Hash,将由SM3算法具体实现,详见调用来源
// (5) 具体算法见国标2-6.1
func calculateE(digest hash.Hash, curve *P256V1Curve, pubX *big.Int, pubY *big.Int, userID []byte, src []byte) *big.Int {z := getZ(digest, curve, pubX, pubY, userID)digest.Reset()digest.Write(z)digest.Write(src)eHash := digest.Sum(nil)return new(big.Int).SetBytes(eHash)
}

calculateE( ) 为SM2签名算法的第2步预处理函数,即,以Z值和带签名消息为基础获取哈希值H:

  • (1) 将第1步预处理获得的Z值写入SM3哈希函数
  • (2) 将拟签名消息M写入SM3哈希函数
  • (3) 获取哈希值H
  • (4) 输入参数的接口类hash.Hash,将由SM3算法具体实现,详见调用来源
  • (5) 具体算法见国标2-6.1
// MarshalSign 为SM2将签名对象(r, s)序列化函数,即将签名对象序列化为符合ASN.1标准DER编码规则的字节串。
func MarshalSign(r, s *big.Int) ([]byte, error) {result, err := asn1.Marshal(sm2Signature{r, s})if err != nil {return nil, err}return result, nil
}

MarshalSign ( ) 为SM2将签名对象(r, s)序列化函数,即将签名对象序列化为符合ASN.1标准DER编码规则的字节串。

// UnmarshalSign 为SM2将签名对象反序列化函数,即将符合ASN.1标准DER编码规则的字节串反序列化为SM2签名对象。
func UnmarshalSign(sign []byte) (r, s *big.Int, err error) {sm2Sign := new(sm2Signature)_, err = asn1.Unmarshal(sign, sm2Sign)if err != nil {return nil, nil, err}return sm2Sign.R, sm2Sign.S, nil
}

UnmarshalSign( ) 为SM2将签名对象反序列化函数,即将符合ASN.1标准DER编码规则的字节串反序列化为SM2签名对象。

// SignToRS 为SM2签名算法的核心函数:
// (1) 以私钥(d倍数)为基础推算公钥点PA(XA, YA)
// (2) 调用预处理函数获取H值
// (3) 调用标准包crypto/rand获取随机数k (国标2-6.1.A3)
// (4) 推算曲线点(x1, y1) = [k]G (国标2-6.1.A4)
// (5) 调用标准包math/big封装的加和取模函数计算r = (e + x1) mod n,
// 并校验r<>0, 且r+k<>n (国标2-6.1.A5)
// (6) 调用标准包math/big封装的取乘法逆元和取模函数计算s = ((1+d)^(-1) * (k - rd)) mod n,
// 并校验s <> 0 (国标2-6.1.A6)
// (7) 返回计算结果(r, s)
func SignToRS(priv *PrivateKey, userID []byte, in []byte) (r, s *big.Int, err error) {digest := sm3.New()pubX, pubY := priv.Curve.ScalarBaseMult(priv.D.Bytes())if userID == nil {userID = sm2SignDefaultUserID}e := calculateE(digest, &priv.Curve, pubX, pubY, userID, in)intZero := new(big.Int).SetInt64(0)intOne := new(big.Int).SetInt64(1)for {var k *big.Intvar err errorfor {k, err = nextK(rand.Reader, priv.Curve.N)if err != nil {return nil, nil, err}px, _ := priv.Curve.ScalarBaseMult(k.Bytes())r = util.Add(e, px)r = util.Mod(r, priv.Curve.N)rk := new(big.Int).Set(r)rk = rk.Add(rk, k)if r.Cmp(intZero) != 0 && rk.Cmp(priv.Curve.N) != 0 {break}}dPlus1ModN := util.Add(priv.D, intOne)dPlus1ModN = util.ModInverse(dPlus1ModN, priv.Curve.N)s = util.Mul(r, priv.D)s = util.Sub(k, s)s = util.Mod(s, priv.Curve.N)s = util.Mul(dPlus1ModN, s)s = util.Mod(s, priv.Curve.N)if s.Cmp(intZero) != 0 {break}}return r, s, nil
}

SignToRS( ) 为SM2签名算法的核心函数:

  • (1) 以私钥(d倍数)为基础推算公钥点PA(XA, YA)
  • (2) 调用预处理函数获取H值
  • (3) 调用标准包crypto/rand获取随机数k (国标2-6.1.A3)
  • (4) 推算曲线点(x1, y1) = [k]G (国标2-6.1.A4)
  • (5) 调用标准包math/big封装的加和取模函数计算r = (e + x1) mod n, 并校验r<>0, 且r+k<>n (国标2-6.1.A5)
  • (6) 调用标准包math/big封装的取乘法逆元和取模函数计算s = ((1+d)^(-1) * (k - rd)) mod n, 并校验s <> 0 (国标2-6.1.A6)
  • (7) 返回计算结果(r, s)
// Sign 为封装后的SM2签名算法公共函数:
// (1) 输入参数为: 签名用户的私钥、ID和待签名信息
// (2) 调用SignToRS函数推算签名结果(r,s)
// (3) 调用MarshalSign函数将签名对象序列化为符合ASN.1标准DER编码规则的字节数组
func Sign(priv *PrivateKey, userID []byte, in []byte) ([]byte, error) {r, s, err := SignToRS(priv, userID, in)if err != nil {return nil, err}return MarshalSign(r, s)
}

Sign( ) 为封装后的SM2签名算法公共函数:

  • (1) 输入参数为: 签名用户的私钥、ID和待签名信息
  • (2) 调用SignToRS函数推算签名结果(r,s)
  • (3) 调用MarshalSign函数将签名对象序列化为符合ASN.1标准DER编码规则的字节数组
// VerifyByRS 为SM2验证签名算法的核心函数,输入参数为消息来源方公钥、用户ID、原始消息:
// (1) 调用math/big标准包(以下略)校验 1 <= r' < n (国标2-7.1.B1)
// (2) 校验 1 <= s' < n (国标2-7.1.B1)
// (3) 调用预处理函数,制备e' = Hash (Z||M') (国标2-7.1.B3-B4)
// (4) 计算 t = (r' + s') mod n, 并校验t<>0 (国标2-7.1.B5)
// (5) 调用elliptic标准包计算曲线上点(x1', y1') = [s']G + [t]PA, 并校验是否为无穷远点O(其实没必要) (国标2-7.1.B5)
// (6) 计算R = (e' + x1') mod n
// (7) 若 R = r' 则通过校验
func VerifyByRS(pub *PublicKey, userID []byte, src []byte, r, s *big.Int) bool {intOne := new(big.Int).SetInt64(1)if r.Cmp(intOne) == -1 || r.Cmp(pub.Curve.N) >= 0 {return false}if s.Cmp(intOne) == -1 || s.Cmp(pub.Curve.N) >= 0 {return false}digest := sm3.New()if userID == nil {userID = sm2SignDefaultUserID}e := calculateE(digest, &pub.Curve, pub.X, pub.Y, userID, src)intZero := new(big.Int).SetInt64(0)t := util.Add(r, s)t = util.Mod(t, pub.Curve.N)if t.Cmp(intZero) == 0 {return false}sgx, sgy := pub.Curve.ScalarBaseMult(s.Bytes())tpx, tpy := pub.Curve.ScalarMult(pub.X, pub.Y, t.Bytes())x, y := pub.Curve.Add(sgx, sgy, tpx, tpy)if util.IsEcPointInfinity(x, y) {return false}expectedR := util.Add(e, x)expectedR = util.Mod(expectedR, pub.Curve.N)return expectedR.Cmp(r) == 0
}

VerifyByRS( ) 为SM2验证签名算法的核心函数,输入参数为消息来源方公钥、用户ID、原始消息:

  • (1) 调用math/big标准包(以下略)校验 1 <= r’ < n (国标2-7.1.B1)
  • (2) 校验 1 <= s’ < n (国标2-7.1.B1)
  • (3) 调用预处理函数,制备e’ = Hash (Z||M’) (国标2-7.1.B3-B4)
  • (4) 计算 t = (r’ + s’) mod n, 并校验t<>0 (国标2-7.1.B5)
  • (5) 调用elliptic标准包计算曲线上点(x1’, y1’) = [s’]G + [t]PA, 并校验是否为无穷远点O(其实没必要) (国标2-7.1.B5)
  • (6) 计算R = (e’ + x1’) mod n
  • (7) 若 R = r’ 则通过校验
// Verify 为SM2封装后的签名验证函数,
// 输入参数为签名人的公钥、ID、原始消息和DER编码字节数组形式的签名(r, s),
// 反序列化签名后调用核心算法函数VerifyByRS校验签名。
func Verify(pub *PublicKey, userID []byte, src []byte, sign []byte) bool {r, s, err := UnmarshalSign(sign)if err != nil {return false}return VerifyByRS(pub, userID, src, r, s)
}

Verify( ) 为SM2封装后的签名验证函数, 输入参数为签名人的公钥、ID、原始消息和DER编码字节数组形式的签名(r, s), 反序列化签名后调用核心算法函数VerifyByRS校验签名。

(未完待续)

国密算法Go语言实现(详解)(九) ——SM2(椭圆曲线公钥密码算法)相关推荐

  1. 国密算法Go语言实现(详解)(十) ——SM2(椭圆曲线公钥密码算法)

    国密算法Go语言实现(详解)(十) --SM2(椭圆曲线公钥密码算法) 原创代码:https://github.com/ZZMarquis/gm 引用时,请导入原创代码库.本文仅以注释方式详解代码逻辑 ...

  2. SM2椭圆曲线公钥密码算法的C语言实现(基于Miracl大数运算库)

    SM2椭圆曲线公钥密码算法的C语言实现(基于Miracl大数运算库) 实验环境 预备知识 FpF_pFp​ 及椭圆曲线 素域 FpF_pFp​ FpF_pFp​ 上的椭圆曲线 FpF_pFp​ 上椭圆 ...

  3. SM2椭圆曲线公钥密码算法(Python实现)

    本文目录 一.实验目的(包括实验环境.实现目标等等) 1. 实验环境 2. 实现目标 3. 实验中需要导入的库 二.方案设计(包括背景.原理.必要的公式.图表.算法步骤等等) 1. 实验背景 2. 实 ...

  4. 12、SM2椭圆曲线公钥密码算法

    参考推荐: 国家密码管理局关于发布<SM2椭圆曲线公钥密码算法>公告(国密局公告第21号)_国家密码管理局 https://blog.csdn.net/u013137970/article ...

  5. GMSSL :SM2椭圆曲线公钥密码算法——数字签名算法1

    2021SC@SDUSC 一.相关术语以及定义 二.数字签名算法 1.辅助函数 密码杂凑算法和随机数发生器 杂凑运算:这个输出串称为该消息的杂凑值. 就是一种可将一个 key 对应到一个索引的函数,一 ...

  6. SM2椭圆曲线公钥密码算法的JAVA实现

    2019独角兽企业重金招聘Python工程师标准>>> package com.zpc.cryptography;import java.io.ByteArrayInputStrea ...

  7. GMSSL :SM2椭圆曲线公钥密码算法——数字签名算法4

    2021SC@SDUSC 目录 一.ECDSA介绍 二.代码分析 一.ECDSA介绍 ECDSA的全名是Elliptic Curve DSA,即椭圆曲线DSA.它是Digital Signature ...

  8. GMSSL :SM2椭圆曲线公钥密码算法-密钥交换协议

    2021SC@SDUSC 目录 一.整体架构 二.具体分析 前一篇文章写了密钥交换协议的基本流程,这一篇文章看一下代码实现 一.整体架构 整体来看这一部分有6个函数,相对重要的是序号3,4代表的函数 ...

  9. 详解高斯混合模型与EM算法

    详解高斯混合模型与EM算法 详解高斯混合模型与EM算法 高斯混合模型 单高斯模型(Gaussian single model, GSM) 一维高斯分布 多维高斯分布 混合高斯模型(Gaussian m ...

最新文章

  1. 机器学习之XGBoosting
  2. Django开发环境准备
  3. 软件常见故障的现象、故障排除的方法
  4. 使用pscp从windows电脑拷贝数据到linux遇到的ssh_init错误
  5. linux用while循环输出1到10,Linux Shell系列教程之(十一)Shell while循环 | Linux大学...
  6. Python 标准库之单元测试框架 -- unittest
  7. 机器学习-吴恩达-笔记-14-应用实例:图片文字识别
  8. MongoDB CookBook读书笔记之导入导出
  9. 仅为代码实际运行资源付费 解构国内首个函数计算
  10. layui时间选择30分钟为单位_layui 时间选择器实现季度选择器
  11. 算法与数据结构篇(暂未解答)
  12. 239.滑动窗口的最大值
  13. 最新PHP自动化发卡网源码
  14. postgresql 客户端 uri 设置时区
  15. 红米手机使用应用沙盒一键修改cpu信息
  16. visual studio code输入感叹号没有提示
  17. 基于51单片机俄罗斯方块游戏电路设计
  18. SD卡学习(SDIO和SPI模式)
  19. An internal error occurred during: “Enabling Maven Dependency Management”. Unsup
  20. python manage.py runserver报错

热门文章

  1. 超详细的fiddler教程,从小白到精通(六)❤️
  2. 著名的菲尔人格测试,看看你适合做什么类型的工作
  3. Linux shell 用sed删除第一行、最后一行或增加删除某行
  4. 区块链架构与交易流程(fabric1.0)
  5. web自定义字体引用与资源压缩
  6. HDU 5148 Cities
  7. 华为防火墙笔记-GRE
  8. opencv-python DIS光流
  9. 计算机操作培训图片,【图片】计算机学习之旅【汇编吧】_百度贴吧
  10. python按某列拆分excel表格_Python对Excel按列值筛选并拆分表格到多个文件的代码...