encryption-rust 1.3.0

可插拔密码学组件库的 Rust 实现:在统一契约下提供对称加密、非对称加密、哈希、密钥派生(HKDF / PBKDF2 / Argon2 / scrypt)、签名(SM2 / ECDSA / Ed25519)、密钥协商(ECDH / X25519)与后量子密钥封装(ML-KEM),包含 AES/Sodium 与国密 SM2/SM3/SM4/ZUC 等实现
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz

//! 国密 SM2 公钥加密 / 私钥解密与签名 / 验签(GB/T 32918)。
//!
//! - 密钥与密文 / 签名均为十六进制字符串;
//! - 密文布局为 **C1C3C2**(现行标准的默认布局),C1 为非压缩点(`04‖X‖Y`);
//! - 私钥为 64 位十六进制(32 字节标量);公钥接受 130 位(`04‖X‖Y`)、
//!   128 位(`X‖Y`,自动补 `04`)与 66 位(压缩 `02/03‖X`)三种写法,
//!   生成密钥对时输出 130 位非压缩形式;
//! - 签名(GB/T 32918.2)为定长 64 字节 `r‖s`,即 128 位十六进制。
//!
//! **用户标识(ZA 的 ID):固定为 [`DEFAULT_DIST_ID`] = `"1234567812345678"`**
//! ——业界惯例默认 ID(GM/T 0003.2 公开示例实际使用 `"ALICE123@YAHOO.COM"`);
//! sm2 crate 本身不设默认,必须显式传入。
//! ID 不随消息或签名传输,**验签方必须使用同一个 ID**,否则验签必然失败。
//!
//! 对应 PHP 版的 `Asymmetric\Sm2AsymmetricCipher` 与 `Guomi\Sm2EncryptionService`。

use getrandom::SysRng;
use sm2::SecretKey;
use sm2::dsa::signature::{Signer as _, Verifier as _};
use sm2::dsa::{Signature, SigningKey, VerifyingKey};
use sm2::elliptic_curve::Generate;
use sm2::elliptic_curve::sec1::ToSec1Point;
use sm2::pke::{DecryptingKey, EncryptingKey};

use crate::contract::{AsymmetricCipher, Identified, Signer};
use crate::error::{Error, Result};
use crate::internal::{hex_decode, hex_encode};

const LABEL: &str = "SM2";

/// 签名 / 验签的用户标识(ZA 计算中的 ID),签名方与验签方必须一致。
///
/// 取值 `"1234567812345678"` 为业界惯例默认 ID(标准公开示例使用
/// `"ALICE123@YAHOO.COM"`;sm2 crate 不设默认,本库固定此值供开箱即用)。
/// 16 字节 ASCII,ENTLA = 0x0080。
pub const DEFAULT_DIST_ID: &str = "1234567812345678";

/// SM2 密钥对(十六进制):私钥 64 位,公钥 130 位(非压缩 `04‖X‖Y`)。
///
/// 复用非对称族共用的 [`crate::asymmetric::KeyPairHex`](`Debug` 遮蔽私钥),
/// 不再本地重复定义——同一份类型两处定义会让遮蔽修复漏掉其中一处。
pub use crate::asymmetric::KeyPairHex;

/// SM2 静态门面(对应 PHP 版 `Sm2EncryptionService`)。
#[derive(Debug, Default, Clone, Copy)]
pub struct Sm2Service;

impl Sm2Service {
    /// 公钥加密,返回十六进制密文(C1C3C2)。
    pub fn encrypt(plaintext: &[u8], public_key_hex: &str) -> Result<String> {
        let encrypting_key = parse_public_key(public_key_hex)?;
        let ciphertext = encrypting_key
            .encrypt(&mut SysRng, plaintext)
            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
        Ok(hex_encode(&ciphertext))
    }

