Skip to main content

One post tagged with "SM2"

View All Tags

· 8 min read

椭圆曲线

y^2 = x^3 + a x + b ,其中 4 a^3 + 27 b^2 != 0 ,a 和 b 为实数。

椭圆曲线与椭圆没有关系,只是长得像椭圆周长公式,所以称为椭圆曲线,是一种关于 x 轴对称的曲线,

定义一个“乘法”运算,椭圆曲线上一个点 A 与另一个点 B 运算,AB 可以连城一条直线,与椭圆曲线相交于一个点(只能相交于一点,或者相交于无穷远点),这个点关于 x 轴对称的点为 C ,AB = C 。由于椭圆曲线关于 x 轴对称,C 点也在椭圆曲线上。B 点 可以慢慢靠近 A 点,趋于 A 点时 AA = 2A,相当于定义了运算的标量乘法,相当于一条切线,切线也会于椭圆曲线相交于一个点,并得到 C 点。

这样的运算满足一些性质:

  • 封闭性:加上无穷远点后,A 和 B 都是集合中的成员,则 AB 也是。无穷远点相当于单位元,用 e 表示无穷远点, eA = A
  • 结合律:A(BC) = (AB)C
  • 交换律:AB = BA
  • 单位元:eA = A
  • 逆元:集合中的任意元素 A 存在元素 B,使得 AB = e ,A 和 B 互为逆元

有限域

由于椭圆曲线在 x 和 y 轴上可以无限大,计算机不能处理无限大的数,需要用到有限域。选定一个大的质数 p ,运算的时候对 p 取模,这样得到的结果就是 0 到 p-1 到数。对于除法, a^(-1) mod p ,例如 9^(-1) mod 23 = 18,因为乘以 9 后可得 1 mod 23 = 9 * 18 mod 23 ,所以 9 的逆元是 18 。 p 为素数保证了 0 到 p-1 每个数都有逆元。求逆元的方法为扩展欧几里得算法。

曲线上任意一点 p ,可以经过若干次标量变换后得到无穷远点,然后变回自己,这些点构成循环子群,元素个数为循环子群的阶。假设父群的阶是 N,子群的阶是 n,那么h = N/n一定是一个整数,h 被称为子群的余因子。

数学难题

给定曲线上两点 P 和 Q ,P 经过 k 次变换得到 Q ,Q = kP,求出 k 是一个数学难题,要一个一个点求,时间复杂度 o(n)。而知道 k 和 P ,求出 Q 却很容易,时间复杂度 o(log n)。

密钥交换

Alice 和 Bob 约定好一条椭圆曲线,一个基点 G ,各自用一个随机数 dA 和 dB 生成一个公钥 Pa 和 Pb ,他们可以根据对方的公钥求出相同的密钥 S ,S = dA Pb = dB Pa = dA dB G = dB dA G,而中间人获取到两人的公钥 Pa Pb 却无法获得 S

数字签名

签名:

  • 首先对待签名信息 m 进行哈希得到哈希值 z
  • 生成随机数 k ,k 在 1 到 n-1 之间
  • 求出 P 点, P = kG
  • 计算参数 r ,r = xp mod n ,xp 为 P 点横坐标
  • 计算参数 s ,s = k^(-1) ( z + r dA ) mod n

(r,s) 构成签名

签名是一个表示 r 和 s 的长字符串,有 2 种格式,一种格式是 plain 格式,用一个 128 字符的 hex 字符串,前 64 个字符表示 r ,后 64 个字符表示 s

验证签名:

根据 r 和 s ,定义 u1 和 u2

u1 = s^(-1) z mod n
u2 = s^(-1) r mod n

验签的 Bob 可以根据 G 和 Alice 的公钥 Pa ,以及 s 和 t 计算出点 P ,算法如下:

u1 G + u2 Pa = u1 G + u2 dA G = (u1 + u2 dA) G = s^(-1) (z + r dA) G = k G = P

验证 P 的 x 轴坐标是否是 r

SM2

SM2 标准椭圆曲线叫做 sm2p256v1,曲线的参数如下

p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
xG = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
yG = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
h = 1

阶数很大,接近 2 的 256 次方,h 为 1 ,使所有点在同一个循环子群,安全性更高。指定一条椭圆曲线,可以避免弱曲线,而且各种参数已知。

