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

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

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

对原创代码的修改内容

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

注释者及联系邮箱

Paul Lee
paul_lee0919@163.com

const (
	// BitSize 代表曲线基础域的比特长度
	BitSize = 256
	// KeyBytes 代表秘钥的字节长度,其中加7整除8其实是“向上取整”,用以兼容基础域位数不是8的整数倍的情况。
	KeyBytes = (BitSize + 7) / 8
	// UnCompress 代表椭圆曲线上的点采用“未压缩”的形式存储,占1个字节,详见国标1-4.1.(b)的定义。
	UnCompress = 0x04
)

以上为SM2算法定义的三个常数:

  1. BitSize 代表曲线基础域的比特长度
  2. KeyBytes 代表秘钥的字节长度,其中加7整除8其实是“向上取整”,用以兼容基础域位数不是8的整数倍的情况
  3. UnCompress 代表椭圆曲线上的点采用“未压缩”的形式存储,占1个字节,详见国标1-4.1.(b)的定义
// CipherTextType 是为了区分两个版本SM2国标在密文形式上的区别而创设的枚举类
type CipherTextType int32

const (
	//C1C2C3 代表旧标准[GM/T 0009-2012]的密文顺序
	C1C2C3 CipherTextType = 1
	//C1C3C2 代表新标准[GB/T 32918-2016]的密文顺序
	C1C3C2 CipherTextType = 2
)

CipherTextType是为了区分新旧两个版本的国标规范而设置的密文顺序标识枚举类,其中:

  1. C1C2C3 代表旧标准[GM/T 0009-2012]的密文顺序
  2. C1C3C2 代表新标准[GB/T 32918-2016]的密文顺序
var (
	// sm2H 代表SM2推荐曲线的余因子h=1
	// 椭圆曲线方程符合 y^2 = x^3 - 3x + b (mod p)
	sm2H = new(big.Int).SetInt64(1)

	// sm2SignDefaultUserID 代表sm2算法默认的加密操作用户A的ID编码(详见国标5-A.1)和SM2使用规范(GB/T 35276-2017第10部分)
	sm2SignDefaultUserID = []byte{
		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}
)

以上为SM2算法定义的两个变量:

  1. sm2H 代表基域Fp上椭圆曲线E的余因子h
  2. 由于SM2推荐曲线的余因子h=1, 即#E(Fp) = n
// sm2P256V1 代表国密SM2推荐参数定义的椭圆曲线
var sm2P256V1 P256V1Curve

// P256V1Curve 代表国密SM2推荐参数定义的椭圆曲线:
// (1) 素数域256位椭圆曲线
// (2) 曲线方程为 Y^2 = X^3 + aX + b
// (3) 其他参数: p, a, b, n, Gx, Gy 详见国标SM2推荐曲线参数
// (4) 在GO语言标准库通用椭圆曲线参数类elliptic.CurveParams的基础上增加了参数a的属性
// (5) 由于SM2推荐曲线符合a=p-3, 所以上述曲线可简化为等价曲线 Y^2 = X^3 - 3X + b (mod p),
// 可直接适用GO语言elliptic标准库的一些公共方法。(没有完成严格的数学验证,请大牛指教)
type P256V1Curve struct {
	*elliptic.CurveParams
	A *big.Int
}

// init() 初始化国密SM2推荐参数计算得出的椭圆曲线。
func init() {
	initSm2P256V1()
}

// initSm2P256V1 为初始化国密SM2推荐参数计算得出的椭圆曲线:
// (1) 基域F(p)为素数域
// (2) 一次元x的系数a=p-3, 所以曲线方程等价于 y^2 = x^3 - 3x^2 + b (mod p) (即符合FIPS186-3标准预设函数)
// (3) 余因子h=1
func initSm2P256V1() {
	sm2P, _ := new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16)
	sm2A, _ := new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16)
	sm2B, _ := new(big.Int).SetString("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16)
	sm2N, _ := new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16)
	sm2Gx, _ := new(big.Int).SetString("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16)
	sm2Gy, _ := new(big.Int).SetString("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16)
	sm2P256V1.CurveParams = &elliptic.CurveParams{Name: "SM2-P-256-V1"}
	sm2P256V1.P = sm2P
	sm2P256V1.A = sm2A
	sm2P256V1.B = sm2B
	sm2P256V1.N = sm2N
	sm2P256V1.Gx = sm2Gx
	sm2P256V1.Gy = sm2Gy
	sm2P256V1.BitSize = BitSize
}

// GetSm2P256V1 为获取国密SM2椭圆曲线定义的函数。
func GetSm2P256V1() P256V1Curve {
	return sm2P256V1
}

P256V1Curve 代表国密SM2推荐参数定义的椭圆曲线:

  1. 素数域256位椭圆曲线
  2. 曲线方程为 Y2 = X3 + aX + b
  3. 其他参数: p, a, b, n, Gx, Gy 详见国标SM2推荐曲线参数
  4. 在GO语言标准库通用椭圆曲线参数类elliptic.CurveParams的基础上增加了参数a的属性
  5. 由于SM2推荐曲线符合a=p-3, 所以上述曲线可简化为等价曲线 Y^2 = X^3 - 3X + b (mod p)
  6. 可直接适用GO语言elliptic标准库的公共方法。(没有完成严谨的数学验证,请大牛指教)

(未完待续)