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//!
11//! 对应 PHP 版的 `Asymmetric\Sm2AsymmetricCipher` 与 `Guomi\Sm2EncryptionService`。
12
13use getrandom::SysRng;
14use sm2::SecretKey;
15use sm2::elliptic_curve::Generate;
16use sm2::elliptic_curve::sec1::ToSec1Point;
17use sm2::pke::{DecryptingKey, EncryptingKey};
18
19use crate::contract::{AsymmetricCipher, Identified};
20use crate::error::{Error, Result};
21use crate::internal::{hex_decode, hex_encode};
22
23const LABEL: &str = "SM2";
24
25/// SM2 密钥对(十六进制):私钥 64 位,公钥 130 位(非压缩 `04‖X‖Y`)。
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct KeyPairHex {
28    pub private_key_hex: String,
29    pub public_key_hex: String,
30}
31
32/// SM2 静态门面(对应 PHP 版 `Sm2EncryptionService`)。
33#[derive(Debug, Default, Clone, Copy)]
34pub struct Sm2Service;
35
36impl Sm2Service {
37    /// 公钥加密,返回十六进制密文(C1C3C2)。
38    pub fn encrypt(plaintext: &[u8], public_key_hex: &str) -> Result<String> {
39        let encrypting_key = parse_public_key(public_key_hex)?;
40        let ciphertext = encrypting_key
41            .encrypt(&mut SysRng, plaintext)
42            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
43        Ok(hex_encode(&ciphertext))
44    }
45
46    /// 私钥解密十六进制密文。
47    pub fn decrypt(ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
48        let decrypting_key = DecryptingKey::new(parse_private_key(private_key_hex)?);
49        let ciphertext =
50            hex_decode(ciphertext_hex).map_err(|_| Error::DecryptionFailed { label: LABEL })?;
51        decrypting_key
52            .decrypt(&ciphertext)
53            .map_err(|_| Error::DecryptionFailed { label: LABEL })
54    }
55
56    /// 生成密钥对:私钥用系统 CSPRNG 采样(不经任何非密码学随机源)。
57    pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
58        let secret_key = SecretKey::try_generate_from_rng(&mut SysRng)
59            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
60        let public_key = secret_key.public_key();
61
62        Ok(KeyPairHex {
63            private_key_hex: hex_encode(secret_key.to_bytes().as_slice()),
64            public_key_hex: hex_encode(public_key.to_sec1_point(false).as_bytes()),
65        })
66    }
67}
68
69/// SM2 非对称加密器(实现 [`AsymmetricCipher`];行为与 [`Sm2Service`] 一致)。
70#[derive(Debug, Default, Clone, Copy)]
71pub struct Sm2AsymmetricCipher;
72
73impl Sm2AsymmetricCipher {
74    pub const IDENTIFIER: &'static str = "sm2";
75
76    pub fn new() -> Self {
77        Self
78    }
79}
80
81impl Identified for Sm2AsymmetricCipher {
82    fn identifier(&self) -> &str {
83        Self::IDENTIFIER
84    }
85}
86
87impl AsymmetricCipher for Sm2AsymmetricCipher {
88    fn encrypt(&self, plaintext: &[u8], public_key_hex: &str) -> Result<String> {
89        Sm2Service::encrypt(plaintext, public_key_hex)
90    }
91
92    fn decrypt(&self, ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
93        Sm2Service::decrypt(ciphertext_hex, private_key_hex)
94    }
95}
96
97/// 接受 130 位(`04‖X‖Y`)/ 128 位(`X‖Y`)/ 66 位(压缩点)三种公钥写法。
98fn parse_public_key(public_key_hex: &str) -> Result<EncryptingKey> {
99    let bytes = hex_decode(public_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
100
101    let sec1 = match bytes.len() {
102        65 | 33 => bytes,
103        64 => {
104            // 裸 X‖Y:补上非压缩点标记。
105            let mut normalized = Vec::with_capacity(65);
106            normalized.push(0x04);
107            normalized.extend_from_slice(&bytes);
108            normalized
109        }
110        _ => return Err(Error::InvalidKey { label: LABEL }),
111    };
112
113    EncryptingKey::from_sec1_bytes(&sec1).map_err(|_| Error::InvalidKey { label: LABEL })
114}
115
116/// 私钥:64 位十六进制(32 字节标量),其余长度一律拒绝。
117fn parse_private_key(private_key_hex: &str) -> Result<SecretKey> {
118    let bytes = hex_decode(private_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
119    SecretKey::from_slice(&bytes).map_err(|_| Error::InvalidKey { label: LABEL })
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn keypair_roundtrip_and_hex_shapes() {
128        let pair = Sm2Service::generate_key_pair_hex().unwrap();
129        assert_eq!(pair.private_key_hex.len(), 64);
130        assert_eq!(pair.public_key_hex.len(), 130);
131        assert!(pair.public_key_hex.starts_with("04"));
132
133        let ciphertext =
134            Sm2Service::encrypt("国密数据 🎉".as_bytes(), &pair.public_key_hex).unwrap();
135        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
136        assert_eq!(plaintext, "国密数据 🎉".as_bytes());
137    }
138
139    #[test]
140    fn bare_xy_public_key_is_accepted_too() {
141        let pair = Sm2Service::generate_key_pair_hex().unwrap();
142        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀
143
144        let ciphertext = Sm2Service::encrypt(b"payload", bare).unwrap();
145        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
146        assert_eq!(plaintext, b"payload");
147    }
148
149    #[test]
150    fn wrong_private_key_or_tampered_ciphertext_fails() {
151        let pair = Sm2Service::generate_key_pair_hex().unwrap();
152        let other = Sm2Service::generate_key_pair_hex().unwrap();
153        let ciphertext = Sm2Service::encrypt(b"secret", &pair.public_key_hex).unwrap();
154
155        assert_eq!(
156            Sm2Service::decrypt(&ciphertext, &other.private_key_hex).unwrap_err(),
157            Error::DecryptionFailed { label: LABEL }
158        );
159
160        let mut tampered = ciphertext.clone();
161        let last = tampered.len() - 1;
162        let flipped = if tampered.as_bytes()[last] == b'0' {
163            '1'
164        } else {
165            '0'
166        };
167        tampered.replace_range(last.., &flipped.to_string());
168        assert_eq!(
169            Sm2Service::decrypt(&tampered, &pair.private_key_hex).unwrap_err(),
170            Error::DecryptionFailed { label: LABEL }
171        );
172    }
173
174    #[test]
175    fn malformed_keys_are_rejected() {
176        let pair = Sm2Service::generate_key_pair_hex().unwrap();
177
178        assert_eq!(
179            Sm2Service::decrypt("00", "zz").unwrap_err(),
180            Error::InvalidKey { label: LABEL }
181        );
182        assert_eq!(
183            Sm2Service::encrypt(b"x", "abcd").unwrap_err(),
184            Error::InvalidKey { label: LABEL }
185        );
186        // 私钥位数不对(多一位十六进制)。
187        let mut longer = pair.private_key_hex.clone();
188        longer.push('a');
189        assert_eq!(
190            Sm2Service::decrypt("00", &longer).unwrap_err(),
191            Error::InvalidKey { label: LABEL }
192        );
193    }
194
195    #[test]
196    fn cipher_trait_matches_service() {
197        let cipher = Sm2AsymmetricCipher::new();
198        assert_eq!(cipher.identifier(), "sm2");
199
200        let pair = Sm2Service::generate_key_pair_hex().unwrap();
201        let ciphertext = cipher.encrypt(b"via trait", &pair.public_key_hex).unwrap();
202        assert_eq!(
203            cipher.decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
204            b"via trait"
205        );
206    }
207}