原创代码:https://github.com/ZZMarquis/gm
引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。
对原创代码的修改内容
- 修改了部分常量、变量、结构体属性的名称, 以便与GO语言标准包规范相统一
- 加入中文注释,解释代码逻辑
注释者及联系邮箱
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( ) 为获取未压缩字节数组格式存储的公钥的方法:
- 将PublicKey实例的坐标(x,y)分别转化为字节数组
- 将“未压缩”标识"0x04"写入输出字节数组raw[]的首字节raw[0]
- 将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利用私钥推算公钥的方法:
- 创设公钥实例,将私钥携带的曲线赋值给公钥实例
- 利用GO语言标准包(crypto/elliptic)定义的Curve接口的ScalarBaseMult()方法
- 根据椭圆曲线、基点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)范围内随机整数的函数:
- 利用标准库math/big设置整数1
- 利用标准库crypto/rand生成随机数
- 审核随机数范围[1, max)
- 本算法中max为基础域的阶数n
(未完待续)