Skip to main content

encryption/guomi/
sm2.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! 国密 SM2 公钥加密 / 私钥解密与签名 / 验签(GB/T 32918)。
4//!
5//! - 密钥与密文 / 签名均为十六进制字符串;
6//! - 密文布局为 **C1C3C2**(现行标准的默认布局),C1 为非压缩点(`04‖X‖Y`);
7//! - 私钥为 64 位十六进制(32 字节标量);公钥接受 130 位(`04‖X‖Y`)、
8//!   128 位(`X‖Y`,自动补 `04`)与 66 位(压缩 `02/03‖X`)三种写法,
9//!   生成密钥对时输出 130 位非压缩形式;
10//! - 签名(GB/T 32918.2)为定长 64 字节 `r‖s`,即 128 位十六进制。
11//!
12//! **用户标识(ZA 的 ID):固定为 [`DEFAULT_DIST_ID`] = `"1234567812345678"`**
13//! ——业界惯例默认 ID(GM/T 0003.2 公开示例实际使用 `"ALICE123@YAHOO.COM"`);
14//! sm2 crate 本身不设默认,必须显式传入。
15//! ID 不随消息或签名传输,**验签方必须使用同一个 ID**,否则验签必然失败。
16//!
17//! 对应 PHP 版的 `Asymmetric\Sm2AsymmetricCipher` 与 `Guomi\Sm2EncryptionService`。
18
19use getrandom::SysRng;
20use sm2::SecretKey;
21use sm2::dsa::signature::{Signer as _, Verifier as _};
22use sm2::dsa::{Signature, SigningKey, VerifyingKey};
23use sm2::elliptic_curve::Generate;
24use sm2::elliptic_curve::sec1::ToSec1Point;
25use sm2::pke::{DecryptingKey, EncryptingKey};
26
27use crate::contract::{AsymmetricCipher, Identified, Signer};
28use crate::error::{Error, Result};
29use crate::internal::{hex_decode, hex_encode};
30
31const LABEL: &str = "SM2";
32
33/// 签名 / 验签的用户标识(ZA 计算中的 ID),签名方与验签方必须一致。
34///
35/// 取值 `"1234567812345678"` 为业界惯例默认 ID(标准公开示例使用
36/// `"ALICE123@YAHOO.COM"`;sm2 crate 不设默认,本库固定此值供开箱即用)。
37/// 16 字节 ASCII,ENTLA = 0x0080。
38pub const DEFAULT_DIST_ID: &str = "1234567812345678";
39
40/// SM2 密钥对(十六进制):私钥 64 位,公钥 130 位(非压缩 `04‖X‖Y`)。
41///
42/// 复用非对称族共用的 [`crate::asymmetric::KeyPairHex`](`Debug` 遮蔽私钥),
43/// 不再本地重复定义——同一份类型两处定义会让遮蔽修复漏掉其中一处。
44pub use crate::asymmetric::KeyPairHex;
45
46/// SM2 静态门面(对应 PHP 版 `Sm2EncryptionService`)。
47#[derive(Debug, Default, Clone, Copy)]
48pub struct Sm2Service;
49
50impl Sm2Service {
51    /// 公钥加密,返回十六进制密文(C1C3C2)。
52    pub fn encrypt(plaintext: &[u8], public_key_hex: &str) -> Result<String> {
53        let encrypting_key = parse_public_key(public_key_hex)?;
54        let ciphertext = encrypting_key
55            .encrypt(&mut SysRng, plaintext)
56            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
57        Ok(hex_encode(&ciphertext))
58    }
59
60    /// 私钥解密十六进制密文。
61    pub fn decrypt(ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
62        let decrypting_key = DecryptingKey::new(parse_private_key(private_key_hex)?);
63        let ciphertext =
64            hex_decode(ciphertext_hex).map_err(|_| Error::DecryptionFailed { label: LABEL })?;
65        decrypting_key
66            .decrypt(&ciphertext)
67            .map_err(|_| Error::DecryptionFailed { label: LABEL })
68    }
69
70    /// 生成密钥对:私钥用系统 CSPRNG 采样(不经任何非密码学随机源)。
71    pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
72        let secret_key = SecretKey::try_generate_from_rng(&mut SysRng)
73            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
74        let public_key = secret_key.public_key();
75
76        Ok(KeyPairHex {
77            private_key_hex: hex_encode(secret_key.to_bytes().as_slice()),
78            public_key_hex: hex_encode(public_key.to_sec1_point(false).as_bytes()),
79        })
80    }
81
82    /// 私钥签名,返回定长 64 字节 `r‖s` 的十六进制(128 位)。
83    ///
84    /// 用户标识固定为 [`DEFAULT_DIST_ID`];随机数 `k` 走 RFC 6979 确定性
85    /// 派生(同一私钥 + 同一消息必得同一签名,不依赖运行时随机源)。
86    ///
87    /// 私钥格式非法(十六进制或长度)返回 [`Error::InvalidKey`],
88    /// 其余签名失败返回 [`Error::SignFailed`]。
89    pub fn sign(message: &[u8], private_key_hex: &str) -> Result<String> {
90        let signing_key = SigningKey::new(DEFAULT_DIST_ID, &parse_private_key(private_key_hex)?)
91            .map_err(|_| Error::SignFailed { label: LABEL })?;
92        let signature: Signature = signing_key
93            .try_sign(message)
94            .map_err(|_| Error::SignFailed { label: LABEL })?;
95        Ok(hex_encode(signature.to_bytes().as_slice()))
96    }
97
98    /// 公钥验签。签名不对、消息被改、公钥 / 签名格式错误一律
99    /// [`Error::VerificationFailed`](与契约一致,不区分原因)。
100    ///
101    /// 公钥写法接受与非对称加密相同的三种形式;用户标识固定为
102    /// [`DEFAULT_DIST_ID`],签名方 ID 不一致时验签必然失败。
103    pub fn verify(message: &[u8], signature_hex: &str, public_key_hex: &str) -> Result<()> {
104        let fail = || Error::VerificationFailed { label: LABEL };
105        let sec1 = sec1_public_key_bytes(public_key_hex).map_err(|_| fail())?;
106        let verifying_key =
107            VerifyingKey::from_sec1_bytes(DEFAULT_DIST_ID, &sec1).map_err(|_| fail())?;
108        let signature_bytes = hex_decode(signature_hex).map_err(|_| fail())?;
109        let signature = Signature::from_slice(&signature_bytes).map_err(|_| fail())?;
110        verifying_key
111            .verify(message, &signature)
112            .map_err(|_| fail())
113    }
114}
115
116/// SM2 非对称加密器(实现 [`AsymmetricCipher`];行为与 [`Sm2Service`] 一致)。
117#[derive(Debug, Default, Clone, Copy)]
118pub struct Sm2AsymmetricCipher;
119
120impl Sm2AsymmetricCipher {
121    pub const IDENTIFIER: &'static str = "sm2";
122
123    pub fn new() -> Self {
124        Self
125    }
126}
127
128impl Identified for Sm2AsymmetricCipher {
129    fn identifier(&self) -> &str {
130        Self::IDENTIFIER
131    }
132}
133
134impl AsymmetricCipher for Sm2AsymmetricCipher {
135    fn encrypt(&self, plaintext: &[u8], public_key_hex: &str) -> Result<String> {
136        Sm2Service::encrypt(plaintext, public_key_hex)
137    }
138
139    fn decrypt(&self, ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
140        Sm2Service::decrypt(ciphertext_hex, private_key_hex)
141    }
142}
143
144/// SM2 签名器(实现 [`Signer`];行为与 [`Sm2Service::sign`] / [`Sm2Service::verify`] 一致)。
145#[derive(Debug, Default, Clone, Copy)]
146pub struct Sm2Signer;
147
148impl Sm2Signer {
149    pub const IDENTIFIER: &'static str = "sm2";
150
151    pub fn new() -> Self {
152        Self
153    }
154}
155
156impl Identified for Sm2Signer {
157    fn identifier(&self) -> &str {
158        Self::IDENTIFIER
159    }
160}
161
162impl Signer for Sm2Signer {
163    fn sign(&self, message: &[u8], private_key_hex: &str) -> Result<String> {
164        Sm2Service::sign(message, private_key_hex)
165    }
166
167    fn verify(&self, message: &[u8], signature_hex: &str, public_key_hex: &str) -> Result<()> {
168        Sm2Service::verify(message, signature_hex, public_key_hex)
169    }
170}
171
172/// 接受 130 位(`04‖X‖Y`)/ 128 位(`X‖Y`)/ 66 位(压缩点)三种公钥写法,
173/// 归一化成 SEC1 字节(加密与验签共用)。
174fn sec1_public_key_bytes(public_key_hex: &str) -> Result<Vec<u8>> {
175    let bytes = hex_decode(public_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
176
177    match bytes.len() {
178        65 | 33 => Ok(bytes),
179        64 => {
180            // 裸 X‖Y:补上非压缩点标记。
181            let mut normalized = Vec::with_capacity(65);
182            normalized.push(0x04);
183            normalized.extend_from_slice(&bytes);
184            Ok(normalized)
185        }
186        _ => Err(Error::InvalidKey { label: LABEL }),
187    }
188}
189
190fn parse_public_key(public_key_hex: &str) -> Result<EncryptingKey> {
191    let sec1 = sec1_public_key_bytes(public_key_hex)?;
192    EncryptingKey::from_sec1_bytes(&sec1).map_err(|_| Error::InvalidKey { label: LABEL })
193}
194
195/// 私钥:64 位十六进制(32 字节标量),其余长度一律拒绝。
196fn parse_private_key(private_key_hex: &str) -> Result<SecretKey> {
197    let bytes = hex_decode(private_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
198    SecretKey::from_slice(&bytes).map_err(|_| Error::InvalidKey { label: LABEL })
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn keypair_roundtrip_and_hex_shapes() {
207        let pair = Sm2Service::generate_key_pair_hex().unwrap();
208        assert_eq!(pair.private_key_hex.len(), 64);
209        assert_eq!(pair.public_key_hex.len(), 130);
210        assert!(pair.public_key_hex.starts_with("04"));
211
212        let ciphertext =
213            Sm2Service::encrypt("国密数据 🎉".as_bytes(), &pair.public_key_hex).unwrap();
214        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
215        assert_eq!(plaintext, "国密数据 🎉".as_bytes());
216    }
217
218    #[test]
219    fn bare_xy_public_key_is_accepted_too() {
220        let pair = Sm2Service::generate_key_pair_hex().unwrap();
221        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀
222
223        let ciphertext = Sm2Service::encrypt(b"payload", bare).unwrap();
224        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
225        assert_eq!(plaintext, b"payload");
226    }
227
228    #[test]
229    fn wrong_private_key_or_tampered_ciphertext_fails() {
230        let pair = Sm2Service::generate_key_pair_hex().unwrap();
231        let other = Sm2Service::generate_key_pair_hex().unwrap();
232        let ciphertext = Sm2Service::encrypt(b"secret", &pair.public_key_hex).unwrap();
233
234        assert_eq!(
235            Sm2Service::decrypt(&ciphertext, &other.private_key_hex).unwrap_err(),
236            Error::DecryptionFailed { label: LABEL }
237        );
238
239        let mut tampered = ciphertext.clone();
240        let last = tampered.len() - 1;
241        let flipped = if tampered.as_bytes()[last] == b'0' {
242            '1'
243        } else {
244            '0'
245        };
246        tampered.replace_range(last.., &flipped.to_string());
247        assert_eq!(
248            Sm2Service::decrypt(&tampered, &pair.private_key_hex).unwrap_err(),
249            Error::DecryptionFailed { label: LABEL }
250        );
251    }
252
253    #[test]
254    fn malformed_keys_are_rejected() {
255        let pair = Sm2Service::generate_key_pair_hex().unwrap();
256
257        assert_eq!(
258            Sm2Service::decrypt("00", "zz").unwrap_err(),
259            Error::InvalidKey { label: LABEL }
260        );
261        assert_eq!(
262            Sm2Service::encrypt(b"x", "abcd").unwrap_err(),
263            Error::InvalidKey { label: LABEL }
264        );
265        // 私钥位数不对(多一位十六进制)。
266        let mut longer = pair.private_key_hex.clone();
267        longer.push('a');
268        assert_eq!(
269            Sm2Service::decrypt("00", &longer).unwrap_err(),
270            Error::InvalidKey { label: LABEL }
271        );
272    }
273
274    #[test]
275    fn cipher_trait_matches_service() {
276        let cipher = Sm2AsymmetricCipher::new();
277        assert_eq!(cipher.identifier(), "sm2");
278
279        let pair = Sm2Service::generate_key_pair_hex().unwrap();
280        let ciphertext = cipher.encrypt(b"via trait", &pair.public_key_hex).unwrap();
281        assert_eq!(
282            cipher.decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
283            b"via trait"
284        );
285    }
286
287    #[test]
288    fn sign_verify_roundtrip_and_signature_shape() {
289        let pair = Sm2Service::generate_key_pair_hex().unwrap();
290        let signature = Sm2Service::sign("国密签名 🎉".as_bytes(), &pair.private_key_hex).unwrap();
291
292        assert_eq!(signature.len(), 128); // 64 字节 r‖s
293        Sm2Service::verify("国密签名 🎉".as_bytes(), &signature, &pair.public_key_hex).unwrap();
294    }
295
296    #[test]
297    fn signature_is_deterministic() {
298        // RFC 6979 确定性 k:同一私钥 + 同一消息必得同一签名。
299        let pair = Sm2Service::generate_key_pair_hex().unwrap();
300        let first = Sm2Service::sign(b"same message", &pair.private_key_hex).unwrap();
301        let second = Sm2Service::sign(b"same message", &pair.private_key_hex).unwrap();
302        assert_eq!(first, second);
303    }
304
305    #[test]
306    fn verify_rejects_wrong_input_forms() {
307        let failed = Error::VerificationFailed { label: LABEL };
308        let pair = Sm2Service::generate_key_pair_hex().unwrap();
309        let other = Sm2Service::generate_key_pair_hex().unwrap();
310        let signature = Sm2Service::sign(b"message", &pair.private_key_hex).unwrap();
311
312        // 消息被改。
313        assert_eq!(
314            Sm2Service::verify(b"messagE", &signature, &pair.public_key_hex).unwrap_err(),
315            failed
316        );
317        // 换了别人的公钥。
318        assert_eq!(
319            Sm2Service::verify(b"message", &signature, &other.public_key_hex).unwrap_err(),
320            failed
321        );
322        // 签名被篡改(翻转最后一个十六进制字符)。
323        let mut tampered = signature.clone();
324        let last = tampered.len() - 1;
325        let flipped = if tampered.as_bytes()[last] == b'0' {
326            '1'
327        } else {
328            '0'
329        };
330        tampered.replace_range(last.., &flipped.to_string());
331        assert_eq!(
332            Sm2Service::verify(b"message", &tampered, &pair.public_key_hex).unwrap_err(),
333            failed
334        );
335        // 格式错误:公钥非法、签名非法、签名长度不对——一律 VerificationFailed。
336        assert_eq!(
337            Sm2Service::verify(b"message", &signature, "zz").unwrap_err(),
338            failed
339        );
340        assert_eq!(
341            Sm2Service::verify(b"message", "not-hex", &pair.public_key_hex).unwrap_err(),
342            failed
343        );
344        assert_eq!(
345            Sm2Service::verify(b"message", "00", &pair.public_key_hex).unwrap_err(),
346            failed
347        );
348    }
349
350    #[test]
351    fn bare_xy_public_key_verifies_too() {
352        let pair = Sm2Service::generate_key_pair_hex().unwrap();
353        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀
354
355        let signature = Sm2Service::sign(b"payload", &pair.private_key_hex).unwrap();
356        Sm2Service::verify(b"payload", &signature, bare).unwrap();
357    }
358
359    /// 压缩公钥(66 位 hex = 02/03‖X)同样可验签——文档承诺的第三种写法。
360    #[test]
361    fn compressed_public_key_verifies_too() {
362        use sm2::elliptic_curve::sec1::ToSec1Point;
363
364        let pair = Sm2Service::generate_key_pair_hex().unwrap();
365        let secret = sm2::SecretKey::from_slice(
366            &crate::internal::hex_decode(&pair.private_key_hex).unwrap(),
367        )
368        .unwrap();
369        let compressed_hex =
370            crate::internal::hex_encode(secret.public_key().to_sec1_point(true).as_bytes());
371        assert_eq!(compressed_hex.len(), 66);
372
373        let signature = Sm2Service::sign(b"payload", &pair.private_key_hex).unwrap();
374        Sm2Service::verify(b"payload", &signature, &compressed_hex).unwrap();
375    }
376
377    /// 跨层核验:本库 `Sm2Signer` 的签名必须能被裸 sm2 crate 的 `VerifyingKey`
378    /// (同一 DIST_ID 与编码路径)接受——钉住封装层没有引入 ID / 编码偏差。
379    #[test]
380    fn signature_is_accepted_by_raw_sm2_crate() {
381        let pair = Sm2Service::generate_key_pair_hex().unwrap();
382        let signature_hex = Sm2Service::sign(b"cross-layer", &pair.private_key_hex).unwrap();
383
384        let sec1 = crate::internal::hex_decode(&pair.public_key_hex).unwrap();
385        let verifying_key =
386            sm2::dsa::VerifyingKey::from_sec1_bytes(DEFAULT_DIST_ID, &sec1).unwrap();
387        let sig_bytes = crate::internal::hex_decode(&signature_hex).unwrap();
388        let signature = sm2::dsa::Signature::from_slice(&sig_bytes).unwrap();
389        verifying_key.verify(b"cross-layer", &signature).unwrap();
390    }
391
392    #[test]
393    fn sign_rejects_malformed_private_key() {
394        assert_eq!(
395            Sm2Service::sign(b"x", "zz").unwrap_err(),
396            Error::InvalidKey { label: LABEL }
397        );
398        assert_eq!(
399            Sm2Service::sign(b"x", "abcd").unwrap_err(),
400            Error::InvalidKey { label: LABEL }
401        );
402    }
403
404    #[test]
405    fn signer_trait_matches_service() {
406        let signer = Sm2Signer::new();
407        assert_eq!(signer.identifier(), "sm2");
408
409        let pair = Sm2Service::generate_key_pair_hex().unwrap();
410        let signature = signer.sign(b"via trait", &pair.private_key_hex).unwrap();
411        signer
412            .verify(b"via trait", &signature, &pair.public_key_hex)
413            .unwrap();
414        assert_eq!(
415            signer.verify(b"other", &signature, &pair.public_key_hex),
416            Err(Error::VerificationFailed { label: LABEL })
417        );
418    }
419
420    /// 外部互通验签:**GM/T 0003.5-2012 附录 A**(同 GB/T 32918.5-2016)签名示例,
421    /// 消息 `"message digest"`、私钥与密文示例相同(`3945208F…C5B8`),
422    /// 取自 OpenSSL 官方测试文件
423    /// [`test/sm2_internal_test.c`](https://github.com/openssl/openssl/blob/master/test/sm2_internal_test.c)
424    /// 的 `sm2_sig_test`,其注释标注 "From Annex A in both GM/T0003.5-2012 and
425    /// GB/T 32918.5-2016"。
426    ///
427    /// 该 `r‖s` 由 OpenSSL 以**默认用户标识**计算(测试中传 `NULL`);OpenSSL 源码
428    /// `crypto/sm2/sm2_sign.c` 的 `default_sm2_id` 即 `"1234567812345678"`,
429    /// 与本库 [`DEFAULT_DIST_ID`] 逐字节一致,故可直接验签、无需改写标识。
430    /// 公钥 `P_A = d_A·G`(`04‖X‖Y`)由私钥经标准 SM2 曲线点乘派生——同一份
431    /// 独立 Python 点乘实现已先用附录 C 的 `C1 = k·G` 对上 OpenSSL 向量。
432    #[test]
433    fn verify_accepts_gmt0003_annex_a_openssl_signature() {
434        const PUBLIC_KEY: &str = concat!(
435            "04",                                                               // 非压缩点标记
436            "09F9DF311E5421A150DD7D161E4BC5C672179FAD1833FC076BB08FF356F35020", // X
437            "CCEA490CE26775A52DC6EA718CC1AA600AED05FBF35E084A6632F6072DA9AD13", // Y
438        );
439        const SIGNATURE: &str = concat!(
440            "F5A03B0648D2C4630EEAC513E1BB81A15944DA3827D5B74143AC7EACEEE720B3", // r
441            "B1B6AA29DF212FD8763182BC0D421CA1BB9038FD1F7F42D4840B69C485BBC1AA", // s
442        );
443
444        Sm2Service::verify(b"message digest", SIGNATURE, PUBLIC_KEY)
445            .expect("GM/T 0003.5 附录 A 的 r‖s 应通过验签");
446    }
447}