国密算法

为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,包括SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、祖冲之密码算法(ZUC)等等。
其中SM1、SM4、SM7、祖冲之密码(ZUC)是对称算法;SM2、SM9是非对称算法;SM3是哈希算法。SM1、SM7算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。

国密算法maven 国密算法有_国密算法maven

SM1

SM1 算法是分组密码算法,分组长度为128位,密钥长度都为 128 比特,算法安全保密强度及相关软硬件实现性能与 AES 相当,算法不公开,仅以IP核的形式存在于芯片中。

采用该算法已经研制了系列芯片、智能IC卡、智能密码钥匙、加密卡、加密机等安全产品,广泛应用于电子政务、电子商务及国民经济的各个应用领域(包括国家政务通、警务通等重要领域)。

SM2

SM2算法:SM2椭圆曲线公钥密码算法是我国自主设计的公钥密码算法,包括SM2-1椭圆曲线数字签名算法,SM2-2椭圆曲线密钥交换协议,SM2-3椭圆曲线公钥加密算法,分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA密码强度要高。

SM2算法主要考虑素域Fp和F2m上的椭圆曲线,分别介绍了这两类域的表示,运算,以及域上的椭圆曲线的点的表示,运算和多倍点计算算法。然后介绍了编程语言中的数据转换,包括整数和字节串,字节串和比特串,域元素和比特串,域元素和整数,点和字节串之间的数据转换规则。详细说明了有限域上椭圆曲线的参数生成以及验证,椭圆曲线的参数包括有限域的选取,椭圆曲线方程参数,椭圆曲线群基点的选取等,并给出了选取的标准以便于验证。最后给椭圆曲线上密钥对的生成以及公钥的验证,用户的密钥对为(s,sP),其中s为用户的私钥,sP为用户的公钥,由于离散对数问题从sP难以得到s,并针对素域和二元扩域给出了密钥对生成细节和验证方式。总则中的知识也适用于SM9算法。

算法原理

加密过程:
设需要发送的消息为比特串 M ,klen 为 M 的比特长度。
为了对明文 M 进行加密,作为加密者的用户 A 应实现以下运算步骤:
A1:用随机数发生器产生随机数k∈[1,n-1];
A2:计算椭圆曲线点 C1=[k]G=(x1,y1),([k]G 表示 k×G )将C1的数据类型转换为比特串;
A3:计算椭圆曲线点 S=[h]PB,若S是无穷远点,则报错并退出;
A4:计算椭圆曲线点 [k]PB=(x2,y2),将坐标 x2、y2 的数据类型转换为比特串;
A5:计算t=KDF(x2 ∥ y2, klen),若 t 为全0比特串,则返回 A1;
A6:计算C2 = M ⊕ t;
A7:计算C3 = Hash(x2 ∥ M ∥ y2);
A8:输出密文C = C1 ∥ C2 ∥ C3。

func Encrypt(pub *PublicKey, in []byte, cipherTextType Sm2CipherTextType) ([]byte, error) {
	c2 := make([]byte, len(in))
	copy(c2, in)
	var c1 []byte
	digest := sm3.New()
	var kPBx, kPBy *big.Int
	for {
		k, err := nextK(rand.Reader, pub.Curve.N)
		if err != nil {
			return nil, err
		}
		kBytes := k.Bytes()
		c1x, c1y := pub.Curve.ScalarBaseMult(kBytes)
		c1 = elliptic.Marshal(pub.Curve, c1x, c1y)
		kPBx, kPBy = pub.Curve.ScalarMult(pub.X, pub.Y, kBytes)
		kdf(digest, kPBx, kPBy, c2)

		if !notEncrypted(c2, in) {
			break
		}
	}

	digest.Reset()
	digest.Write(kPBx.Bytes())
	digest.Write(in)
	digest.Write(kPBy.Bytes())
	c3 := digest.Sum(nil)

	c1Len := len(c1)
	c2Len := len(c2)
	c3Len := len(c3)
	result := make([]byte, c1Len+c2Len+c3Len)
	if cipherTextType == C1C2C3 {
		copy(result[:c1Len], c1)
		copy(result[c1Len:c1Len+c2Len], c2)
		copy(result[c1Len+c2Len:], c3)
	} else if cipherTextType == C1C3C2 {
		copy(result[:c1Len], c1)
		copy(result[c1Len:c1Len+c3Len], c3)
		copy(result[c1Len+c3Len:], c2)
	} else {
		return nil, errors.New("unknown cipherTextType:" + string(cipherTextType))
	}
	return result, nil
}

解密过程:
设klen为密文中C2的比特长度。
为了对密文C=C1 ∥ C2 ∥ C3 进行解密,作为解密者的用户 B 应实现以下运算步骤:
B1:从C中取出比特串C1,将C1的数据类型转换为椭圆曲线上的点,验证C1是否满足椭圆曲线方程,若不满足则报错并退出;
B2:计算椭圆曲线点 S=[h]C1,若S是无穷远点,则报错并退出;
B3:计算[dB]C1=(x2,y2),将坐标x2、y2的数据类型转换为比特串;
B4:计算t=KDF(x2 ∥ y2, klen),若t为全0比特串,则报错并退出;
B5:从C中取出比特串C2,计算M′ = C2 ⊕ t;
B6:计算u = Hash(x2 ∥ M′ ∥ y2),从C中取出比特串C3,若u != C3,则报错并退出;
B7:输出明文M′。

