SM2公钥加密算法

在SM2加密算法中,最后的密文是通过将明文与之长度一致的派生密钥t进行异或得到的。SM2加密算法的核心在于如何让发送方和接收方在不通过任何形式直接传输t的条件下能同时得到相同的t。

在非对称加密中,发送方A和接收方B各自持有私钥d和公钥P。

在传输开始前,接收方会提前将自己的公钥P公开,此时发送方手上持有自己的密钥对(dA和PA)和对方的公钥PB,虽然对于一次数据加密发送方的公钥作用并不大。

先来看发送方最后发送的密文结构CC,完整的密文由C1C_1C2C_2C3C_3组成。

C=C1C2C3C=C_1||C_2||C_3

其中,C2C_2就是明文MM与派生密钥t进行按位异或运算的结果,只要生成的t看起来十分随机,明文在加密后就会变成一堆乱码。

C3C_3的作用是进行完整性校验,接收方在解密出明文后可以进行哈希运算,结果与 C3C_3 完全匹配才能说明在传输过程中C2C_2没有经过中间篡改。

关键的在于C1C_1,接收方通过C1C_1能够运算出相同的派生密钥t,使用派生密钥t与C2C_2再进行一次按位异或就能轻松得到明文,接下来我们关注发送方如何计算出CC

发送方加密明文

计算C1

首先,发送方会生成一个随机数 k,该随机数只使用一次,能够保障每次生成的C1C_1都不同,这样一来每次生成的派生密钥 t 也不一样。这样一来即使传输的是相同的内容,每次生成的密文都会完全不同,能够有效防止通过密文比对来获取明文。

接下来,发送方使用公开点G和k计算密文第一部分C1C_1

C1=[k]GC_1=[k]G

G是由 SM2 算法约定的在椭圆曲线上的一个基点,所有使用SM2加密算法的过程都会使用这一个基点。C1C_1 是公开传输的,由于椭圆曲线离散对数问题的特点,中间攻击者无法通过C1C_1逆推出k的具体数值。

计算共享秘密点

接收方的公钥PB也是椭圆曲线上一点,由基点与接收方私钥dB计算得出

PB=[dB]GPB=[dB]G

发送方使用对方的公钥和k计算出共享秘密点S(x,y)S(x,y)

S(x,y)=[k]PB=[kdB]GS(x,y)=[k]PB=[k·{dB}]G

计算派生密钥 t

由计算出的共享秘密点SS的坐标,将其带入密钥派生函数KDF可以得到一个与明文等长的二进制数,也就是t。

KDF 接收两个参数:ZZ 和klen,Z 就是之前计算得到的秘密共享点坐标的拼接xyx||y,klen为期待输出t的长度。

KDF 函数本质是一个循环,每次循环都会调用一次 SM3 杂凑算法(哈希算法)输出一个 256 位的二进制数,只要输出的二进制串组合在一起大于明文长度,再将多余的截断,得到的就是派生密钥 t 。

具体过程是,最开始先设置一个32比特的计数器ct,初始值为0x00000001 ,将ZctZ||ct 作为种子输入,生成256位的比特串,然后计数器自增,再将Zct++Z|| ct++作为下一个种子生成256位的比特串。直到生成的比特串总长度大于或等于明文,若长度相等最后拼接的比特串就是派生密钥,如果长度比明文长度大,就从后面开始截断直到长度一致。

如果最后计算出的派生密钥 t 为全0的比特串(概率极小),将其与明文进行按位异或结果还是明文,无意义。故此时需要重新生成随机数 k 再次计算。

计算完整性校验值

使用以下方式得出C3C_3

C3=Hash(xMy)C_3= Hash(x||M||y)

该式子由明文MM、共享秘密点的坐标决定,保证了加密的密文只与当次传输有效。如果攻击者把截获的合法密文发送给接收方,接收方会因为此次传输的共享秘密点不一致直接拒绝。

接收方解密密文

当接收方获取发送方传输的数据后,同时获取了C1C_1C2C_2C3C_3

通过前面,我们知道

C1=[k]GC_1=[k]G

而共享秘密点

S=[kdB]GS=[k·dB]G

所以接收方只要将自己的密钥dB和C1C_1相乘,就能得到共享秘密点,进而通过 KDF 获取派生密钥t。

获取派生密钥后,与C2C_2进行一次按位异或就能轻松获取明文。

思考

在一整个过程中,SM2加密算法的安全性其实来自于椭圆曲线上点运算的特殊性,通过一个点与跳跃次数获取下一个点的过程是非常容易的,而知道两个点逆向跳跃次数却几乎不可能。这能够保证接收方私钥dB和随机数k都被保护在等式的右侧,攻击者永远也不可能知道这两个变量的具体值,也就不可能知道共享秘密点SS ,进而不可能知道派生密钥。