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 概覽頁面。