SM2 加密 / 解密 / 签名

推荐曲线 sm2p256v1(GB/T 32918.5)上的完整 SM2 公钥工具箱:生成密钥对,用基于 SM3 的 ZA 方案签名与验签(GB/T 32918.2),并按标准 C1C3C2 顺序、旧式 C1C2C3 顺序或 ASN.1 DER 加解密。全部在浏览器中运行;不会上传任何数据。
私钥 (hex,64 位 —— 请像口令一样保管)
公钥 (hex,带 04 前缀的 130 位,或 128 位的 x‖y)
私钥用于签名与解密,公钥用于验签与加密。密钥绝不离开本页。
用户 ID(混入 ZA 的“用户标识”;默认值为事实标准)
按以下方式读取消息: 固定 k(高级):
r (hex)
s (hex)
Signature, ASN.1 DER (SEQUENCE { r INTEGER, s INTEGER } —— OpenSSL/GmSSL 使用的格式)
关于固定 k 输入框:每一次真实签名都必须使用新鲜且不可预测的 k —— 复用或被猜到 k 会泄露私钥(索尼 ECDSA 的教训)。本页在该框为空时用 crypto.getRandomValues 生成随机 k。填写此框只是为了复现已知向量,用于教学或测试。
用户 ID(必须与签名方一致)
按以下方式读取消息: 签名格式:
r (hex)
s (hex)
✓ 签名有效
✗ 签名无效
按以下方式读取消息: 密文格式: 输出:
固定 k(高级):
混合加密提醒:SM2 加密适用于短小的秘密(会话密钥、PIN),而不是批量数据 —— 每条消息都要付 96 字节的 C1+C3 开销和一次椭圆曲线点乘。请用 SM2 加密一把随机的 SM4/AES 密钥,再用对称密码加密数据(SM4 页面 / AES 页面)。
按以下方式读取密文: 顺序: 按以下方式输出明文:
OpenSSL 交叉验证 —— 本页的 C1C3C2 hex 与 openssl pkeyutl -sm2encrypt -pubin -inkey pub.pem(OpenSSL 3.x,默认带 -pkeyopt ec_scheme:c1c3c2)以及 GmSSL 的 gmssl sm2randkey / sm2encrypt 工具一致;在相同的用户 ID(-pkeyopt sm2_userid:1234567812345678)下,签名与 openssl dgst -sm3 -sign 一致。解密时接受 C1 前面多出的 04 字节(多个库会这样输出)。
合规说明:SM2 是由中国商用密码合规与互操作需求驱动的算法选择,其安全性与 256 位 ECC(NIST P-256)相当。生产环境中请配合真正的密钥管理系统或硬件密钥使用 —— 浏览器页面是计算器,不是保险柜。
自检
重跑由一份独立的 Python 参考实现产出的密钥推导、固定 k 签名与加密向量(并与 gmssl 交叉核对),以及 DER 签名与 DER 密文往返、随机 k 的签名/验签与加解密往返,还有反向路径(C3 被篡改、密文被截断、公钥不在曲线上、r = 0)。
什么是 SM2?
一句话:SM2 是中国的国家标准公钥密码体系 —— 固定曲线 sm2p256v1 上的椭圆曲线签名与加密方案,标准编号 GB/T 32918-2016(前身 GM/T 0003-2012),在中国“SM”套件中扮演的角色,正如 ECDSA/ECDH(P-256)在 NIST 世界里的角色。
一段话讲清 ECC(直觉版)

所有椭圆曲线密码都建立在同一个诀窍上:给定大素域上曲线的基点 G 以及倍数 P = d·G,要从 P 反推出 d 就得解椭圆曲线离散对数问题,而目前没有任何快速算法能做到。私钥 d 就是一个 256 位的数;公钥就是点 d·G。SM2 并不是新的曲线运算 —— 它是在某一特定曲线上签名(类似 ECDSA)和加密(类似迷你版 ECIES)的一套具体做法,只是把哈希换成了 SM3。

sm2p256v1 与 NIST P-256
方面sm2p256v1NIST P-256(secp256r1)
域大小256 位素数256 位素数
曲线方程y² = x³ + ax + by² = x³ − 3x + b(a = −3)
安全强度≈ 128 位≈ 128 位
签名SM2(GB/T 32918.2),基于 SM3,用户 ID 绑定进 ZAECDSA with SHA-256
加密SM2(GB/T 32918.4),C1C3C2ECIES(ANSI X9.63,未被 NIST 标准化)
哈希SM3(GB/T 32905)SHA-2 家族
发布时间2010 年设计,GM/T 0003-2012,GB/T 32918-2016;已纳入 ISO/IEC 14888-31999 年(SEC 2 / NIST)
TLS 支持TLCP(GM/T 386)与 TLS 1.3 RFC 8998 套件到处都是

