第三章 SM2算法原理快速入门

聊到国密算法,SM2是绕不开的核心。它是基于椭圆曲线的公钥密码体制,对标国际的ECDSA和ECIES。坦白说,椭圆曲线密码学在刚入行时是有点抽象——一群点落在曲线上,还搞什么加法运算?但真正上手做接口改造时你会发现,理解这套理论能让你少踩很多坑。

3.1 椭圆曲线密码学基础

椭圆曲线密码学,英文简称ECC。它依赖的是椭圆曲线在有限域上的离散对数问题。你想想看,如果给你一个基点G和它的倍数kG,计算kG很容易,但从kG反推出k却非常困难。这就是ECC的安全基石。

SM2使用的椭圆曲线方程是:

y² = x³ + ax + b  (mod p)

其中p是一个大素数。SM2的a、b、p、G等参数都已经标准化了。我建议在实现时直接使用国家发布的参数,千万别自己随便挑曲线——我之前见过有人为了对比性能自己造曲线,结果根本没考虑安全强度。

核心要点:椭圆曲线上的点之间可以定义加法运算,这是ECC公钥生成、签名验签、加解密的基础。

具体来说,曲线上的加法可以这样理解:对于两个不同的点P和Q,连接它们的直线会与曲线相交于第三个点R',这个R'关于x轴的对称点就是P+Q的结果。当P和Q重合时,就变成了点倍运算——过P做切线,交点再对称。说白了,椭圆曲线的点乘就是这么一步步算出来的。

概念 说明
基点G SM2标准曲线上的公共生成点
阶数n G的倍数构成的循环群的元素个数
私钥d 随机选取的大整数,范围在[1, n-2]
公钥Q Q = d * G,即私钥乘基点

嗯,这里要注意:椭圆曲线密码体制设计的好坏,很大程度上取决于曲线的选择。SM2选的是素域上的Fp曲线,计算效率和安全强度都是经过仔细权衡的。我在项目里对比过SM2和ECDSA,同样的安全强度下SM2的公钥长度比RSA短得多。

3.2 密钥对生成

SM2的密钥对生成过程其实很简单:

  1. 选取随机数d ∈ [1, n-2]
  2. 计算公钥Q = d * G
  3. 私钥保存d,公钥发布Q

看吧,就这么几步。但随机数生成的质量非常关键。我记得有一次项目中,同事直接用了Java的Random类生成密钥,结果导致部分密钥出现了重复。因为伪随机数生成器在没有足够种子熵的情况下,会出现周期性。我建议使用系统级的加密安全随机数生成器,比如Java的SecureRandom或OpenSSL的RAND_bytes。

我曾经遇到过:某项目在密钥对生成后,直接把私钥以明文形式写在了配置文件中。这是不允许的。SM2私钥必须加密存储,可以考虑用PIN码或硬件加密机保护。

另外,公钥的存储格式也很重要。SM2公钥通常包含两个分量:x坐标和y坐标。这两个值一起才能唯一确定公钥点。如果只存x坐标,验签时会多出一个重建y坐标的步骤,既麻烦又容易出错。

3.3 签名与验签流程

SM2的数字签名算法比RSA签名更复杂一些——它引入了用户身份标识ZA。说白了,签名时不仅要防消息被篡改,还要防签名被重用到其他用户或场景中。我用一个流程图来展示SM2签名验签的核心逻辑:

SM2 签名与验签流程 签名方 私钥 d、消息 M、ZA Step 1: 计算 e = Hv(ZA || M) Step 2: 生成随机数 k ∈ [1, n-1] Step 3: 计算椭圆曲线点 (x1, y1) = kG Step 4: 计算 r = (e + x1) mod n Step 5: 计算 s = ((1+d)⁻¹*(k - r*d)) mod n 输出签名 (r, s) 验签方 公钥 Q、消息 M、ZA、(r, s) Step 1: 验证 r, s 在范围内 Step 2: 计算 e = Hv(ZA || M) Step 3: 计算 t = (r + s) mod n Step 4: 计算点 (x1, y1) = sG + tQ Step 5: 计算 R = (e + x1) mod n 验证 R == r 发送 (M, (r, s), ZA) R == r 则通过

你看签名过程里有个随机数k。这个k值必须严格保密且每次签名都要重新生成。为什么?因为一旦k泄露,私钥就可以被推导出来。我曾经见过一个系统因为性能压力,对同一笔交易重复使用了相同的k,结果验签虽然正常通过了,但被外部攻击者拿到了重复签名的数据,一下就把私钥算出来了。

避坑指南:确保每次签名都使用不同的随机数k。如果使用硬件加密机,要让设备自身产生随机数,不要从软件传入。我习惯的做法是每次签名前调用一次RNG得到新k,绝不缓存或复用。

3.4 加解密过程

SM2的加解密与签名验签名不同。加密时,它用接收方的公钥对数据做加密;解密时,用接收方的私钥把数据恢复出来。SM2加密采用的是混合加密框架——说白了,就是用SM2公钥加密一个对称密钥,然后用这个对称密钥加密真正的消息。

具体步骤我列在下面:

  • 加密:先生成一个随机数k,计算点C1 = kG。然后用k与接收方公钥Q的乘积生成一个共享秘密点S = kQ。通过S的x坐标派生出对称密钥,加密消息得到密文C2。最后计算杂凑值C3。最终密文是C1 || C2 || C3。
  • 解密:先检查C1是否在曲线上,然后用私钥d计算S = d * C1。用同样的KDF从S的x坐标派生出对称密钥,解密C2得到原始消息。最后验证C3是否匹配。

这里有个细节——SM2加密时生成的C1其实是椭圆曲线上的一个点,它包含了x和y两个坐标。在传输时可以压缩存储,但我在实际开发中发现直接存两个整数更安全,省去了解压缩步骤可能引入的偏差。

步骤 加密方 解密方
生成随机数 k —
计算共享点 S = kQ S = d * C1
派生密钥 从S.x派生 从S.x派生
加密/解密 SM4对称加密 SM4对称解密
完整性 计算C3 验证C3
我建议:如果你只是做接口加密改造,不用自己实现完整SM2加解密。可以直接调用GM/T 0018国密接口,底层已经帮你处理好点运算和KDF了。但一定要搞清楚你调用的是签名接口还是加密接口,两者不能混用——我见过有人在加密接口里调签名库,结果加解密后数据无法匹配。

嗯,第三章节的主要内容就是这些。椭圆曲线基础、密钥对生成、签名验签、加解密,每个环节都有它存在的理由。你想想看,SM2能够在1024位RSA差不多的性能下提供更高的安全强度,这都是因为它建立在离散对数困难的基础上。搞懂这些,后面的SM3和SM4加解密改造就会顺畅很多。