SM2 签名和验签

hutool 签名 demo ,底层是 bouncycastle ,speed4j 底层也是这个

user id 在计算摘要时用到,两边约定好,不可超过 16 位,可为空,默认值为 "1234567812345678"

byte[] dataBytes="abcd".getBytes(); //待签名数据
String userId="spdb.ebank.nbia"; //双方约定好的 user id ,可为空,默认为 "1234567812345678"

//私钥
String privateKeyHex = "2FEA1D3EFF8668FCC663582AAF0F0300608F145DF5159717F24B3BAA5718561F";

final SM2 sm2 = new SM2(privateKeyHex, null, null);

//使用 PLAIN 格式
sm2.usePlainEncoding();

//使用私钥签名
byte[] sign = sm2.sign(dataBytes, userId.getBytes());

//转化为十六进制字符串,PLAIN 格式
String s = HexUtil.encodeHexStr(sign);
System.out.println("Signature: " + s);

//PLAIN 格式转换为 ASN1 格式
String asn1Sign = HexUtil.encodeHexStr(SmUtil.rsPlainToAsn1(sign));
System.out.println("ASN1: "+asn1Sign);

//ASN1 格式转换为 PLAIN 格式
String plainSign = HexUtil.encodeHexStr(SmUtil.rsAsn1ToPlain(HexUtil.decodeHex(asn1Sign)));
System.out.println("PLAIN: "+plainSign);

输出:

Signature: e01a75085bb6bd9ccaf17cd96990a0c63b4fb1676a72cded49cfe9e7b7badd7bd6a125beec73f659458d6ee5c075bba0e65df2c786c6881cfdf038de90d06574
ASN1: 3046022100e01a75085bb6bd9ccaf17cd96990a0c63b4fb1676a72cded49cfe9e7b7badd7b022100d6a125beec73f659458d6ee5c075bba0e65df2c786c6881cfdf038de90d06574
PLAIN: e01a75085bb6bd9ccaf17cd96990a0c63b4fb1676a72cded49cfe9e7b7badd7bd6a125beec73f659458d6ee5c075bba0e65df2c786c6881cfdf038de90d06574

用公钥验证签名

公钥开头的 04 表示未压缩公钥,后面 64 位为 x 轴坐标,再 64 位为 y 轴坐标。

// Plain 格式的签名
String plainSign = "e01a75085bb6bd9ccaf17cd96990a0c63b4fb1676a72cded49cfe9e7b7badd7bd6a125beec73f659458d6ee5c075bba0e65df2c786c6881cfdf038de90d06574";
// ASN1 格式的签名
String asn1Sign = "3046022100e01a75085bb6bd9ccaf17cd96990a0c63b4fb1676a72cded49cfe9e7b7badd7b022100d6a125beec73f659458d6ee5c075bba0e65df2c786c6881cfdf038de90d06574";

// 公钥,04 开头未压缩格式
String publicKeyHex ="04138C0C8EEA76E2AB3DCE4AAFA195F4EADF34ABF48629659770C6A04D7D0CBF32590488AB33734628C4BEE413EB20A2B4B1B3173153CFA2D50C06F90A3B78A654";

//需要加密的明文
byte[] dataBytes = "abcd".getBytes();

// user id
String userId = "spdb.ebank.nbia";

final SM2 sm2ASN1 = new SM2(null, ECKeyUtil.toSm2PublicParams(publicKeyHex));


// true
boolean verifyASN1 = sm2ASN1.verify(dataBytes, HexUtil.decodeHex(asn1Sign), userId.getBytes());

System.out.println("ASN1: " + verifyASN1);

final SM2 sm2Plain = new SM2(null, ECKeyUtil.toSm2PublicParams(publicKeyHex));

// 设为 plain 格式
sm2Plain.usePlainEncoding();
boolean verifyPlain = sm2Plain.verify(dataBytes, HexUtil.decodeHex(plainSign), userId.getBytes());
System.out.println("Plain: " + verifyPlain);

参考资料

https://www.jianshu.com/p/5b04b66a55a1

https://www.hutool.cn/docs/#/crypto/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86-AsymmetricCrypto