两条曲线的实际安全性相同,也都没有可行的攻击。选哪一条取决于法规与互操作,而不是强度。

用户 ID 与 ZA —— SM2 比 ECDSA 多签进去的东西

在哈希之前,SM2 会把签名方的身份折进摘要:ZA = SM3(ENTL ‖ ID ‖ a ‖ b ‖ xG ‖ yG ‖ xA ‖ yA),其中 ENTLa 是 ID 的比特长度,中间四个字段钉死具体的曲线,最后两个是签名方的公钥。消息按 SM3(ZA ‖ M) 计算哈希。由此产生两个后果:签名与所声称的身份绑定(这是特性);验证方必须被告知签名方的 ID 并使用同一个 ID(这是经典的互操作坑)。几乎整个生态都默认使用 16 位的 1234567812345678 —— 它并非出自标准正文,而是来自 GM/T 0003 附录的示例;OpenSSL 的默认值与之相同,本页也一样。

密文格式:C1C3C2、C1C2C3 与 DER

一份 SM2 密文由三部分组成:C1 = 临时点 kG(64 字节,x‖y),C3 = SM3(x2 ‖ M ‖ y2)(32 字节,完整性),C2 = M ⊕ KDF(x2 ‖ y2)(被掩码的消息)。2012 年的 GM/T 0003 文本把它们拼接为 C1 ‖ C2 ‖ C3;2016 年的 GB/T 32918.4 标准改序为 C1 ‖ C3 ‖ C2,OpenSSL 1.1.1+ 也跟着改了。老旧的银行与政务系统仍在使用旧顺序 —— 这就是本页两种顺序都提供、并附带 ASN.1 形式(INTEGER x1、INTEGER y1、OCTET STRING C3、OCTET STRING C2 组成的 SEQUENCE,某些库会输出这种格式)的原因。当用对方的工具解密失败时,字节顺序是第一个要检查的东西。

常见错误
  • k 复用或来路不正。签名与加密中的随机数 k 必须每次操作都新鲜且不可预测。两个签名一旦复用同一个 k,任何同时看到这两者的人都能直接算出私钥。本页的 k 取自浏览器 CSPRNG;固定 k 输入框仅为复现已知向量而设。
  • 用户 ID 不匹配。用默认 ID 签名、却用自定义 ID 验签(或反之),则每一个合法签名都会失败。ID 属于被签名数据的一部分。
  • 以为到处都是 C1C3C2。GM/T 0003 时代的系统、部分智能卡以及较老的 GmSSL 构建默认使用 C1C2C3;若干工具还会在前面加一个未压缩点标记字节 04。这些形式本页在解密时全都接受。
  • 用 SM2 加密批量数据。每份密文都要带 96 字节开销,并耗费两次点乘。请用 SM2 加密密钥、用 SM4/AES 加密数据。
  • 把带 04 前缀的公钥当成 65 字节原始密钥。04 只是编码标记:密钥本体是 x ‖ y,共 128 个 hex 字符。这两种写法本页都接受。
常见问题

能和 OpenSSL / GmSSL 互通吗?能。对同一公钥、消息和 k,本页的 C1C3C2 hex 输出与 openssl pkeyutl -sm2encrypt 一致;DER 签名与 openssl dgst -sm3 -sign 的输出逐字节相同(用 -pkeyopt sm2_userid:... 设置用户 ID)。随机 k 的运行结果当然各不相同 —— 请通过解密/验签来比对,不要比较是否相等。

为什么这里没有 SM2 密钥交换?GB/T 32918.3 定义的是一个交互式的认证密钥交换协议,做成单页计算器毫无意义。签名与加密已覆盖所有非交互式用途。

WebCrypto 支持 SM2 吗?不支持 —— 没有任何浏览器实现它(Chrome 只在非标准开关后提供,甚至可能没有)。本页运行的是从零编写的 JavaScript 实现,并把标准自带的附录向量接进了上面的自检。

能用 OpenSSL 的 PEM 密钥吗?请改为导出原始 hex:openssl ec -in key.pem -text 会打印出私钥标量与公钥点。本页只接受 hex 密钥,这是刻意为之 —— PEM/SEC1 那一套机制放在它该在的RSA 页面。

那 SM9 是什么?是另一类东西:标识密码,任意字符串(比如一个邮箱地址)就是公钥,由 KGC 签发私钥。参见SM9 概览页面。