3、密码学基础与椭圆曲线数学

说实话,刚接触SM2那会儿,我觉得椭圆曲线这玩意儿挺玄的。后来被项目逼着啃了一遍有限域和群论,才意识到——你如果不把这块地基打牢,后面做密钥生成、签名验签简直就是一锅粥。这章我们就聊聊底层的数学工具,我会结合我自己踩过的坑来讲。

3.1 有限域——椭圆曲线的“地基”

有限域,说白了就是一个元素个数有限的集合,里面的加减乘除都封闭。SM2用的是素域GF(p),p是一个256位的奇素数。我记得自己第一次实现域内求逆时,直接调了Python的pow(a, p-2, p) —— 嗯,费马小定理,简单粗暴。但当时没考虑常数时间,结果侧信道攻击直接凉了。

避坑:我曾经在嵌入式平台上用扩展欧几里得算法做模逆,没做掩码,结果被SPA攻击扒了私钥。现在项目里一律要求用蒙哥马利模乘加固定时间处理。

有限域的三要素:

  • 特征p:SM2中p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
  • 阶p:域内共有p个元素:0, 1, …, p-1
  • 运算:加减乘除模p,除法通过乘以逆元实现

3.2 椭圆曲线方程:Weierstrass标准型

SM2选用的曲线方程长这样:

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

其中a, b是域内常数,且判别式4a³+27b² ≠ 0(保证曲线无奇点)。
SM2国标里,a = p-3(为了加速计算),b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93。

你想想看,曲线上每个点(x, y)其实都是域GF(p)上的元素对。我刚开始做SM2密钥生成时,老把点坐标和域元素搞混——其实点坐标本身就是域元素,但点与点之间的运算属于群运算,别混淆了。

3.3 加法与倍点运算——核心操作

椭圆曲线上的加法定义很巧妙:过点P和Q的直线与曲线交于第三点R',其关于x轴的对称点就是P+Q的结果。倍点就是P+P,这时直线退化为切线。我见过不少初学者以为椭圆曲线加法就是坐标分量相加——大错特错。

加法公式(仿射坐标):

  • 当P≠Q且x1≠x2时:斜率 λ = (y2-y1)*(x2-x1)⁻¹ mod p
  • 当P=Q时(倍点):λ = (3x1²+a)*(2y1)⁻¹ mod p
  • 最终结果:x3 = λ² - x1 - x2, y3 = λ(x1 - x3) - y1

我在做硬件加速时,发现仿射坐标每步都要做模逆——这个模逆在FPGA上又慢又贵。后来改成雅可比投影坐标,把模逆消到整个倍点循环最后一步,吞吐量翻了4倍。你如果做高性能实现,记住这句话:投影坐标是王道。

3.4 阶与循环子群——理解群的规模

椭圆曲线上所有点加上无穷远点O,构成一个阿贝尔群。这个群的阶记为#E。SM2曲线的阶n是一个256位的素数。阶为什么重要?因为点的标量乘法kG,其周期等于k mod n。如果n不是素数,就可能存在小子群攻击。

我曾经在测试中故意选了一条阶含小因子的曲线,然后用Pohlig-Hellman算法秒解了离散对数——说白了,阶一定要是素数或接近素数。SM2的n是素数,所以整条曲线就是一个循环群,任意非无穷远点都可以生成整个群。

经验:实际生成SM2密钥时,私钥d要取[1, n-2]的随机数,公钥P = dG。这里的G是基点,阶为n。我习惯在密钥生成后做一次验证——检查dG是否等于O、d是否满足范围。这一步看似多余,但能直接筛掉内存随机数生成器被污染的情况。

循环子群的概念也很重要:任何非无穷远点P都能生成一个循环子群,其阶是n的因子。SM2的基点阶就是n本身,所以它生成的子群就是整个曲线群。在做SM2签名/加密时,所有标量乘法都在这个子群内闭包。

椭圆曲线密码学数学基础结构 素域 GF(p) 特征p=0xFFFFFFFE… 阶p Weierstrass方程 y² = x³+ax+b (mod p) 判别式4a³+27b²≠0 加法群(点+无穷远点O) 群运算:加法/倍点 仿射/雅可比坐标 阶 n (素数) 与 循环子群 基点G阶=n → 整个曲线为循环群 → 离散对数困难 SM2密钥对 / 标量乘法 私钥d ∈ [1,n-2],公钥P=dG 投影坐标加速倍点 常数时间防侧信道 验证阶n为素数 防小子群攻击

你看,从有限域到曲线方程,再到加法群和循环子群,层层递进。我个人习惯在学新算法前,先在脑海中画出这个结构图——哪部分属于域,哪部分属于群,标量乘法和域元素运算怎么交叉。分清楚了,后面看SM2的签名、加密、密钥交换代码,就基本不会迷路。