func Decrypt(priv *PrivateKey, in []byte, cipherTextType Sm2CipherTextType) ([]byte, error) {
	c1Len := ((priv.Curve.BitSize+7)/8)*2 + 1
	c1 := make([]byte, c1Len)
	copy(c1, in[:c1Len])
	c1x, c1y := elliptic.Unmarshal(priv.Curve, c1)
	sx, sy := priv.Curve.ScalarMult(c1x, c1y, sm2H.Bytes())
	if util.IsEcPointInfinity(sx, sy) {
		return nil, errors.New("[h]C1 at infinity")
	}
	c1x, c1y = priv.Curve.ScalarMult(c1x, c1y, priv.D.Bytes())

	digest := sm3.New()
	c3Len := digest.Size()
	c2Len := len(in) - c1Len - c3Len
	c2 := make([]byte, c2Len)
	c3 := make([]byte, c3Len)
	if cipherTextType == C1C2C3 {
		copy(c2, in[c1Len:c1Len+c2Len])
		copy(c3, in[c1Len+c2Len:])
	} else if cipherTextType == C1C3C2 {
		copy(c3, in[c1Len:c1Len+c3Len])
		copy(c2, in[c1Len+c3Len:])
	} else {
		return nil, errors.New("unknown cipherTextType:" + string(cipherTextType))
	}

	kdf(digest, c1x, c1y, c2)

	digest.Reset()
	digest.Write(c1x.Bytes())
	digest.Write(c2)
	digest.Write(c1y.Bytes())
	newC3 := digest.Sum(nil)

	if !bytes.Equal(newC3, c3) {
		return nil, errors.New("invalid cipher text")
	}
	return c2, nil
}

密钥扩展函数:

func kdf(digest hash.Hash, c1x *big.Int, c1y *big.Int, encData []byte) {
	bufSize := 4
	if bufSize < digest.BlockSize() {
		bufSize = digest.BlockSize()
	}
	buf := make([]byte, bufSize)

	encDataLen := len(encData)
	c1xBytes := c1x.Bytes()
	c1yBytes := c1y.Bytes()
	off := 0
	ct := uint32(0)
	for off < encDataLen {
		digest.Reset()
		digest.Write(c1xBytes)
		digest.Write(c1yBytes)
		ct++
		binary.BigEndian.PutUint32(buf, ct)
		digest.Write(buf[:4])
		tmp := digest.Sum(nil)
		copy(buf[:bufSize], tmp[:bufSize])

		xorLen := encDataLen - off
		if xorLen > digest.BlockSize() {
			xorLen = digest.BlockSize()
		}
		xor(encData[off:], buf, xorLen)
		off += xorLen
	}
}

原理:
用户 A 持有公钥PB=[dB]G(仅有PB值),用户 B 持有私钥 dB
加密:C1=k×G C2=M⊕(k×PB) 解密:M′=C2 ⊕ (dB×C1) # 这里只叙述基本原理,便于理解
证明:dB×C1=dB×k×G=k×(dB×G)=k×PB 因此,M′=C2 ⊕ (dB×C1)=M⊕(k×PB)⊕(k×PB)=M 得证
注:此实现算法所研究的椭圆曲线是基于域 Fp 上的椭圆曲线
安全参数设置:
随机数 k 和私钥 dB 应较大。

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
}

国密算法maven 国密算法有_随机数_02

SM3

SM3算法:SM3密码杂凑(哈希、散列)算法给出了杂凑函数算法的计算方法和计算步骤,并给出了运算示例。此算法适用于商用密码应用中的数字签名和验证,消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。在SM2,SM9标准中使用。此算法对输入长度小于2的64次方的比特消息,经过填充和迭代压缩,生成长度为256比特的杂凑值,其中使用了异或,模,模加,移位,与,或,非运算,由填充,迭代过程,消息扩展和压缩函数所构成。

算法原理

整个流程包括消息填充、分块、迭代压缩3个步骤。

国密算法maven 国密算法有_ci_03


符号:

国密算法maven 国密算法有_迭代_04


常量与函数:

国密算法maven 国密算法有_国密算法maven_05


初始值与常量

