当前位置:   article > 正文

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

golang sm2

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


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

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

对原创代码的修改内容

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

注释者及联系邮箱

Paul Lee
paul_lee0919@163.com

// PublicKey 代表SM2算法的公钥类:
// (1) X,Y 为P点(有限素数域上基点G的D倍点)坐标
// (2) Curve 为SM2算法的椭圆曲线
type PublicKey struct {
	X, Y  *big.Int
	Curve P256V1Curve
}

// PrivateKey 代表SM2算法的私钥类:
// (1) D代表公钥P点相对于基点G的倍数
// (2) Curve 为SM2算法的椭圆曲线
type PrivateKey struct {
	D     *big.Int
	Curve P256V1Curve
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

以上为SM2算法公钥类和私钥类的定义,其中:

  • 不论是公钥类还是私钥类,均将其基础椭圆曲线定义作为基本属性
  • 公钥为曲线上的一个点P,因此,其特征值指向P点坐标(X, Y)
  • 私钥为公钥点P相对于基点G的倍数,因此,其特征值指向倍数D
// GenerateKey 为国密SM2生成秘钥对的函数:
// (1) 利用GO语言标准包crypto/rand生成随机数rand;
// (2) 将SM2推荐曲线参数和随机数rand输入GO语言标准包crypto/elliptic的公钥对生成方法GenerateKey(),生成密钥对核心参数(priv, x, y);
// (3) 根据PublicKey类和PrivateKey类的定义生成公钥和私钥的实例,并将上述核心参数赋值给实例各相应属性以完成初始化.
func GenerateKey(rand io.Reader) (*PrivateKey, *PublicKey, error) {
   priv, x, y, err := elliptic.GenerateKey(sm2P256V1, rand)
   if err != nil {
   	return nil, nil, err
   }
   privateKey := new(PrivateKey)
   privateKey.Curve = sm2P256V1
   privateKey.D = new(big.Int).SetBytes(priv)
   publicKey := new(PublicKey)
   publicKey.Curve = sm2P256V1
   publicKey.X = x
   publicKey.Y = y
   return privateKey, publicKey, nil
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

GenerateKey( )为国密SM2生成秘钥对的函数:

  • (1) 利用GO语言标准包crypto/rand生成随机数rand;
  • (2) 将SM2推荐曲线参数和随机数rand输入GO语言标准包crypto/elliptic的公钥对生成方法GenerateKey(),生成密钥对核心参数(priv, x, y);
  • (3) 根据PublicKey类和PrivateKey类的定义生成公钥和私钥的实例,并将上述核心参数赋值给实例各相应属性以完成初始化.
// RawBytesToPublicKey 将字节数组形式的原始格式数据转化为SM2公钥的方法:
// (1) 校验原始格式数据的字节长度(32的2倍,即64个字节)
// (2) 利用GO语言标准包math/big的SetBytes()方法将原始格式数据转变成大端整数
// (3) 赋值给PublicKey实例的相关属性,完成公钥初始化
func RawBytesToPublicKey(bytes []byte) (*PublicKey, error) {
	if len(bytes) != KeyBytes*2 {
		return nil, errors.New("Public key raw bytes length must be " + string(KeyBytes*2))
	}
	publicKey := new(PublicKey)
	publicKey.Curve = sm2P256V1
	publicKey.X = new(big.Int).SetBytes(bytes[:KeyBytes])
	publicKey.Y = new(big.Int).SetBytes(bytes[KeyBytes:])
	return publicKey, nil
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

RawBytesToPublicKey( )为将字节数组形式的原始格式数据转化为SM2公钥的函数:

  • (1) 校验原始格式数据的字节长度(32的2倍,即64个字节)
  • (2) 利用GO语言标准包math/big的SetBytes()方法将原始格式数据转变成大端整数
  • (3) 赋值给PublicKey实例的相关属性,完成公钥初始化
// RawBytesToPrivateKey 将字节数组形式的原始格式数据转变为SM2私钥的方法:
// (1) 校验原始格式数据的字节长度(256位除以8,即32字节)
// (2) 利用GO语言标准包math/big的SetBytes()方法将原始格式数据转变成大端整数
// (3) 赋值给PrivateKey实例的相关属性,完成私钥初始化
func RawBytesToPrivateKey(bytes []byte) (*PrivateKey, error) {
	if len(bytes) != KeyBytes {
		return nil, errors.New("Private key raw bytes length must be " + string(KeyBytes))
	}
	privateKey := new(PrivateKey)
	privateKey.Curve = sm2P256V1
	privateKey.D = new(big.Int).SetBytes(bytes)
	return privateKey, nil
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

RawBytesToPrivateKey( )为将字节数组形式的原始格式数据转变为SM2私钥的函数:

  • (1) 校验原始格式数据的字节长度(256位除以8,即32字节)
  • (2) 利用GO语言标准包math/big的SetBytes()方法将原始格式数据转变成大端整数
  • (3) 赋值给PrivateKey实例的相关属性,完成私钥初始化
// GetUnCompressBytes 为获取未压缩字节数组格式存储的公钥的方法:
// (1) 将PublicKey实例的坐标(x,y)分别转化为字节数组
// (2) 将“未压缩”标识"0x04"写入输出字节数组raw[]的首字节raw[0]
// (3) 将x坐标写入raw[:33], 将y坐标写入raw[33:]
func (pub *PublicKey) GetUnCompressBytes() []byte {
	xBytes := pub.X.Bytes()
	yBytes := pub.Y.Bytes()
	xl := len(xBytes)
	yl := len(yBytes)

	raw := make([]byte, 1+KeyBytes*2)
	raw[0] = UnCompress
	if xl > KeyBytes {
		copy(raw[1:1+KeyBytes], xBytes[xl-KeyBytes:])
	} else if xl < KeyBytes {
		copy(raw[1+(KeyBytes-xl):1+KeyBytes], xBytes)
	} else {
		copy(raw[1:1+KeyBytes], xBytes)
	}

	if yl > KeyBytes {
		copy(raw[1+KeyBytes:], yBytes[yl-KeyBytes:])
	} else if yl < KeyBytes {
		copy(raw[1+KeyBytes+(KeyBytes-yl):], yBytes)
	} else {
		copy(raw[1+KeyBytes:], yBytes)
	}
	return raw
}

// GetRawBytes 为获得字节数组格式存储的公钥的方法(不带“未压缩”标识字节)。
func (pub *PublicKey) GetRawBytes() []byte {
	raw := pub.GetUnCompressBytes()
	return raw[1:]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

GetUnCompressBytes( ) 为获取未压缩字节数组格式存储的公钥的方法:

  1. 将PublicKey实例的坐标(x,y)分别转化为字节数组
  2. 将“未压缩”标识"0x04"写入输出字节数组raw[]的首字节raw[0]
  3. 将x坐标写入raw[:33], 将y坐标写入raw[33:]
// GetRawBytes 为获得字节数组格式存储的私钥的方法。
func (pri *PrivateKey) GetRawBytes() []byte {
	dBytes := pri.D.Bytes()
	dl := len(dBytes)
	if dl > KeyBytes {
		raw := make([]byte, KeyBytes)
		copy(raw, dBytes[dl-KeyBytes:])
		return raw
	} else if dl < KeyBytes {
		raw := make([]byte, KeyBytes)
		copy(raw[KeyBytes-dl:], dBytes)
		return raw
	} else {
		return dBytes
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

GetRawBytes( ) 为获得字节数组格式存储的私钥的方法。

// CalculatePubKey 为SM2利用私钥推算公钥的方法:
// (1) 创设公钥实例,将私钥携带的曲线赋值给公钥实例
// (2) 利用GO语言标准包(crypto/elliptic)定义的Curve接口的ScalarBaseMult()方法,
// 根据椭圆曲线、基点G、私钥(D倍数)推算公钥(倍点P)
func CalculatePubKey(priv *PrivateKey) *PublicKey {
	pub := new(PublicKey)
	pub.Curve = priv.Curve
	pub.X, pub.Y = priv.Curve.ScalarBaseMult(priv.D.Bytes())
	return pub
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

CalculatePubKey( ) 为SM2利用私钥推算公钥的方法:

  1. 创设公钥实例,将私钥携带的曲线赋值给公钥实例
  2. 利用GO语言标准包(crypto/elliptic)定义的Curve接口的ScalarBaseMult()方法
  3. 根据椭圆曲线、基点G、私钥(D倍数)推算公钥(倍点P)
// nextK 为生成[1, max)范围内随机整数的函数:
// (1) 利用标准库math/big设置整数1
// (2) 利用标准库crypto/rand生成随机数
// (3) 审核随机数范围[1, max)
// (4) 本算法中max为基础域的阶数n
func nextK(rnd io.Reader, max *big.Int) (*big.Int, error) {
	intOne := new(big.Int).SetInt64(1)
	var k *big.Int
	var err error
	for {
		k, err = rand.Int(rnd, max)
		if err != nil {
			return nil, err
		}
		if k.Cmp(intOne) >= 0 {
			return k, err
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

nextK( ) 为生成[1, max)范围内随机整数的函数:

  1. 利用标准库math/big设置整数1
  2. 利用标准库crypto/rand生成随机数
  3. 审核随机数范围[1, max)
  4. 本算法中max为基础域的阶数n

(未完待续)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/483338
推荐阅读
相关标签
  

闽ICP备14008679号