    /// 私钥解密十六进制密文。
    pub fn decrypt(ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
        let decrypting_key = DecryptingKey::new(parse_private_key(private_key_hex)?);
        let ciphertext =
            hex_decode(ciphertext_hex).map_err(|_| Error::DecryptionFailed { label: LABEL })?;
        decrypting_key
            .decrypt(&ciphertext)
            .map_err(|_| Error::DecryptionFailed { label: LABEL })
    }

    /// 生成密钥对:私钥用系统 CSPRNG 采样(不经任何非密码学随机源)。
    pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
        let secret_key = SecretKey::try_generate_from_rng(&mut SysRng)
            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
        let public_key = secret_key.public_key();

        Ok(KeyPairHex {
            private_key_hex: hex_encode(secret_key.to_bytes().as_slice()),
            public_key_hex: hex_encode(public_key.to_sec1_point(false).as_bytes()),
        })
    }

    /// 私钥签名,返回定长 64 字节 `r‖s` 的十六进制(128 位)。
    ///
    /// 用户标识固定为 [`DEFAULT_DIST_ID`];随机数 `k` 走 RFC 6979 确定性
    /// 派生(同一私钥 + 同一消息必得同一签名,不依赖运行时随机源)。
    ///
    /// 私钥格式非法(十六进制或长度)返回 [`Error::InvalidKey`],
    /// 其余签名失败返回 [`Error::SignFailed`]。
    pub fn sign(message: &[u8], private_key_hex: &str) -> Result<String> {
        let signing_key = SigningKey::new(DEFAULT_DIST_ID, &parse_private_key(private_key_hex)?)
            .map_err(|_| Error::SignFailed { label: LABEL })?;
        let signature: Signature = signing_key
            .try_sign(message)
            .map_err(|_| Error::SignFailed { label: LABEL })?;
        Ok(hex_encode(signature.to_bytes().as_slice()))
    }

    /// 公钥验签。签名不对、消息被改、公钥 / 签名格式错误一律
    /// [`Error::VerificationFailed`](与契约一致,不区分原因)。
    ///
    /// 公钥写法接受与非对称加密相同的三种形式;用户标识固定为
    /// [`DEFAULT_DIST_ID`],签名方 ID 不一致时验签必然失败。
    pub fn verify(message: &[u8], signature_hex: &str, public_key_hex: &str) -> Result<()> {
        let fail = || Error::VerificationFailed { label: LABEL };
        let sec1 = sec1_public_key_bytes(public_key_hex).map_err(|_| fail())?;
        let verifying_key =
            VerifyingKey::from_sec1_bytes(DEFAULT_DIST_ID, &sec1).map_err(|_| fail())?;
        let signature_bytes = hex_decode(signature_hex).map_err(|_| fail())?;
        let signature = Signature::from_slice(&signature_bytes).map_err(|_| fail())?;
        verifying_key
            .verify(message, &signature)
            .map_err(|_| fail())
    }
}

/// SM2 非对称加密器(实现 [`AsymmetricCipher`];行为与 [`Sm2Service`] 一致)。
#[derive(Debug, Default, Clone, Copy)]
pub struct Sm2AsymmetricCipher;

impl Sm2AsymmetricCipher {
    pub const IDENTIFIER: &'static str = "sm2";

    pub fn new() -> Self {
        Self
    }
}

impl Identified for Sm2AsymmetricCipher {
    fn identifier(&self) -> &str {
        Self::IDENTIFIER
    }
}

impl AsymmetricCipher for Sm2AsymmetricCipher {
    fn encrypt(&self, plaintext: &[u8], public_key_hex: &str) -> Result<String> {
        Sm2Service::encrypt(plaintext, public_key_hex)
    }

    fn decrypt(&self, ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
        Sm2Service::decrypt(ciphertext_hex, private_key_hex)
    }
}

/// SM2 签名器(实现 [`Signer`];行为与 [`Sm2Service::sign`] / [`Sm2Service::verify`] 一致)。
#[derive(Debug, Default, Clone, Copy)]
pub struct Sm2Signer;

impl Sm2Signer {
    pub const IDENTIFIER: &'static str = "sm2";

