国密算法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
}

以上为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
}

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
}

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
}

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:]
}

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
	}
}

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
}

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
		}
	}
}

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

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

(未完待续)