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#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct KeyPairHex {
43    pub private_key_hex: String,
44    pub public_key_hex: String,
45}
46
47/// SM2 静态门面(对应 PHP 版 `Sm2EncryptionService`)。
48#[derive(Debug, Default, Clone, Copy)]
49pub struct Sm2Service;
50
51impl Sm2Service {
52    /// 公钥加密,返回十六进制密文(C1C3C2)。
53    pub fn encrypt(plaintext: &[u8], public_key_hex: &str) -> Result<String> {
54        let encrypting_key = parse_public_key(public_key_hex)?;
55        let ciphertext = encrypting_key
56            .encrypt(&mut SysRng, plaintext)
57            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
58        Ok(hex_encode(&ciphertext))
59    }
60
61    /// 私钥解密十六进制密文。
62    pub fn decrypt(ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
63        let decrypting_key = DecryptingKey::new(parse_private_key(private_key_hex)?);
64        let ciphertext =
65            hex_decode(ciphertext_hex).map_err(|_| Error::DecryptionFailed { label: LABEL })?;
66        decrypting_key
67            .decrypt(&ciphertext)
68            .map_err(|_| Error::DecryptionFailed { label: LABEL })
69    }
70
71    /// 生成密钥对:私钥用系统 CSPRNG 采样(不经任何非密码学随机源)。
72    pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
73        let secret_key = SecretKey::try_generate_from_rng(&mut SysRng)
74            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
75        let public_key = secret_key.public_key();
76
77        Ok(KeyPairHex {
78            private_key_hex: hex_encode(secret_key.to_bytes().as_slice()),
79            public_key_hex: hex_encode(public_key.to_sec1_point(false).as_bytes()),
80        })
81    }
82
83    /// 私钥签名,返回定长 64 字节 `r‖s` 的十六进制(128 位)。
84    ///
85    /// 用户标识固定为 [`DEFAULT_DIST_ID`];随机数 `k` 走 RFC 6979 确定性
86    /// 派生(同一私钥 + 同一消息必得同一签名,不依赖运行时随机源)。
87    ///
88    /// 私钥格式非法(十六进制或长度)返回 [`Error::InvalidKey`],
89    /// 其余签名失败返回 [`Error::SignFailed`]。
90    pub fn sign(message: &[u8], private_key_hex: &str) -> Result<String> {
91        let signing_key = SigningKey::new(DEFAULT_DIST_ID, &parse_private_key(private_key_hex)?)
92            .map_err(|_| Error::SignFailed { label: LABEL })?;
93        let signature: Signature = signing_key
94            .try_sign(message)
95            .map_err(|_| Error::SignFailed { label: LABEL })?;
96        Ok(hex_encode(signature.to_bytes().as_slice()))
97    }
98
99    /// 公钥验签。签名不对、消息被改、公钥 / 签名格式错误一律
100    /// [`Error::VerificationFailed`](与契约一致,不区分原因)。
101    ///
102    /// 公钥写法接受与非对称加密相同的三种形式;用户标识固定为
103    /// [`DEFAULT_DIST_ID`],签名方 ID 不一致时验签必然失败。
104    pub fn verify(message: &[u8], signature_hex: &str, public_key_hex: &str) -> Result<()> {
105        let fail = || Error::VerificationFailed { label: LABEL };
106        let sec1 = sec1_public_key_bytes(public_key_hex).map_err(|_| fail())?;
107        let verifying_key =
108            VerifyingKey::from_sec1_bytes(DEFAULT_DIST_ID, &sec1).map_err(|_| fail())?;
109        let signature_bytes = hex_decode(signature_hex).map_err(|_| fail())?;
110        let signature = Signature::from_slice(&signature_bytes).map_err(|_| fail())?;
111        verifying_key
112            .verify(message, &signature)
113            .map_err(|_| fail())
114    }
115}
116
117/// SM2 非对称加密器(实现 [`AsymmetricCipher`];行为与 [`Sm2Service`] 一致)。
118#[derive(Debug, Default, Clone, Copy)]
119pub struct Sm2AsymmetricCipher;
120
121impl Sm2AsymmetricCipher {
122    pub const IDENTIFIER: &'static str = "sm2";
123
124    pub fn new() -> Self {
125        Self
126    }
127}
128
129impl Identified for Sm2AsymmetricCipher {
130    fn identifier(&self) -> &str {
131        Self::IDENTIFIER
132    }
133}
134
135impl AsymmetricCipher for Sm2AsymmetricCipher {
136    fn encrypt(&self, plaintext: &[u8], public_key_hex: &str) -> Result<String> {
137        Sm2Service::encrypt(plaintext, public_key_hex)
138    }
139
140    fn decrypt(&self, ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
141        Sm2Service::decrypt(ciphertext_hex, private_key_hex)
142    }
143}
144
145/// SM2 签名器(实现 [`Signer`];行为与 [`Sm2Service::sign`] / [`Sm2Service::verify`] 一致)。
146#[derive(Debug, Default, Clone, Copy)]
147pub struct Sm2Signer;
148
149impl Sm2Signer {
150    pub const IDENTIFIER: &'static str = "sm2";
151
152    pub fn new() -> Self {
153        Self
154    }
155}
156
157impl Identified for Sm2Signer {
158    fn identifier(&self) -> &str {
159        Self::IDENTIFIER
160    }
161}
162
163impl Signer for Sm2Signer {
164    fn sign(&self, message: &[u8], private_key_hex: &str) -> Result<String> {
165        Sm2Service::sign(message, private_key_hex)
166    }
167
168    fn verify(&self, message: &[u8], signature_hex: &str, public_key_hex: &str) -> Result<()> {
169        Sm2Service::verify(message, signature_hex, public_key_hex)
170    }
171}
172
173/// 接受 130 位(`04‖X‖Y`)/ 128 位(`X‖Y`)/ 66 位(压缩点)三种公钥写法,
174/// 归一化成 SEC1 字节(加密与验签共用)。
175fn sec1_public_key_bytes(public_key_hex: &str) -> Result<Vec<u8>> {
176    let bytes = hex_decode(public_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
177
178    match bytes.len() {
179        65 | 33 => Ok(bytes),
180        64 => {
181            // 裸 X‖Y:补上非压缩点标记。
182            let mut normalized = Vec::with_capacity(65);
183            normalized.push(0x04);
184            normalized.extend_from_slice(&bytes);
185            Ok(normalized)
186        }
187        _ => Err(Error::InvalidKey { label: LABEL }),
188    }
189}
190
191fn parse_public_key(public_key_hex: &str) -> Result<EncryptingKey> {
192    let sec1 = sec1_public_key_bytes(public_key_hex)?;
193    EncryptingKey::from_sec1_bytes(&sec1).map_err(|_| Error::InvalidKey { label: LABEL })
194}
195
196/// 私钥:64 位十六进制(32 字节标量),其余长度一律拒绝。
197fn parse_private_key(private_key_hex: &str) -> Result<SecretKey> {
198    let bytes = hex_decode(private_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
199    SecretKey::from_slice(&bytes).map_err(|_| Error::InvalidKey { label: LABEL })
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn keypair_roundtrip_and_hex_shapes() {
208        let pair = Sm2Service::generate_key_pair_hex().unwrap();
209        assert_eq!(pair.private_key_hex.len(), 64);
210        assert_eq!(pair.public_key_hex.len(), 130);
211        assert!(pair.public_key_hex.starts_with("04"));
212
213        let ciphertext =
214            Sm2Service::encrypt("国密数据 🎉".as_bytes(), &pair.public_key_hex).unwrap();
215        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
216        assert_eq!(plaintext, "国密数据 🎉".as_bytes());
217    }
218
219    #[test]
220    fn bare_xy_public_key_is_accepted_too() {
221        let pair = Sm2Service::generate_key_pair_hex().unwrap();
222        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀
223
224        let ciphertext = Sm2Service::encrypt(b"payload", bare).unwrap();
225        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
226        assert_eq!(plaintext, b"payload");
227    }
228
229    #[test]
230    fn wrong_private_key_or_tampered_ciphertext_fails() {
231        let pair = Sm2Service::generate_key_pair_hex().unwrap();
232        let other = Sm2Service::generate_key_pair_hex().unwrap();
233        let ciphertext = Sm2Service::encrypt(b"secret", &pair.public_key_hex).unwrap();
234
235        assert_eq!(
236            Sm2Service::decrypt(&ciphertext, &other.private_key_hex).unwrap_err(),
237            Error::DecryptionFailed { label: LABEL }
238        );
239
240        let mut tampered = ciphertext.clone();
241        let last = tampered.len() - 1;
242        let flipped = if tampered.as_bytes()[last] == b'0' {
243            '1'
244        } else {
245            '0'
246        };
247        tampered.replace_range(last.., &flipped.to_string());
248        assert_eq!(
249            Sm2Service::decrypt(&tampered, &pair.private_key_hex).unwrap_err(),
250            Error::DecryptionFailed { label: LABEL }
251        );
252    }
253
254    #[test]
255    fn malformed_keys_are_rejected() {
256        let pair = Sm2Service::generate_key_pair_hex().unwrap();
257
258        assert_eq!(
259            Sm2Service::decrypt("00", "zz").unwrap_err(),
260            Error::InvalidKey { label: LABEL }
261        );
262        assert_eq!(
263            Sm2Service::encrypt(b"x", "abcd").unwrap_err(),
264            Error::InvalidKey { label: LABEL }
265        );
266        // 私钥位数不对(多一位十六进制)。
267        let mut longer = pair.private_key_hex.clone();
268        longer.push('a');
269        assert_eq!(
270            Sm2Service::decrypt("00", &longer).unwrap_err(),
271            Error::InvalidKey { label: LABEL }
272        );
273    }
274
275    #[test]
276    fn cipher_trait_matches_service() {
277        let cipher = Sm2AsymmetricCipher::new();
278        assert_eq!(cipher.identifier(), "sm2");
279
280        let pair = Sm2Service::generate_key_pair_hex().unwrap();
281        let ciphertext = cipher.encrypt(b"via trait", &pair.public_key_hex).unwrap();
282        assert_eq!(
283            cipher.decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
284            b"via trait"
285        );
286    }
287
288    #[test]
289    fn sign_verify_roundtrip_and_signature_shape() {
290        let pair = Sm2Service::generate_key_pair_hex().unwrap();
291        let signature = Sm2Service::sign("国密签名 🎉".as_bytes(), &pair.private_key_hex).unwrap();
292
293        assert_eq!(signature.len(), 128); // 64 字节 r‖s
294        Sm2Service::verify("国密签名 🎉".as_bytes(), &signature, &pair.public_key_hex).unwrap();
295    }
296
297    #[test]
298    fn signature_is_deterministic() {
299        // RFC 6979 确定性 k:同一私钥 + 同一消息必得同一签名。
300        let pair = Sm2Service::generate_key_pair_hex().unwrap();
301        let first = Sm2Service::sign(b"same message", &pair.private_key_hex).unwrap();
302        let second = Sm2Service::sign(b"same message", &pair.private_key_hex).unwrap();
303        assert_eq!(first, second);
304    }
305
306    #[test]
307    fn verify_rejects_wrong_input_forms() {
308        let failed = Error::VerificationFailed { label: LABEL };
309        let pair = Sm2Service::generate_key_pair_hex().unwrap();
310        let other = Sm2Service::generate_key_pair_hex().unwrap();
311        let signature = Sm2Service::sign(b"message", &pair.private_key_hex).unwrap();
312
313        // 消息被改。
314        assert_eq!(
315            Sm2Service::verify(b"messagE", &signature, &pair.public_key_hex).unwrap_err(),
316            failed
317        );
318        // 换了别人的公钥。
319        assert_eq!(
320            Sm2Service::verify(b"message", &signature, &other.public_key_hex).unwrap_err(),
321            failed
322        );
323        // 签名被篡改(翻转最后一个十六进制字符)。
324        let mut tampered = signature.clone();
325        let last = tampered.len() - 1;
326        let flipped = if tampered.as_bytes()[last] == b'0' {
327            '1'
328        } else {
329            '0'
330        };
331        tampered.replace_range(last.., &flipped.to_string());
332        assert_eq!(
333            Sm2Service::verify(b"message", &tampered, &pair.public_key_hex).unwrap_err(),
334            failed
335        );
336        // 格式错误:公钥非法、签名非法、签名长度不对——一律 VerificationFailed。
337        assert_eq!(
338            Sm2Service::verify(b"message", &signature, "zz").unwrap_err(),
339            failed
340        );
341        assert_eq!(
342            Sm2Service::verify(b"message", "not-hex", &pair.public_key_hex).unwrap_err(),
343            failed
344        );
345        assert_eq!(
346            Sm2Service::verify(b"message", "00", &pair.public_key_hex).unwrap_err(),
347            failed
348        );
349    }
350
351    #[test]
352    fn bare_xy_public_key_verifies_too() {
353        let pair = Sm2Service::generate_key_pair_hex().unwrap();
354        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀
355
356        let signature = Sm2Service::sign(b"payload", &pair.private_key_hex).unwrap();
357        Sm2Service::verify(b"payload", &signature, bare).unwrap();
358    }
359
360    /// 压缩公钥(66 位 hex = 02/03‖X)同样可验签——文档承诺的第三种写法。
361    #[test]
362    fn compressed_public_key_verifies_too() {
363        use sm2::elliptic_curve::sec1::ToSec1Point;
364
365        let pair = Sm2Service::generate_key_pair_hex().unwrap();
366        let secret = sm2::SecretKey::from_slice(
367            &crate::internal::hex_decode(&pair.private_key_hex).unwrap(),
368        )
369        .unwrap();
370        let compressed_hex =
371            crate::internal::hex_encode(secret.public_key().to_sec1_point(true).as_bytes());
372        assert_eq!(compressed_hex.len(), 66);
373
374        let signature = Sm2Service::sign(b"payload", &pair.private_key_hex).unwrap();
375        Sm2Service::verify(b"payload", &signature, &compressed_hex).unwrap();
376    }
377
378    /// 跨层核验:本库 `Sm2Signer` 的签名必须能被裸 sm2 crate 的 `VerifyingKey`
379    /// (同一 DIST_ID 与编码路径)接受——钉住封装层没有引入 ID / 编码偏差。
380    #[test]
381    fn signature_is_accepted_by_raw_sm2_crate() {
382        let pair = Sm2Service::generate_key_pair_hex().unwrap();
383        let signature_hex = Sm2Service::sign(b"cross-layer", &pair.private_key_hex).unwrap();
384
385        let sec1 = crate::internal::hex_decode(&pair.public_key_hex).unwrap();
386        let verifying_key =
387            sm2::dsa::VerifyingKey::from_sec1_bytes(DEFAULT_DIST_ID, &sec1).unwrap();
388        let sig_bytes = crate::internal::hex_decode(&signature_hex).unwrap();
389        let signature = sm2::dsa::Signature::from_slice(&sig_bytes).unwrap();
390        verifying_key.verify(b"cross-layer", &signature).unwrap();
391    }
392
393    #[test]
394    fn sign_rejects_malformed_private_key() {
395        assert_eq!(
396            Sm2Service::sign(b"x", "zz").unwrap_err(),
397            Error::InvalidKey { label: LABEL }
398        );
399        assert_eq!(
400            Sm2Service::sign(b"x", "abcd").unwrap_err(),
401            Error::InvalidKey { label: LABEL }
402        );
403    }
404
405    #[test]
406    fn signer_trait_matches_service() {
407        let signer = Sm2Signer::new();
408        assert_eq!(signer.identifier(), "sm2");
409
410        let pair = Sm2Service::generate_key_pair_hex().unwrap();
411        let signature = signer.sign(b"via trait", &pair.private_key_hex).unwrap();
412        signer
413            .verify(b"via trait", &signature, &pair.public_key_hex)
414            .unwrap();
415        assert_eq!(
416            signer.verify(b"other", &signature, &pair.public_key_hex),
417            Err(Error::VerificationFailed { label: LABEL })
418        );
419    }
420}