国密算法
为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,包括SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、祖冲之密码算法(ZUC)等等。
其中SM1、SM4、SM7、祖冲之密码(ZUC)是对称算法;SM2、SM9是非对称算法;SM3是哈希算法。SM1、SM7算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
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
}
SM3
SM3算法:SM3密码杂凑(哈希、散列)算法给出了杂凑函数算法的计算方法和计算步骤,并给出了运算示例。此算法适用于商用密码应用中的数字签名和验证,消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。在SM2,SM9标准中使用。此算法对输入长度小于2的64次方的比特消息,经过填充和迭代压缩,生成长度为256比特的杂凑值,其中使用了异或,模,模加,移位,与,或,非运算,由填充,迭代过程,消息扩展和压缩函数所构成。
算法原理
整个流程包括消息填充、分块、迭代压缩3个步骤。
符号:
常量与函数:
初始值与常量
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算法相同。
分块:
迭代压缩:
即对分块消息进行压缩。压缩函数CF:
假设压缩函数处理第Bi个消息块,其流程如下:
其中,ABCDEFGH为字寄存器,长度为32-bit,j为迭代轮数,TT1,TT2为中间值字寄存器;
可以看出压缩函数共有64轮迭代,ABCDEFGH寄存器值初始化为前一个消息块Bi−1的压缩结果Vi 。
流程具体操作:
消息扩展:
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]
}
压缩:
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
杂凑值: