国密SM2加密算法理解

0. 写在前面

工作的第32天。

这周网关那边的同事安排我了解一下SM2算法,学完后写个学习笔记巩固一下知识,也方便之后汇报。

文末附上了学习过程中使用到且经筛选后的参考资料,以作补充。纯小白,如有不足,敬请指正。

后续准备基于AT32的板子做一个wifi桌面智能小摆件,会在博客同步更新学习经验分享。另外,如果时间充裕会写一个医学影像相关的3D图像分割的技术分享。

1. SM2算法

首先,通过下图对算法有个整体的认识。

在这里插入图片描述

SM2是基于椭圆曲线密码学(ECC)的一种加密算法,其曲线定义与参数设置如下:

椭圆曲线:y^3 = x^3 + ax +b

default_ecc_table = {
    'n': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
    'p': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
    'g': '32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7'
         'bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0',
    'a': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
    'b': '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
}

SM2算法中的倍点操作也是ECC的核心部分,用于实现公钥的生成、签名的验证等关键步骤。
(注:剩余三个操作函数本文不作详细介绍)

在这里插入图片描述

ECC算法可以简单描述为:

K = G * k

G为初始定义的G点
k为密钥
K为公钥

理解ECC算法需要先明确两个推广

  • 曲线上两点A+B推广为点A自身加法,即A+B->A+A
  • 乘法作为多次加法的推广,即A+A+…A-> k*A

然后需要理解A点与B点加法如何在几何上实现

  • 定义无穷远点0,得出A+B+C =0 -> A+B= -C= D
  • C为AB连线后与椭圆曲线的交点
  • D与C沿x轴对称
    在这里插入图片描述
    A+A的情况时,则过A点做曲线的切线
    在这里插入图片描述

这时最终点为2A,操作数k为2,同理反复进行n次,我们也能得到k=n时最终点的位置。
即等式表示的:K = G * k。

接着,我们将上述ECC部分内容与私钥dA、公钥pA、G点进行联系:

K = G * k -> pA = G * dA

pA为公开的私钥,映射到曲线上为终止点位置
G为预设的椭圆上一点,映射到曲线上为点A
dA私钥,映射到曲线上为需要执行几次点加操作

可以发现,已知操作次数(dA)与椭圆上一点(G),易得终止点位置(pA)。但是,若只知道最终点位置(pA)与椭圆上一点(G),难以求出操作次数(dA)。

1.0 密钥对生成

椭圆的连续域处理成离散域,然后在该离散域进行私钥的随机生成。
在这里插入图片描述

得到私钥后,通过之前介绍的pA = G * dA,进行公钥的计算。

class PrivateKey:
    def __init__(self, curve=sm2p256v1, secret=None):
        self.curve = curve
        self.secret = secret or SystemRandom().randrange(1, curve.N)

    def publicKey(self): # xQ,yQ
        curve = self.curve
        xPublicKey, yPublicKey = multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)
        return PublicKey(xPublicKey, yPublicKey, curve)

    def toString(self):
        return "{}".format(str(hex(self.secret))[2:].zfill(64))

1.1 加/解密

加/解密过程关注将msg进行拆分后的三个分支C1,C2,C3。两过程实际为查看加密计算的点(x′1; y′1) 和解密计算的点 ( x1, y1 )是否相等。

在这里插入图片描述

该部分为验证过程的计算展示,通过等式代入推导展现加/解密过程,实际为验证加密计算的点(x′1; y′1) 和解密计算的点 ( x1, y1 )相等。

encrypt:

C1 = k * G         (1)
(x1,y1) = k * pA   (2)
 
decrypt:

(x1′,y1′)  
= dA * C1
= dA *  k * G # 带入(1)
= k * dA * G
= k * pA ==(x1,x2)

python代码实现,可对应流程图理解。

  def encrypt(self, data): #public_key
        # 加密函数,data消息(bytes)
        msg = data.hex()  # 消息转化为16进制字符串  k是每次随机的
        k = func.random_hex(self.para_len) #1.k随机生成
        print('k:',k)
        C1 = self._kg(int(k, 16), self.ecc_table['g']) # 2.得到 C1 k*G C1 随机数K与G进行倍乘 记作(kx,ky)
        xy = self._kg(int(k, 16), self.public_key) # 3.得到C2 k*public
        x2 = xy[0:self.para_len]
        y2 = xy[self.para_len:2*self.para_len] #分别提取x y
        ml = len(msg)
        # 1.复用计算c1时产生的随机数k
        # 2.计算kpk(x, y),
        # 3.根据data的长度与kpx, kpy生成与data等长的密钥流,用于c2的最终计算
        t = sm3.sm3_kdf(xy.encode('utf8'), ml/2)  #这个t是密钥流
        if int(t, 16) == 0: 
            return None #不能生成0 生成0要重新分配k
        else:
            form = '%%0%dx' % ml
            C2 = form % (int(msg, 16) ^ int(t, 16)) # 3.得到C2  msg|t C2操作 密钥流异或data 。这步C2获得了数据密文
            #4. c3 = HASH(kpx, data, kpy)
            C3 = sm3.sm3_hash([
                i for i in bytes.fromhex('%s%s%s' % (x2, msg, y2)) #C3 使用SM3对kx,ky,data的hash值,用于解密时校验
            ])
            #5. C = C1 || C2 || C3
            # 加密过程就是去计算这三个东西,操作就是倍乘
            if self.mode:
                return bytes.fromhex('%s%s%s' % (C1, C3, C2))
            else:
                return bytes.fromhex('%s%s%s' % (C1, C2, C3))
            #在解密时需要在解密出原文后计算HASH值做最后确认,确认一致后认定解密成功,不一致则解密失败。
    def decrypt(self, data): #private_key
        # 解密函数,data密文(bytes)
        data = data.hex()
        len_2 = 2 * self.para_len
        len_3 = len_2 + 64
        print('len2:',len_2) #len2==128
        C1 = data[0:len_2] # 1.密文中分离出c1
        print('decode c1:',C1)

        if self.mode:
            C3 = data[len_2:len_3]
            C2 = data[len_3:]
        else:
            C2 = data[len_2:-64]
            C3 = data[-64:]

        xy = self._kg(int(self.private_key, 16), C1) # 2.私钥与c1计算(cx,cy) by d*c1(x,y)
        # print('xy = %s' % xy)
        x2 = xy[0:self.para_len]
        y2 = xy[self.para_len:len_2]
        cl = len(C2)
        # 生成需要长度
        t = sm3.sm3_kdf(xy.encode('utf8'), cl/2) # 3.根据c2长度 计算密钥流t
        # 如果全0解密错误
        if int(t, 16) == 0:
            return None
        else:
            form = '%%0%dx' % cl
            # 4. M = C2异或t ,M为明文
            M = form % (int(C2, 16) ^ int(t, 16))
            # 5. 计算u=hash(x2||M||y2)u!=C3 解密错误
            u = sm3.sm3_hash([
                i for i in bytes.fromhex('%s%s%s' % (x2, M, y2))
            ])
            return bytes.fromhex(M)

1.2 加/验签

直接用过流程图进行过程展示。
主要关注两个分支:

  • 通过信息msg的hash操作,获得e
  • x1的获取

接着使用二者进行SM2算法的规定运算流程,并验证加签计算的点(x′1; y′1) 和验签计算的点 ( x1, y1 )是否相等。

在这里插入图片描述
该部分为验证过程的计算展示,通过等式代入推导展现加/验签过程,实际为验证加签计算的点(x′1; y′1) 和验签计算的点 ( x1, y1 )相等。

sign:

(x1,y1)= k * G                  (1)
s=((1+dA)^-1 * (k-r*dA)) mod n  (2)

vertify:

t = s′ + r′  (3)
pA = G * dA  (4)

(x′1; y′1)
= s′ * G + t * pA
= s′ * G + s′ * pA + r′ * pA # 代入(3)
= s′ * G + s′ * dA * G + r′* dA * G # 代入(4)
= [(1+ dA) * s′] * G+(r′ * dA) * G #代入(2)
= [k − r * dA) ] * G+(r′ * dA) * G
= k * G == ( x1, y1 )

python代码实现:

    def verify(self, Sign, data):
        # 就是为了验证(x1,y1)与之前是否相同
        # 验签函数,sign签名r||s,E消息hash,public_key公钥

        # 解码签名,得到整数r,s
        if self.asn1:
            unhex_sign = unhexlify(Sign.encode())
            seq_der = DerSequence()
            origin_sign = seq_der.decode(unhex_sign)
            r = origin_sign[0]
            s = origin_sign[1]
        else:
            r = int(Sign[0:self.para_len], 16)
            s = int(Sign[self.para_len:2*self.para_len], 16)
        #1. e = hash(M)
        e = int(data.hex(), 16)
        #2. t = (r+s) mod n (r,s)是之前sign中得到的
        t = (r + s) % int(self.ecc_table['n'], base=16)
        # t=0则验证失败
        if t == 0:
            return 0
        # 通过曲线点计算 P1 P2
        #3.(x1,y1) =s*G+t*pub

        P1 = self._kg(s, self.ecc_table['g'])    # s*G
        P2 = self._kg(t, self.public_key)      # t*pub
        # print(P1)
        # print(P2)
        # s、t分别计算P1、P2两个点,根据两个值进行验证
        # 有的步骤是只有一个P1进行运算的,然后最后将两部分相加,这跟分为P1、P2算没有区别
        if P1 == P2:
            P1 = '%s%s' % (P1, 1)
            P1 = self._double_point(P1) #自己倍乘,其实就是加
        else:
            P1 = '%s%s' % (P1, 1)
            P1 = self._add_point(P1, P2)
            P1 = self._convert_jacb_to_nor(P1)

        x = int(P1[0:self.para_len], 16) #提取P1的x后进行r的验证,相等则sign
        return r == ((e + x) % int(self.ecc_table['n'], base=16))

    def sign(self, data, K):
        """
        签名函数, data消息的hash,private_key私钥,K随机数,均为16进制字符串
        :param self:
        :param data: data消息的hash
        :param K: K随机数
        :return:
        """
        E = data.hex()  # 消息转化为16进制字符串
        e = int(E, 16) # 1.hash 生成16进制的数字

        d = int(self.private_key, 16) # 私钥
        k = int(K, 16) # 2.k已经随机生成得到

        P1 = self._kg(k, self.ecc_table['g']) #3.计算椭圆曲线上的点(x1,y1 )= k*G

        x = int(P1[0:self.para_len], 16)
        #4. r=(e+x1) mod n
        R = ((e + x) % int(self.ecc_table['n'], base=16))
        # 如果r=0或者r+k=n,则重新选择随机数k
        if R == 0 or R + k == int(self.ecc_table['n'], base=16):
            return None
        # 5. 计算s=((1+d)^-1*(k-r*d)) mod n,其中d为私钥,
        d_1 = pow(
            d+1, int(self.ecc_table['n'], base=16) - 2, int(self.ecc_table['n'], base=16))
        S = (d_1*(k + R) - R) % int(self.ecc_table['n'], base=16)
        if S == 0:
            return None
        elif self.asn1:
            return DerSequence([DerInteger(R), DerInteger(S)]).encode().hex()
        else:
            # 6. 签名值为(r, s)
            return '%064x%064x' % (R, S)

参考资料

代码
ECC理解
SM2整体理解
SM2签名、验签

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周树皮不皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值