func (digest *sm3Digest) Reset() {
	digest.byteCount = 0

	digest.xBufOff = 0
	for i := 0; i < len(digest.xBuf); i++ {
		digest.xBuf[i] = 0
	}

	for i := 0; i < len(digest.inWords); i++ {
		digest.inWords[i] = 0
	}

	for i := 0; i < len(digest.w); i++ {
		digest.w[i] = 0
	}

	digest.v[0] = 0x7380166F
	digest.v[1] = 0x4914B2B9
	digest.v[2] = 0x172442D7
	digest.v[3] = 0xDA8A0600
	digest.v[4] = 0xA96F30BC
	digest.v[5] = 0x163138AA
	digest.v[6] = 0xE38DEE4D
	digest.v[7] = 0xB0FB0E4E

	digest.xOff = 0
}
T[j] = 0x79CC4519
T[j] = 0x7A879D8A

布尔函数

func ff0(x uint32, y uint32, z uint32) uint32 {
	return x ^ y ^ z
}

func ff1(x uint32, y uint32, z uint32) uint32 {
	return (x & y) | (x & z) | (y & z)
}

func gg0(x uint32, y uint32, z uint32) uint32 {
	return x ^ y ^ z
}

func gg1(x uint32, y uint32, z uint32) uint32 {
	return (x & y) | ((^x) & z)
}

置换函数

func p0(x uint32) uint32 {
	r9 := bits.RotateLeft32(x, 9)
	r17 := bits.RotateLeft32(x, 17)
	return x ^ r9 ^ r17
}

func p1(x uint32) uint32 {
	r15 := bits.RotateLeft32(x, 15)
	r23 := bits.RotateLeft32(x, 23)
	return x ^ r15 ^ r23
}

填充:

假设消息m 的长度为l 比特。首先将比特“ 1”添加到消息的末尾,再添加k 个“ 0”, k是满

足l + 1 + k ≡ 448mod512 的最小的非负整数。然后再添加一个64位比特串,该比特串是长度l的二进

制表示。填充后的消息m′ 的比特长度为512的倍数。该步骤与MD5、SHA算法相同。

国密算法maven 国密算法有_ci_06


国密算法maven 国密算法有_随机数_07


分块:

国密算法maven 国密算法有_随机数_08


迭代压缩:

国密算法maven 国密算法有_国密算法maven_09


即对分块消息进行压缩。压缩函数CF:

假设压缩函数处理第Bi个消息块,其流程如下:

国密算法maven 国密算法有_ci_10


其中,ABCDEFGH为字寄存器,长度为32-bit,j为迭代轮数,TT1,TT2为中间值字寄存器;

可以看出压缩函数共有64轮迭代,ABCDEFGH寄存器值初始化为前一个消息块Bi−1的压缩结果Vi 。

流程具体操作:

消息扩展:

国密算法maven 国密算法有_国密算法maven_11

for j := 0; j < 16; j++ {
		digest.w[j] = digest.inWords[j]
	}
	for j := 16; j < 68; j++ {
		wj3 := digest.w[j-3]
		r15 := (wj3 << 15) | (wj3 >> (32 - 15))
		wj13 := digest.w[j-13]
		r7 := (wj13 << 7) | (wj13 >> (32 - 7))
		digest.w[j] = p1(digest.w[j-16]^digest.w[j-9]^r15) ^ r7 ^ digest.w[j-6]
	}

压缩:

国密算法maven 国密算法有_随机数_12

A := digest.v[0]
	B := digest.v[1]
	C := digest.v[2]
	D := digest.v[3]
	E := digest.v[4]
	F := digest.v[5]
	G := digest.v[6]
	H := digest.v[7]

	for j := 0; j < 16; j++ {
		a12 := (A << 12) | (A >> (32 - 12))
		s1 := a12 + E + gT[j]
		SS1 := (s1 << 7) | (s1 >> (32 - 7))
		SS2 := SS1 ^ a12
		Wj := digest.w[j]
		W1j := Wj ^ digest.w[j+4]
		TT1 := ff0(A, B, C) + D + SS2 + W1j
		TT2 := gg0(E, F, G) + H + SS1 + Wj
		D = C
		C = (B << 9) | (B >> (32 - 9))
		B = A
		A = TT1
		H = G
		G = (F << 19) | (F >> (32 - 19))
		F = E
		E = p0(TT2)
	}

	for j := 16; j < 64; j++ {
		a12 := (A << 12) | (A >> (32 - 12))
		s1 := a12 + E + gT[j]
		SS1 := (s1 << 7) | (s1 >> (32 - 7))
		SS2 := SS1 ^ a12
		Wj := digest.w[j]
		W1j := Wj ^ digest.w[j+4]
		TT1 := ff1(A, B, C) + D + SS2 + W1j
		TT2 := gg1(E, F, G) + H + SS1 + Wj
		D = C
		C = (B << 9) | (B >> (32 - 9))
		B = A
		A = TT1
		H = G
		G = (F << 19) | (F >> (32 - 19))
		F = E
		E = p0(TT2)
	}

	digest.v[0] ^= A
	digest.v[1] ^= B
	digest.v[2] ^= C
	digest.v[3] ^= D
	digest.v[4] ^= E
	digest.v[5] ^= F
	digest.v[6] ^= G
	digest.v[7] ^= H

	digest.xOff = 0

杂凑值:

国密算法maven 国密算法有_国密算法maven_13