    pub fn new() -> Self {
        Self
    }
}

impl Identified for Sm2Signer {
    fn identifier(&self) -> &str {
        Self::IDENTIFIER
    }
}

impl Signer for Sm2Signer {
    fn sign(&self, message: &[u8], private_key_hex: &str) -> Result<String> {
        Sm2Service::sign(message, private_key_hex)
    }

    fn verify(&self, message: &[u8], signature_hex: &str, public_key_hex: &str) -> Result<()> {
        Sm2Service::verify(message, signature_hex, public_key_hex)
    }
}

/// 接受 130 位(`04‖X‖Y`)/ 128 位(`X‖Y`)/ 66 位(压缩点)三种公钥写法,
/// 归一化成 SEC1 字节(加密与验签共用)。
fn sec1_public_key_bytes(public_key_hex: &str) -> Result<Vec<u8>> {
    let bytes = hex_decode(public_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;

    match bytes.len() {
        65 | 33 => Ok(bytes),
        64 => {
            // 裸 X‖Y:补上非压缩点标记。
            let mut normalized = Vec::with_capacity(65);
            normalized.push(0x04);
            normalized.extend_from_slice(&bytes);
            Ok(normalized)
        }
        _ => Err(Error::InvalidKey { label: LABEL }),
    }
}

fn parse_public_key(public_key_hex: &str) -> Result<EncryptingKey> {
    let sec1 = sec1_public_key_bytes(public_key_hex)?;
    EncryptingKey::from_sec1_bytes(&sec1).map_err(|_| Error::InvalidKey { label: LABEL })
}

/// 私钥:64 位十六进制(32 字节标量),其余长度一律拒绝。
fn parse_private_key(private_key_hex: &str) -> Result<SecretKey> {
    let bytes = hex_decode(private_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
    SecretKey::from_slice(&bytes).map_err(|_| Error::InvalidKey { label: LABEL })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn keypair_roundtrip_and_hex_shapes() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        assert_eq!(pair.private_key_hex.len(), 64);
        assert_eq!(pair.public_key_hex.len(), 130);
        assert!(pair.public_key_hex.starts_with("04"));

        let ciphertext =
            Sm2Service::encrypt("国密数据 🎉".as_bytes(), &pair.public_key_hex).unwrap();
        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
        assert_eq!(plaintext, "国密数据 🎉".as_bytes());
    }

    #[test]
    fn bare_xy_public_key_is_accepted_too() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀

        let ciphertext = Sm2Service::encrypt(b"payload", bare).unwrap();
        let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
        assert_eq!(plaintext, b"payload");
    }

    #[test]
    fn wrong_private_key_or_tampered_ciphertext_fails() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let other = Sm2Service::generate_key_pair_hex().unwrap();
        let ciphertext = Sm2Service::encrypt(b"secret", &pair.public_key_hex).unwrap();

        assert_eq!(
            Sm2Service::decrypt(&ciphertext, &other.private_key_hex).unwrap_err(),
            Error::DecryptionFailed { label: LABEL }
        );

        let mut tampered = ciphertext.clone();
        let last = tampered.len() - 1;
        let flipped = if tampered.as_bytes()[last] == b'0' {
            '1'
        } else {
            '0'
        };
        tampered.replace_range(last.., &flipped.to_string());
        assert_eq!(
            Sm2Service::decrypt(&tampered, &pair.private_key_hex).unwrap_err(),
            Error::DecryptionFailed { label: LABEL }
        );
    }

    #[test]
    fn malformed_keys_are_rejected() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();

        assert_eq!(
            Sm2Service::decrypt("00", "zz").unwrap_err(),
            Error::InvalidKey { label: LABEL }
        );
        assert_eq!(
            Sm2Service::encrypt(b"x", "abcd").unwrap_err(),
            Error::InvalidKey { label: LABEL }
        );
        // 私钥位数不对(多一位十六进制)。
        let mut longer = pair.private_key_hex.clone();
        longer.push('a');
        assert_eq!(
            Sm2Service::decrypt("00", &longer).unwrap_err(),
            Error::InvalidKey { label: LABEL }
        );
    }

    #[test]
    fn cipher_trait_matches_service() {
        let cipher = Sm2AsymmetricCipher::new();
        assert_eq!(cipher.identifier(), "sm2");

        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let ciphertext = cipher.encrypt(b"via trait", &pair.public_key_hex).unwrap();
        assert_eq!(
            cipher.decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
            b"via trait"
        );
    }

    #[test]
    fn sign_verify_roundtrip_and_signature_shape() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let signature = Sm2Service::sign("国密签名 🎉".as_bytes(), &pair.private_key_hex).unwrap();

        assert_eq!(signature.len(), 128); // 64 字节 r‖s
        Sm2Service::verify("国密签名 🎉".as_bytes(), &signature, &pair.public_key_hex).unwrap();
    }

    #[test]
    fn signature_is_deterministic() {
        // RFC 6979 确定性 k:同一私钥 + 同一消息必得同一签名。
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let first = Sm2Service::sign(b"same message", &pair.private_key_hex).unwrap();
        let second = Sm2Service::sign(b"same message", &pair.private_key_hex).unwrap();
        assert_eq!(first, second);
    }

    #[test]
    fn verify_rejects_wrong_input_forms() {
        let failed = Error::VerificationFailed { label: LABEL };
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let other = Sm2Service::generate_key_pair_hex().unwrap();
        let signature = Sm2Service::sign(b"message", &pair.private_key_hex).unwrap();

        // 消息被改。
        assert_eq!(
            Sm2Service::verify(b"messagE", &signature, &pair.public_key_hex).unwrap_err(),
            failed
        );
        // 换了别人的公钥。
        assert_eq!(
            Sm2Service::verify(b"message", &signature, &other.public_key_hex).unwrap_err(),
            failed
        );
        // 签名被篡改(翻转最后一个十六进制字符)。
        let mut tampered = signature.clone();
        let last = tampered.len() - 1;
        let flipped = if tampered.as_bytes()[last] == b'0' {
            '1'
        } else {
            '0'
        };
        tampered.replace_range(last.., &flipped.to_string());
        assert_eq!(
            Sm2Service::verify(b"message", &tampered, &pair.public_key_hex).unwrap_err(),
            failed
        );
        // 格式错误:公钥非法、签名非法、签名长度不对——一律 VerificationFailed。
        assert_eq!(
            Sm2Service::verify(b"message", &signature, "zz").unwrap_err(),
            failed
        );
        assert_eq!(
            Sm2Service::verify(b"message", "not-hex", &pair.public_key_hex).unwrap_err(),
            failed
        );
        assert_eq!(
            Sm2Service::verify(b"message", "00", &pair.public_key_hex).unwrap_err(),
            failed
        );
    }

    #[test]
    fn bare_xy_public_key_verifies_too() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let bare = &pair.public_key_hex[2..]; // 去掉 04 前缀

        let signature = Sm2Service::sign(b"payload", &pair.private_key_hex).unwrap();
        Sm2Service::verify(b"payload", &signature, bare).unwrap();
    }

    /// 压缩公钥(66 位 hex = 02/03‖X)同样可验签——文档承诺的第三种写法。
    #[test]
    fn compressed_public_key_verifies_too() {
        use sm2::elliptic_curve::sec1::ToSec1Point;

        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let secret = sm2::SecretKey::from_slice(
            &crate::internal::hex_decode(&pair.private_key_hex).unwrap(),
        )
        .unwrap();
        let compressed_hex =
            crate::internal::hex_encode(secret.public_key().to_sec1_point(true).as_bytes());
        assert_eq!(compressed_hex.len(), 66);

        let signature = Sm2Service::sign(b"payload", &pair.private_key_hex).unwrap();
        Sm2Service::verify(b"payload", &signature, &compressed_hex).unwrap();
    }

    /// 跨层核验:本库 `Sm2Signer` 的签名必须能被裸 sm2 crate 的 `VerifyingKey`
    /// (同一 DIST_ID 与编码路径)接受——钉住封装层没有引入 ID / 编码偏差。
    #[test]
    fn signature_is_accepted_by_raw_sm2_crate() {
        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let signature_hex = Sm2Service::sign(b"cross-layer", &pair.private_key_hex).unwrap();

        let sec1 = crate::internal::hex_decode(&pair.public_key_hex).unwrap();
        let verifying_key =
            sm2::dsa::VerifyingKey::from_sec1_bytes(DEFAULT_DIST_ID, &sec1).unwrap();
        let sig_bytes = crate::internal::hex_decode(&signature_hex).unwrap();
        let signature = sm2::dsa::Signature::from_slice(&sig_bytes).unwrap();
        verifying_key.verify(b"cross-layer", &signature).unwrap();
    }

    #[test]
    fn sign_rejects_malformed_private_key() {
        assert_eq!(
            Sm2Service::sign(b"x", "zz").unwrap_err(),
            Error::InvalidKey { label: LABEL }
        );
        assert_eq!(
            Sm2Service::sign(b"x", "abcd").unwrap_err(),
            Error::InvalidKey { label: LABEL }
        );
    }

    #[test]
    fn signer_trait_matches_service() {
        let signer = Sm2Signer::new();
        assert_eq!(signer.identifier(), "sm2");

        let pair = Sm2Service::generate_key_pair_hex().unwrap();
        let signature = signer.sign(b"via trait", &pair.private_key_hex).unwrap();
        signer
            .verify(b"via trait", &signature, &pair.public_key_hex)
            .unwrap();
        assert_eq!(
            signer.verify(b"other", &signature, &pair.public_key_hex),
            Err(Error::VerificationFailed { label: LABEL })
        );
    }

    /// 外部互通验签:**GM/T 0003.5-2012 附录 A**(同 GB/T 32918.5-2016)签名示例,
    /// 消息 `"message digest"`、私钥与密文示例相同(`3945208F…C5B8`),
    /// 取自 OpenSSL 官方测试文件
    /// [`test/sm2_internal_test.c`](https://github.com/openssl/openssl/blob/master/test/sm2_internal_test.c)
    /// 的 `sm2_sig_test`,其注释标注 "From Annex A in both GM/T0003.5-2012 and
    /// GB/T 32918.5-2016"。
    ///
    /// 该 `r‖s` 由 OpenSSL 以**默认用户标识**计算(测试中传 `NULL`);OpenSSL 源码
    /// `crypto/sm2/sm2_sign.c` 的 `default_sm2_id` 即 `"1234567812345678"`,
    /// 与本库 [`DEFAULT_DIST_ID`] 逐字节一致,故可直接验签、无需改写标识。
    /// 公钥 `P_A = d_A·G`(`04‖X‖Y`)由私钥经标准 SM2 曲线点乘派生——同一份
    /// 独立 Python 点乘实现已先用附录 C 的 `C1 = k·G` 对上 OpenSSL 向量。
    #[test]
    fn verify_accepts_gmt0003_annex_a_openssl_signature() {
        const PUBLIC_KEY: &str = concat!(
            "04",                                                               // 非压缩点标记
            "09F9DF311E5421A150DD7D161E4BC5C672179FAD1833FC076BB08FF356F35020", // X
            "CCEA490CE26775A52DC6EA718CC1AA600AED05FBF35E084A6632F6072DA9AD13", // Y
        );
        const SIGNATURE: &str = concat!(
            "F5A03B0648D2C4630EEAC513E1BB81A15944DA3827D5B74143AC7EACEEE720B3", // r
            "B1B6AA29DF212FD8763182BC0D421CA1BB9038FD1F7F42D4840B69C485BBC1AA", // s
        );

        Sm2Service::verify(b"message digest", SIGNATURE, PUBLIC_KEY)
            .expect("GM/T 0003.5 附录 A 的 r‖s 应通过验签");
    }
}