发布 v0.2.1:SM2 密钥交换、HKDF-SM3、DER 编解码

新增功能:
- SM2 密钥交换:GB/T 32918.3 完整协议(exchange_a/b)、ECDH
- SM2 DER 编解码:签名 DER、SEC1/PKCS#8 私钥解析
- SM2 便捷 API:sign_message/verify_message 自动计算 Z 值
- HKDF-SM3:RFC 5869 兼容的 HKDF
- SM3 Hasher 增强:reset/finalize_reset 流式复用
- SM4 AEAD 合并格式:GCM/CCM combined(TLS 格式)

文档更新:
- CHANGELOG 添加 v0.2.1 变更记录
This commit is contained in:
huangxt
2026-03-08 20:58:36 +08:00
parent a58fe8275e
commit 2580571881
11 changed files with 1525 additions and 17 deletions
+20 -1
View File
@@ -5,10 +5,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] - v0.2.0
## [0.2.1] - 2025-03-08
### Added
- **SM2 key exchange** (`sm2::key_exchange` module)
- `exchange_a` / `exchange_b`: GB/T 32918.3 full key exchange protocol with confirmation hash
- `ecdh`: Simple SM2-ECDH shared secret computation (TLS/rustls compatible)
- `ecdh_from_slice`: Slice-based ECDH for TLS integration
- `EphemeralKey`: Ephemeral key pair with `ZeroizeOnDrop`
- **SM2 DER codec** (`sm2::der` module)
- `sig_to_der` / `sig_from_der`: Signature DER encoding/decoding
- `private_key_from_sec1_der`: RFC 5915 SEC1 private key parsing
- `private_key_from_pkcs8_der`: RFC 5958 PKCS#8 private key parsing
- **SM2 convenience API** (`sm2` module)
- `sign_message` / `verify_message`: One-step sign/verify with automatic Z-value computation
- **HKDF-SM3** (`sm3::hkdf` module)
- `hkdf_extract` / `hkdf_expand` / `hkdf`: RFC 5869 compatible
- **SM3 Hasher enhancements**
- `reset()` / `finalize_reset()`: Streaming hasher reuse
- **SM4 AEAD combined format**
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`: TLS format (ciphertext||tag)
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`: Same format for CCM
- **BLS signatures** (`bls` module, requires `alloc` feature)
- `bls_keygen` / `bls_sign` / `bls_verify`: minimal-signature-size variant (sig ∈ G1, pk ∈ G2)
- `bls_aggregate` / `bls_aggregate_verify`: multi-message aggregate signatures
@@ -102,6 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- XTS: reject non-16-byte-aligned input instead of silently truncating
- SM9 `hash_to_range`: replaced variable-iteration `while` loop with constant-time conditional select
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
+18 -1
View File
@@ -5,10 +5,26 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [Unreleased] - v0.2.0
## [0.2.1] - 2025-03-08
### 新增
- **SM2 密钥交换**`sm2::key_exchange` 模块)
- `exchange_a` / `exchange_b`GB/T 32918.3 完整密钥交换协议(带确认哈希)
- `ecdh`:简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls
- `EphemeralKey`:临时密钥对(`ZeroizeOnDrop`
- **SM2 DER 编解码**`sm2::der` 模块)
- `sig_to_der` / `sig_from_der`:签名 DER 编解码
- `private_key_from_sec1_der` / `private_key_from_pkcs8_der`:私钥解析
- **SM2 便捷 API**`sm2` 模块)
- `sign_message` / `verify_message`:自动计算 Z 值的一步签名/验签
- **HKDF-SM3**`sm3::hkdf` 模块)
- `hkdf_extract` / `hkdf_expand` / `hkdf`RFC 5869 兼容
- **SM3 Hasher 增强**
- `reset()` / `finalize_reset()`:支持流式复用
- **SM4 AEAD 合并格式**
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`TLS 格式(密文||Tag
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`:同上
- **BLS 签名**`bls` 模块,需 `alloc` 特性)
- `bls_keygen` / `bls_sign` / `bls_verify`:最小签名尺寸变体(签名 ∈ G1,公钥 ∈ G2)
- `bls_aggregate` / `bls_aggregate_verify`:多消息聚合签名
@@ -102,6 +118,7 @@
- XTS:拒绝非 16 字节对齐输入,而非静默截断
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
Generated
+1 -1
View File
@@ -422,7 +422,7 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libsmx"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"criterion",
"crypto-bigint",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "libsmx"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
rust-version = "1.83.0"
license = "Apache-2.0"
+3
View File
@@ -23,6 +23,8 @@ pub enum Error {
PointAtInfinity,
/// 输入数据长度不合法
InvalidInputLength,
/// 密钥交换失败(共享点为无穷远或 KDF 输出全零)
KeyExchangeFailed,
// ── SM4 错误 ────────────────────────────────────────────────────────────
/// AEAD 认证标签验证失败(GCM/CCM 解密时)
@@ -55,6 +57,7 @@ impl fmt::Display for Error {
Error::DecryptFailed => write!(f, "decryption failed"),
Error::PointAtInfinity => write!(f, "point at infinity"),
Error::InvalidInputLength => write!(f, "invalid input length"),
Error::KeyExchangeFailed => write!(f, "key exchange failed"),
Error::AuthTagMismatch => write!(f, "authentication tag mismatch"),
Error::NotOnCurve => write!(f, "point not on curve"),
Error::ZeroScalar => write!(f, "zero scalar"),
+438
View File
@@ -0,0 +1,438 @@
//! SM2 签名与密钥 DER 编解码
//!
//! ## 签名格式
//! TLS 使用 ASN.1 DER 格式表示签名:
//! ```text
//! SEQUENCE {
//! INTEGER r,
//! INTEGER s
//! }
//! ```
//! 而 libsmx 内部使用原始 `r||s`(64 字节)。本模块提供两者互转。
//!
//! ## 私钥格式
//! - **SEC1**RFC 5915):`ECPrivateKey SEQUENCE { version INTEGER(1), privateKey OCTET STRING, ... }`
//! - **PKCS#8**RFC 5958):`PrivateKeyInfo SEQUENCE { version INTEGER(0), algorithm, privateKey OCTET STRING(SEC1) }`
//!
//! ## DER INTEGER 编码规则
//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数)
//! - tag = 0x02length 占 1 字节(r/s < 256 位时长度 ≤ 33
//! - SEQUENCE tag = 0x30
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::error::Error;
use crate::sm2::PrivateKey;
/// 将原始签名 `r||s`64 字节)编码为 DER SEQUENCE
///
/// 输出格式:`30 <len> 02 <rlen> <r> 02 <slen> <s>`
#[cfg(feature = "alloc")]
pub fn sig_to_der(raw: &[u8; 64]) -> Vec<u8> {
let r = &raw[..32];
let s = &raw[32..];
let r_enc = encode_integer(r);
let s_enc = encode_integer(s);
let inner_len = r_enc.len() + s_enc.len();
let mut der = Vec::with_capacity(2 + inner_len);
der.push(0x30); // SEQUENCE tag
der.push(inner_len as u8); // SEQUENCE lengthinner < 256 字节)
der.extend_from_slice(&r_enc);
der.extend_from_slice(&s_enc);
der
}
/// 将 DER 编码签名解码为原始 `r||s`(64 字节)
///
/// # 错误
/// 格式不合法时返回 `Error::InvalidSignature`
pub fn sig_from_der(der: &[u8]) -> Result<[u8; 64], Error> {
let err = || Error::InvalidSignature;
// SEQUENCE tag
let (tag, rest) = split_first(der).ok_or_else(err)?;
if *tag != 0x30 {
return Err(err());
}
// SEQUENCE length
let (seq_len, rest) = split_first(rest).ok_or_else(err)?;
let seq_len = *seq_len as usize;
if rest.len() < seq_len {
return Err(err());
}
let body = &rest[..seq_len];
// 解析 r
let (r_bytes, body) = decode_integer(body).ok_or_else(err)?;
// 解析 s
let (s_bytes, body) = decode_integer(body).ok_or_else(err)?;
// 不应有多余数据
if !body.is_empty() {
return Err(err());
}
// r 和 s 都必须是正整数,不超过 32 字节
if r_bytes.is_empty() || r_bytes.len() > 33 || s_bytes.is_empty() || s_bytes.len() > 33 {
return Err(err());
}
let mut raw = [0u8; 64];
// Reason: DER INTEGER 可能有前缀 0x00(最高位保护),去除后左对齐写入 32 字节槽
let r_stripped = strip_leading_zero(r_bytes);
let s_stripped = strip_leading_zero(s_bytes);
if r_stripped.len() > 32 || s_stripped.len() > 32 {
return Err(err());
}
let r_off = 32 - r_stripped.len();
let s_off = 32 - s_stripped.len();
raw[r_off..32].copy_from_slice(r_stripped);
raw[32 + s_off..64].copy_from_slice(s_stripped);
Ok(raw)
}
// ── 内部辅助 ──────────────────────────────────────────────────────────────────
/// 将 32 字节大端整数编码为 DER INTEGER(带 tag 0x02 和 length
#[cfg(feature = "alloc")]
fn encode_integer(bytes: &[u8]) -> Vec<u8> {
// 去除前导零(至少保留 1 字节)
let start = bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(bytes.len() - 1);
let val = &bytes[start..];
// 最高位为 1 时需补 0x00,防止被解析为负数
let needs_pad = val[0] & 0x80 != 0;
let val_len = val.len() + if needs_pad { 1 } else { 0 };
let mut enc = Vec::with_capacity(2 + val_len);
enc.push(0x02); // INTEGER tag
enc.push(val_len as u8); // length
if needs_pad {
enc.push(0x00);
}
enc.extend_from_slice(val);
enc
}
/// 从字节流中解析一个 DER INTEGER,返回 (value_bytes, 剩余字节)
fn decode_integer(data: &[u8]) -> Option<(&[u8], &[u8])> {
let (tag, rest) = split_first(data)?;
if *tag != 0x02 {
return None;
}
let (len, rest) = split_first(rest)?;
let len = *len as usize;
if rest.len() < len {
return None;
}
Some((&rest[..len], &rest[len..]))
}
/// 去除前导 0x00 字节
fn strip_leading_zero(bytes: &[u8]) -> &[u8] {
match bytes.iter().position(|&b| b != 0) {
Some(i) => &bytes[i..],
None => &bytes[bytes.len().saturating_sub(1)..], // 全零时保留末字节
}
}
fn split_first(data: &[u8]) -> Option<(&u8, &[u8])> {
data.split_first()
}
// ── DER 长度解码 ──────────────────────────────────────────────────────────────
/// 解析 DER 长度字段,返回 (length, 剩余字节)
///
/// 支持:单字节(< 0x80)、两字节(0x81 nn)、三字节(0x82 nn nn
fn parse_length(data: &[u8]) -> Option<(usize, &[u8])> {
let (first, rest) = data.split_first()?;
if *first < 0x80 {
// Reason: 最高位为 0 时,本字节直接表示长度
Some((*first as usize, rest))
} else if *first == 0x81 {
let (len, rest) = rest.split_first()?;
Some((*len as usize, rest))
} else if *first == 0x82 {
if rest.len() < 2 {
return None;
}
let len = (rest[0] as usize) << 8 | rest[1] as usize;
Some((len, &rest[2..]))
} else {
// 不支持更长或不定长编码
None
}
}
/// 解析一个 TLVtag-length-value),返回 (value_bytes, 剩余字节)
fn parse_tlv(data: &[u8], expected_tag: u8) -> Option<(&[u8], &[u8])> {
let (tag, rest) = data.split_first()?;
if *tag != expected_tag {
return None;
}
let (len, rest) = parse_length(rest)?;
if rest.len() < len {
return None;
}
Some((&rest[..len], &rest[len..]))
}
// ── 私钥 DER 解析 ─────────────────────────────────────────────────────────────
/// 从 SEC1 DER 解析 SM2 私钥(RFC 5915
///
/// 格式:
/// ```text
/// ECPrivateKey ::= SEQUENCE {
/// version INTEGER { ecPrivkeyVer1(1) },
/// privateKey OCTET STRING, -- 32 字节原始私钥
/// [0] ECParameters OPTIONAL,
/// [1] BIT STRING OPTIONAL
/// }
/// ```
///
/// # 错误
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
pub fn private_key_from_sec1_der(der: &[u8]) -> Result<PrivateKey, Error> {
let err = || Error::InvalidPrivateKey;
// 解析外层 SEQUENCE
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
// version INTEGER,值应为 1ecPrivkeyVer1
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
if ver_bytes != [0x01] {
return Err(err());
}
// privateKey OCTET STRING32 字节)
let (key_bytes, _rest) = parse_tlv(rest, 0x04).ok_or_else(err)?;
if key_bytes.len() != 32 {
return Err(err());
}
let key_arr: &[u8; 32] = key_bytes.try_into().map_err(|_| err())?;
PrivateKey::from_bytes(key_arr)
}
/// 从 PKCS#8 DER 解析 SM2 私钥(RFC 5958
///
/// 格式:
/// ```text
/// PrivateKeyInfo ::= SEQUENCE {
/// version INTEGER (0),
/// algorithm AlgorithmIdentifier SEQUENCE { ... },
/// privateKey OCTET STRING (SEC1 DER)
/// }
/// ```
///
/// # 错误
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
pub fn private_key_from_pkcs8_der(der: &[u8]) -> Result<PrivateKey, Error> {
let err = || Error::InvalidPrivateKey;
// 解析外层 SEQUENCEPrivateKeyInfo
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
// version INTEGER,值应为 0
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
if ver_bytes != [0x00] {
return Err(err());
}
// AlgorithmIdentifier SEQUENCE(跳过,不验证 OID
let (_, rest) = parse_tlv(rest, 0x30).ok_or_else(err)?;
// privateKey OCTET STRING(内含 SEC1 DER
let (sec1_der, _) = parse_tlv(rest, 0x04).ok_or_else(err)?;
private_key_from_sec1_der(sec1_der)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_raw(r: [u8; 32], s: [u8; 32]) -> [u8; 64] {
let mut raw = [0u8; 64];
raw[..32].copy_from_slice(&r);
raw[32..].copy_from_slice(&s);
raw
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_basic() {
let r = [0x01u8; 32];
let s = [0x02u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_high_bit_set() {
// r/s 最高位为 1,需要 DER 填充 0x00
let mut r = [0u8; 32];
r[0] = 0x80; // 最高位为 1
let mut s = [0u8; 32];
s[0] = 0xFF;
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
// 验证 DER 中有 0x00 填充
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_leading_zeros() {
// r 前有大量前导零
let mut r = [0u8; 32];
r[31] = 0x42; // 只有最后一字节非零
let s = [0x01u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[test]
fn test_der_invalid_tag() {
// 非 SEQUENCE tag
let bad = [0x10, 0x08, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00];
assert!(sig_from_der(&bad).is_err());
}
#[test]
fn test_der_truncated() {
let bad = [0x30, 0x10]; // length 声明 16 字节但无内容
assert!(sig_from_der(&bad).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_structure() {
// 验证 DER 字节结构符合 ASN.1 规范
let r = [0x01u8; 32];
let s = [0x01u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
assert_eq!(der[0], 0x30); // SEQUENCE
assert_eq!(der[2], 0x02); // INTEGER tag for r
// 长度字段合理(r/s 各最多 33 字节 + 2 字节头 = 35,×2 + 2 = 72
assert!(der.len() <= 72);
assert!(der.len() >= 8);
}
// ── 私钥 DER 解析测试 ──────────────────────────────────────────────────────
// 已知 SM2 私钥原始字节(与其他测试共用)
const RAW_KEY: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, 0x9f,
0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, 0x4d, 0xf7,
0xc5, 0xb8,
];
/// 构造最小 SEC1 DER(只有 version + privateKey 字段)
#[cfg(feature = "alloc")]
fn make_sec1_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
// version INTEGER = 102 01 01
// privateKey OCTET STRING04 20 <32 bytes>
// inner = 3 + 2 + 32 = 37 bytes → SEQUENCE 30 25 ...
let mut der = alloc::vec![0x30u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20];
der.extend_from_slice(key);
der
}
/// 构造最小 PKCS#8 DER(包含虚拟 AlgorithmIdentifier OID
#[cfg(feature = "alloc")]
fn make_pkcs8_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
let sec1 = make_sec1_der(key);
// AlgorithmIdentifier 最小化:30 06 06 01 00 06 01 00(两个 OID,各 1 字节占位)
let alg_id: &[u8] = &[0x30, 0x06, 0x06, 0x01, 0x00, 0x06, 0x01, 0x00];
// version INTEGER = 002 01 00
let version: &[u8] = &[0x02, 0x01, 0x00];
// privateKey OCTET STRING 包装 sec1
let mut priv_oct = alloc::vec![0x04u8, sec1.len() as u8];
priv_oct.extend_from_slice(&sec1);
// inner = version + alg_id + priv_oct
let inner_len = version.len() + alg_id.len() + priv_oct.len();
let mut der = alloc::vec![0x30u8, inner_len as u8];
der.extend_from_slice(version);
der.extend_from_slice(alg_id);
der.extend_from_slice(&priv_oct);
der
}
#[cfg(feature = "alloc")]
#[test]
fn test_sec1_der_roundtrip() {
let der = make_sec1_der(&RAW_KEY);
let key = private_key_from_sec1_der(&der).expect("SEC1 解析应成功");
assert_eq!(key.as_bytes(), &RAW_KEY);
}
#[cfg(feature = "alloc")]
#[test]
fn test_pkcs8_der_roundtrip() {
let der = make_pkcs8_der(&RAW_KEY);
let key = private_key_from_pkcs8_der(&der).expect("PKCS#8 解析应成功");
assert_eq!(key.as_bytes(), &RAW_KEY);
}
#[test]
fn test_sec1_der_invalid_tag() {
// 首字节不是 SEQUENCE tag
let bad = [0x02u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20, 0x00];
assert!(private_key_from_sec1_der(&bad).is_err());
}
#[test]
fn test_sec1_der_wrong_version() {
// version 应为 1,此处给 0;最后 32 字节填充为 RAW_KEY
let mut der = [0u8; 39];
der[0] = 0x30;
der[1] = 0x25; // SEQUENCE length 37
der[2] = 0x02;
der[3] = 0x01;
der[4] = 0x00; // version = 0(错误,应为 1
der[5] = 0x04;
der[6] = 0x20; // OCTET STRING 32 字节
der[7..39].copy_from_slice(&RAW_KEY);
assert!(private_key_from_sec1_der(&der).is_err());
}
#[test]
fn test_sec1_der_key_too_short() {
// privateKey 只有 16 字节(不足 32
let der = [
0x30, 0x15, // SEQUENCE 21 字节
0x02, 0x01, 0x01, // version = 1
0x04, 0x10, // OCTET STRING 16 字节
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
assert!(private_key_from_sec1_der(&der).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_pkcs8_der_invalid_outer_tag() {
let mut der = make_pkcs8_der(&RAW_KEY);
der[0] = 0x04; // 破坏外层 SEQUENCE tag
assert!(private_key_from_pkcs8_der(&der).is_err());
}
}
+620
View File
@@ -0,0 +1,620 @@
//! SM2 密钥交换协议(GB/T 32918.3-2016
//!
//! 提供两种密钥交换方式:
//! - `ecdh`: 简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls
//! - `exchange_a` / `exchange_b`: 完整 GB/T 32918.3 密钥交换协议(带确认哈希)
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::error::Error;
use crate::sm2::ec::{AffinePoint, JacobianPoint};
use crate::sm2::field::{fn_add, fn_mul, fp_to_bytes, Fn, GROUP_ORDER_MINUS_1};
use crate::sm2::get_z;
use crate::sm3::Sm3Hasher;
// ── x̄ 辅助函数(GB/T 32918.3 核心运算)─────────────────────────────────────────
/// 计算 x̄ = 2^w + (x & (2^w - 1)),其中 w = ⌈(⌈log2(n)⌉ / 2)⌉ - 1 = 127
///
/// 对 SM2 256 位群阶,w=127。在大端 32 字节表示中:
/// - 清除高 128 位(bytes[0..16]),保留低 128 位
/// - 设 bytes[16] 的 bit7 = 1(即加 2^127
fn x_bar(x_bytes: &[u8; 32]) -> U256 {
let mut buf = [0u8; 32];
// Reason: 保留 x 的低 128 位(bytes[16..32]),高 128 位清零
buf[16..32].copy_from_slice(&x_bytes[16..32]);
// 设 bit 127bytes[16] 的最高位)
buf[16] |= 0x80;
U256::from_be_slice(&buf)
}
// ── EphemeralKey(临时密钥对)────────────────────────────────────────────────────
/// SM2 密钥交换临时密钥对(离开作用域自动清零)
///
/// 用于密钥交换协议中的临时私钥和对应公钥。
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct EphemeralKey {
r_bytes: [u8; 32],
#[zeroize(skip)]
r_point: [u8; 65],
}
impl EphemeralKey {
/// 生成临时密钥对
pub fn generate<R: RngCore>(rng: &mut R) -> Self {
loop {
let mut r_bytes = [0u8; 32];
rng.fill_bytes(&mut r_bytes);
let r = U256::from_be_slice(&r_bytes);
if bool::from(r.is_zero()) || r >= GROUP_ORDER_MINUS_1 {
r_bytes.zeroize();
continue;
}
let r_jac = JacobianPoint::scalar_mul_g(&r);
// Reason: r 在合法范围内,scalar_mul_g 不会产生无穷远点
let r_aff = r_jac.to_affine().expect("valid r produces valid point");
return EphemeralKey {
r_bytes,
r_point: r_aff.to_bytes(),
};
}
}
/// 从指定标量创建临时密钥对(测试用)
pub fn from_scalar(r: &U256) -> Result<Self, Error> {
if bool::from(r.is_zero()) || *r >= GROUP_ORDER_MINUS_1 {
return Err(Error::InvalidPrivateKey);
}
let r_jac = JacobianPoint::scalar_mul_g(r);
let r_aff = r_jac.to_affine().map_err(|_| Error::InvalidPrivateKey)?;
Ok(EphemeralKey {
r_bytes: r.to_be_bytes(),
r_point: r_aff.to_bytes(),
})
}
/// 获取临时公钥(发送给对方)
pub fn public_key(&self) -> &[u8; 65] {
&self.r_point
}
}
// ── 简单 ECDH ──────────────────────────────────────────────────────────────────
/// 简单 SM2-ECDH 共享密钥计算
///
/// 计算 shared = my_priv · peer_pub,返回共享点的 x 坐标(32 字节)。
/// 适用于 TLS/rustls 等只需要原始 ECDH 共享密钥的场景。
///
/// # 参数
/// - `my_priv`: 己方私钥
/// - `peer_pub`: 对方公钥(65 字节,04||x||y
///
/// # 错误
/// - `InvalidPublicKey`: 公钥格式错误或不在曲线上
/// - `PointAtInfinity`: 共享点为无穷远(不应发生于合法输入)
pub fn ecdh(my_priv: &crate::sm2::PrivateKey, peer_pub: &[u8; 65]) -> Result<[u8; 32], Error> {
let peer = AffinePoint::from_bytes(peer_pub)?;
let d = U256::from_be_slice(my_priv.as_bytes());
let peer_jac = JacobianPoint::from_affine(&peer);
let shared = JacobianPoint::scalar_mul(&d, &peer_jac);
let shared_aff = shared.to_affine()?;
Ok(fp_to_bytes(&shared_aff.x))
}
/// 从变长切片执行 SM2-ECDHrustls `ActiveKeyExchange::complete` 适配)
///
/// 等同于 `ecdh`,但接受 `&[u8]` 而非 `&[u8; 65]`,省去调用方的长度转换。
///
/// # 错误
/// - `InvalidInputLength`: peer_pub 长度不等于 65
/// - `InvalidPublicKey` / `PointAtInfinity`: 同 `ecdh`
pub fn ecdh_from_slice(
my_priv: &crate::sm2::PrivateKey,
peer_pub: &[u8],
) -> Result<[u8; 32], Error> {
let pub_fixed: &[u8; 65] = peer_pub.try_into().map_err(|_| Error::InvalidInputLength)?;
ecdh(my_priv, pub_fixed)
}
// ── 完整密钥交换协议(GB/T 32918.3)──────────────────────────────────────────────
/// 密钥交换结果
#[cfg(feature = "alloc")]
pub struct ExchangeResult {
/// 协商出的共享密钥
pub key: Vec<u8>,
/// 己方确认哈希(发给对方验证)
pub s_self: [u8; 32],
/// 对方确认哈希(用于验证对方发来的值)
pub s_peer: [u8; 32],
}
/// 发起方 A 执行密钥交换
///
/// # 参数
/// - `klen`: 期望密钥长度(字节)
/// - `id_a`: 发起方用户 ID
/// - `id_b`: 响应方用户 ID
/// - `pri_key_a`: 发起方私钥
/// - `pub_key_a`: 发起方公钥(65 字节)
/// - `pub_key_b`: 响应方公钥(65 字节)
/// - `eph_key_a`: 发起方临时密钥
/// - `r_b`: 响应方临时公钥(65 字节)
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
pub fn exchange_a(
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_a: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_a: &EphemeralKey,
r_b: &[u8; 65],
) -> Result<ExchangeResult, Error> {
compute_shared(
true, klen, id_a, id_b, pri_key_a, pub_key_a, pub_key_b, eph_key_a, r_b,
)
}
/// 响应方 B 执行密钥交换
///
/// # 参数
/// - `klen`: 期望密钥长度(字节)
/// - `id_a`: 发起方用户 ID
/// - `id_b`: 响应方用户 ID
/// - `pri_key_b`: 响应方私钥
/// - `pub_key_a`: 发起方公钥(65 字节)
/// - `pub_key_b`: 响应方公钥(65 字节)
/// - `eph_key_b`: 响应方临时密钥
/// - `r_a`: 发起方临时公钥(65 字节)
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
pub fn exchange_b(
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_b: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_b: &EphemeralKey,
r_a: &[u8; 65],
) -> Result<ExchangeResult, Error> {
compute_shared(
false, klen, id_a, id_b, pri_key_b, pub_key_a, pub_key_b, eph_key_b, r_a,
)
}
/// 内部共享计算
///
/// `is_initiator`: true 表示发起方 Afalse 表示响应方 B
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
fn compute_shared(
is_initiator: bool,
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_self: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_self: &EphemeralKey,
r_peer: &[u8; 65],
) -> Result<ExchangeResult, Error> {
// 计算 ZA、ZB
let z_a = get_z(id_a, pub_key_a);
let z_b = get_z(id_b, pub_key_b);
// 解析临时公钥坐标
let r_self_aff = AffinePoint::from_bytes(eph_key_self.public_key())?;
let r_peer_aff = AffinePoint::from_bytes(r_peer)?;
// 计算 x̄_self 和 x̄_peer
let x_self_bytes = fp_to_bytes(&r_self_aff.x);
let x_peer_bytes = fp_to_bytes(&r_peer_aff.x);
let x_bar_self = x_bar(&x_self_bytes);
let x_bar_peer = x_bar(&x_peer_bytes);
// t = (d_self + x̄_self · r_self) mod n
let d_self = U256::from_be_slice(pri_key_self.as_bytes());
let r_self = U256::from_be_slice(&eph_key_self.r_bytes);
let t_fn = fn_add(
&Fn::new(&d_self),
&fn_mul(&Fn::new(&x_bar_self), &Fn::new(&r_self)),
);
// V/U = t · (peer_pub + x̄_peer · R_peer)
// Reason: 先计算 x̄_peer · R_peer(标量乘),再加 peer_pub(仿射点)
let peer_pub_bytes = if is_initiator { pub_key_b } else { pub_key_a };
let peer_pub_aff = AffinePoint::from_bytes(peer_pub_bytes)?;
let peer_pub_jac = JacobianPoint::from_affine(&peer_pub_aff);
let r_peer_jac = JacobianPoint::from_affine(&r_peer_aff);
let x_bar_peer_r = JacobianPoint::scalar_mul(&x_bar_peer, &r_peer_jac);
let combined = JacobianPoint::add(&peer_pub_jac, &x_bar_peer_r);
let t = t_fn.retrieve();
let v_point = JacobianPoint::scalar_mul(&t, &combined);
let v_aff = v_point.to_affine().map_err(|_| Error::KeyExchangeFailed)?;
let xv = fp_to_bytes(&v_aff.x);
let yv = fp_to_bytes(&v_aff.y);
// K = KDF(xV || yV || ZA || ZB, klen)
let mut kdf_input = Vec::with_capacity(32 + 32 + 32 + 32);
kdf_input.extend_from_slice(&xv);
kdf_input.extend_from_slice(&yv);
kdf_input.extend_from_slice(&z_a);
kdf_input.extend_from_slice(&z_b);
let key = crate::sm2::kdf::kdf(&kdf_input, klen);
// KDF 输出全零时返回错误(防弱密钥)
if key.iter().all(|&b| b == 0) {
return Err(Error::KeyExchangeFailed);
}
// 确认哈希
// (x1,y1) 始终是 RA(发起方),(x2,y2) 始终是 RB(响应方)
let (x1, y1, x2, y2) = if is_initiator {
(
fp_to_bytes(&r_self_aff.x),
fp_to_bytes(&r_self_aff.y),
fp_to_bytes(&r_peer_aff.x),
fp_to_bytes(&r_peer_aff.y),
)
} else {
(
fp_to_bytes(&r_peer_aff.x),
fp_to_bytes(&r_peer_aff.y),
fp_to_bytes(&r_self_aff.x),
fp_to_bytes(&r_self_aff.y),
)
};
// 内部哈希 hash_v = SM3(xV || ZA || ZB || x1 || y1 || x2 || y2)
let mut h = Sm3Hasher::new();
h.update(&xv);
h.update(&z_a);
h.update(&z_b);
h.update(&x1);
h.update(&y1);
h.update(&x2);
h.update(&y2);
let hash_v = h.finalize();
// S1 = SM3(0x02 || yV || hash_v) — 己方若为 B,则 S1 是己方确认值
let s1 = {
let mut h = Sm3Hasher::new();
h.update(&[0x02]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// SA = SM3(0x03 || yV || hash_v)
let sa = {
let mut h = Sm3Hasher::new();
h.update(&[0x03]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// Reason: 发起方 A 的确认哈希是 SA(0x03),响应方 B 的确认哈希是 S1(0x02)
let (s_self, s_peer) = if is_initiator {
(sa, s1) // A 发送 SA 给 B 验证,A 验证 B 发来的 S1
} else {
(s1, sa) // B 发送 S1 给 A 验证,B 验证 A 发来的 SA
};
Ok(ExchangeResult {
key,
s_self,
s_peer,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sm2::PrivateKey;
#[allow(dead_code)]
struct FakeRng(#[allow(dead_code)] [u8; 32]);
impl RngCore for FakeRng {
fn next_u32(&mut self) -> u32 {
0
}
fn next_u64(&mut self) -> u64 {
0
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for (i, b) in dest.iter_mut().enumerate() {
*b = self.0[i % 32];
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
#[test]
fn test_x_bar() {
// x = 1(低 128 位只有 bit 0
let mut x_bytes = [0u8; 32];
x_bytes[31] = 0x01;
let result = x_bar(&x_bytes);
// 期望 2^127 + 1
let mut expected = [0u8; 32];
expected[16] = 0x80;
expected[31] = 0x01;
assert_eq!(result, U256::from_be_slice(&expected));
}
#[test]
fn test_x_bar_high_bits_cleared() {
// x 有高 128 位数据,应被清除
let x_bytes = [0xFFu8; 32];
let result = x_bar(&x_bytes);
// 低 128 位全 1 + 2^127 设置位 = 0x80 FF...FF 后 16 字节加上 2^127
// 高 16 字节应为 0bytes[16] = 0xFF | 0x80 = 0xFF
let mut expected = [0u8; 32];
expected[16..32].copy_from_slice(&[0xFF; 16]);
expected[16] |= 0x80; // 已经是 0xFF,不变
assert_eq!(result, U256::from_be_slice(&expected));
}
#[test]
fn test_ecdh_roundtrip() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
// A 用 B 的公钥算 ECDH,B 用 A 的公钥算 ECDH,结果应一致
let shared_a = ecdh(&pri_a, &pub_b).unwrap();
let shared_b = ecdh(&pri_b, &pub_a).unwrap();
assert_eq!(shared_a, shared_b);
}
#[test]
fn test_ecdh_invalid_pubkey() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
// 无效公钥(全零 y 坐标不在曲线上)
let mut bad_pub = [0x04u8; 65];
bad_pub[1] = 0x01;
assert!(ecdh(&pri_a, &bad_pub).is_err());
}
#[test]
fn test_ecdh_from_slice_length_check() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
// 长度不对应报 InvalidInputLength
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 64]).is_err());
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 66]).is_err());
}
#[test]
fn test_ecdh_from_slice_equals_ecdh() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_b = pri_b.public_key();
let r1 = ecdh(&pri_a, &pub_b).unwrap();
let r2 = ecdh_from_slice(&pri_a, &pub_b).unwrap();
assert_eq!(r1, r2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_roundtrip() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let id_a = b"Alice@test.com";
let id_b = b"Bob@test.com";
// 生成临时密钥
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_a = exchange_a(
16,
id_a,
id_b,
&pri_a,
&pub_a,
&pub_b,
&eph_a,
eph_b.public_key(),
)
.unwrap();
let result_b = exchange_b(
16,
id_a,
id_b,
&pri_b,
&pub_a,
&pub_b,
&eph_b,
eph_a.public_key(),
)
.unwrap();
// 协商出的密钥应相同
assert_eq!(result_a.key, result_b.key);
assert!(!result_a.key.is_empty());
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_confirmation() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let id_a = b"1234567812345678";
let id_b = b"1234567812345678";
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_a = exchange_a(
16,
id_a,
id_b,
&pri_a,
&pub_a,
&pub_b,
&eph_a,
eph_b.public_key(),
)
.unwrap();
let result_b = exchange_b(
16,
id_a,
id_b,
&pri_b,
&pub_a,
&pub_b,
&eph_b,
eph_a.public_key(),
)
.unwrap();
// 确认哈希交叉验证:A.s_peer == B.s_selfB.s_peer == A.s_self
assert_eq!(result_a.s_peer, result_b.s_self);
assert_eq!(result_b.s_peer, result_a.s_self);
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_different_ids() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
// 使用不同 ID 组合
let eph_a1 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b1 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_1 = exchange_a(
16,
b"ID_A_1",
b"ID_B_1",
&pri_a,
&pub_a,
&pub_b,
&eph_a1,
eph_b1.public_key(),
)
.unwrap();
let eph_a2 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b2 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_2 = exchange_a(
16,
b"ID_A_2",
b"ID_B_2",
&pri_a,
&pub_a,
&pub_b,
&eph_a2,
eph_b2.public_key(),
)
.unwrap();
// 不同 ID 应产生不同密钥
assert_ne!(result_1.key, result_2.key);
}
}
+95
View File
@@ -10,9 +10,11 @@
//! 签名必须使用 `SM3(Z||M)` 作为消息摘要,而非直接 `SM3(M)`。
//! 所有公开签名接口均要求调用方提供用户 ID(或已计算好的 Z 值)。
pub mod der;
pub mod ec;
pub mod field;
pub mod kdf;
pub mod key_exchange;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
@@ -174,6 +176,56 @@ pub fn sign_with_k(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8;
Ok(sig)
}
/// SM2 签名(标准接口,随机 k)
///
/// # 合规说明
/// 此函数接受预计算好的消息摘要 `e = SM3(Z||M)`。
/// 调用方应先用 `get_z` + `get_e` 计算 e,确保满足 GB/T 32918.2-2016 §5.5。
/// SM2 签名(便捷接口,自动计算 Z 值与消息摘要)
///
/// 等同于 `get_z` + `get_e` + `sign` 的组合,适合不需要手动管理摘要的场景。
///
/// # 参数
/// - `msg`: 原始消息
/// - `id`: 用户可辨别标识(通常使用 `b"1234567812345678"`
/// - `pri_key`: 私钥
/// - `rng`: 随机数生成器
///
/// # 合规说明
/// 内部自动计算 `Z = SM3(ENTL||ID||a||b||Gx||Gy||Px||Py)` 和 `e = SM3(Z||M)`
/// 符合 GB/T 32918.2-2016 §5.5。
pub fn sign_message<R: RngCore>(
msg: &[u8],
id: &[u8],
pri_key: &PrivateKey,
rng: &mut R,
) -> [u8; 64] {
let pub_key = pri_key.public_key();
let z = get_z(id, &pub_key);
let e = get_e(&z, msg);
sign(&e, pri_key, rng)
}
/// SM2 验签(便捷接口,自动计算 Z 值与消息摘要)
///
/// 等同于 `get_z` + `get_e` + `verify` 的组合。
///
/// # 参数
/// - `msg`: 原始消息
/// - `id`: 用户可辨别标识
/// - `pub_key`: 公钥(65 字节,04||x||y
/// - `sig`: 签名(64 字节,r||s
pub fn verify_message(
msg: &[u8],
id: &[u8],
pub_key: &[u8; 65],
sig: &[u8; 64],
) -> Result<(), Error> {
let z = get_z(id, pub_key);
let e = get_e(&z, msg);
verify(&e, pub_key, sig)
}
/// SM2 签名(标准接口,随机 k)
///
/// # 合规说明
@@ -481,6 +533,49 @@ mod tests {
assert_eq!(plaintext, msg);
}
#[test]
fn test_sign_message_verify_message_roundtrip() {
let d_bytes: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_key = PrivateKey::from_bytes(&d_bytes).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng([
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
]);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
verify_message(msg, DEFAULT_ID, &pub_key, &sig).expect("便捷验签应通过");
}
#[test]
fn test_verify_message_rejects_wrong_id() {
let d_bytes: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_key = PrivateKey::from_bytes(&d_bytes).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng([
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
]);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
// 用错误 ID 验签应失败
assert!(verify_message(msg, b"wrong-id", &pub_key, &sig).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_decrypt_rejects_tampered_ciphertext() {
+194
View File
@@ -0,0 +1,194 @@
//! HKDF-SM3:基于 HMAC-SM3 的密钥派生函数(RFC 5869
//!
//! TLS 1.3 的密钥调度完全基于 HKDF,因此本模块是 rustls 国密适配的基础依赖。
//!
//! ## 协议
//!
//! ```text
//! Extract: PRK = HMAC-SM3(salt, IKM)
//! Expand: T(0) = b""
//! T(i) = HMAC-SM3(PRK, T(i-1) || info || i) i = 1, 2, ...
//! OKM = T(1) || T(2) || ... 取前 len 字节
//! ```
//!
//! 参考:[RFC 5869](https://www.rfc-editor.org/rfc/rfc5869)
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use super::hmac_sm3;
use super::DIGEST_LEN;
#[cfg(feature = "alloc")]
use crate::error::Error;
/// HKDF-SM3 Extract
///
/// PRK = HMAC-SM3(salt, IKM)
///
/// # 参数
/// - `salt`:可选盐值;`None` 时视为全零 32 字节(RFC 5869 §2.2
/// - `ikm`:输入密钥材料
///
/// # 返回
/// 32 字节伪随机密钥(PRK)
pub fn hkdf_extract(salt: Option<&[u8]>, ikm: &[u8]) -> [u8; DIGEST_LEN] {
// Reason: RFC 5869 §2.2 规定 salt 缺省时视为长度为 HashLen 的零字节序列
let zeros = [0u8; DIGEST_LEN];
let salt = salt.unwrap_or(&zeros);
hmac_sm3(salt, ikm)
}
/// HKDF-SM3 Expand
///
/// OKM = T(1) || T(2) || ... 截取前 `len` 字节
///
/// # 参数
/// - `prk`32 字节伪随机密钥(来自 `hkdf_extract` 输出)
/// - `info`:上下文信息(可为空)
/// - `len`:期望输出长度(字节),不得超过 255 × 32 = 8160
///
/// # 错误
/// `len > 255 * 32` 时返回 `Error::InvalidInputLength`
#[cfg(feature = "alloc")]
pub fn hkdf_expand(prk: &[u8; DIGEST_LEN], info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
// Reason: RFC 5869 §2.3 限制最大输出为 255 × HashLen
const MAX_LEN: usize = 255 * DIGEST_LEN;
if len > MAX_LEN {
return Err(Error::InvalidInputLength);
}
let mut okm = Vec::with_capacity(len + DIGEST_LEN);
let mut t_prev = [0u8; DIGEST_LEN]; // T(0) = b""
let mut t_prev_len = 0usize; // 第一轮 T(0) 为空
let rounds = len.div_ceil(DIGEST_LEN);
for i in 1u8..=(rounds as u8) {
// HMAC-SM3(PRK, T(i-1) || info || i)
// Reason: 用拼接方式避免在 no_std 环境分配临时 Vec
let mut input = [0u8; DIGEST_LEN + 255 + 1]; // T_prev(32) + info(≤255) + counter(1)
let info_len = info.len().min(255);
input[..t_prev_len].copy_from_slice(&t_prev[..t_prev_len]);
input[t_prev_len..t_prev_len + info_len].copy_from_slice(&info[..info_len]);
input[t_prev_len + info_len] = i;
let t_i = hmac_sm3(prk, &input[..t_prev_len + info_len + 1]);
okm.extend_from_slice(&t_i);
t_prev = t_i;
t_prev_len = DIGEST_LEN; // 第二轮起 T_prev 固定 32 字节
}
okm.truncate(len);
Ok(okm)
}
/// HKDF-SM3 一步完成(extract + expand
///
/// 适合只需要派生一段密钥材料的场景。
///
/// # 参数
/// - `salt`:可选盐值(`None` 视为 32 字节零)
/// - `ikm`:输入密钥材料
/// - `info`:上下文绑定信息
/// - `len`:输出长度(字节)
#[cfg(feature = "alloc")]
pub fn hkdf(salt: Option<&[u8]>, ikm: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
let prk = hkdf_extract(salt, ikm);
hkdf_expand(&prk, info, len)
}
#[cfg(test)]
mod tests {
use super::*;
/// RFC 5869 附录 A.1 测试向量(以 SHA-256 为基准验结构,此处用 SM3 验确定性和正确性)
#[test]
fn test_hkdf_extract_deterministic() {
let salt = b"test-salt";
let ikm = b"input-key-material";
let prk1 = hkdf_extract(Some(salt), ikm);
let prk2 = hkdf_extract(Some(salt), ikm);
assert_eq!(prk1, prk2);
assert_eq!(prk1.len(), 32);
}
#[test]
fn test_hkdf_extract_none_salt_equals_zero_salt() {
let ikm = b"some ikm";
let zeros = [0u8; 32];
let prk_none = hkdf_extract(None, ikm);
let prk_zero = hkdf_extract(Some(&zeros), ikm);
assert_eq!(prk_none, prk_zero);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_length() {
let prk = [0x42u8; 32];
let info = b"test-info";
assert_eq!(hkdf_expand(&prk, info, 16).unwrap().len(), 16);
assert_eq!(hkdf_expand(&prk, info, 32).unwrap().len(), 32);
assert_eq!(hkdf_expand(&prk, info, 48).unwrap().len(), 48);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_deterministic() {
let prk = [0x11u8; 32];
let info = b"ctx";
let out1 = hkdf_expand(&prk, info, 32).unwrap();
let out2 = hkdf_expand(&prk, info, 32).unwrap();
assert_eq!(out1, out2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_prefix_consistency() {
// T(1..n) 前缀应与长度更短的输出一致
let prk = [0x22u8; 32];
let info = b"prefix-test";
let short = hkdf_expand(&prk, info, 32).unwrap();
let long = hkdf_expand(&prk, info, 64).unwrap();
assert_eq!(&long[..32], &short[..]);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_max_len_rejected() {
let prk = [0u8; 32];
let result = hkdf_expand(&prk, b"", 255 * 32 + 1);
assert!(result.is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_max_len_accepted() {
let prk = [0u8; 32];
let result = hkdf_expand(&prk, b"", 255 * 32);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 255 * 32);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_different_info_different_output() {
let prk = [0x33u8; 32];
let out1 = hkdf_expand(&prk, b"info-a", 32).unwrap();
let out2 = hkdf_expand(&prk, b"info-b", 32).unwrap();
assert_ne!(out1, out2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_roundtrip_salt_info() {
// 模拟 TLS 1.3 密钥调度:extract 然后 expand 两种 label
let salt = b"tls13-early-secret-salt";
let ikm = b"shared-secret-from-key-exchange";
let prk = hkdf_extract(Some(salt), ikm);
let key1 = hkdf_expand(&prk, b"tls13 key", 16).unwrap();
let key2 = hkdf_expand(&prk, b"tls13 iv", 12).unwrap();
assert_eq!(key1.len(), 16);
assert_eq!(key2.len(), 12);
assert_ne!(&key1[..12], &key2[..]);
}
}
+72 -13
View File
@@ -23,6 +23,7 @@
//! 如需 HMAC,请使用 [`hmac_sm3`]。
mod compress;
pub mod hkdf;
use compress::{compress, IV};
@@ -104,33 +105,56 @@ impl Sm3Hasher {
///
/// 调用后此 hasher 不应再使用(消耗所有权的版本请用 [`finalize`](Self::finalize))。
pub fn finalize(mut self) -> [u8; DIGEST_LEN] {
Self::finalize_inner(&mut self)
}
/// 完成哈希并重置状态(供复用,无需重新构造)
///
/// 等同于 `finalize()` 后调用 `reset()`,但只需一次操作。
/// rustls `Hasher` trait 要求此语义(`finish(&mut self)`)。
pub fn finalize_reset(&mut self) -> [u8; DIGEST_LEN] {
let out = Self::finalize_inner(self);
self.reset();
out
}
/// 重置为初始状态(等同于重新调用 `new()`,但复用已分配内存)
pub fn reset(&mut self) {
self.state = IV;
self.buffer = [0u8; 64];
self.buf_len = 0;
self.bit_len = 0;
}
/// 内部完成函数(同时供消耗版和借用版使用)
fn finalize_inner(h: &mut Self) -> [u8; DIGEST_LEN] {
// 计算总位数(包含缓冲区中的字节)
let total_bits = self.bit_len.wrapping_add((self.buf_len as u64) * 8);
let total_bits = h.bit_len.wrapping_add((h.buf_len as u64) * 8);
// Padding:追加 0x80 + 零字节,使消息长度 ≡ 56 (mod 64)
self.buffer[self.buf_len] = 0x80;
self.buf_len += 1;
h.buffer[h.buf_len] = 0x80;
h.buf_len += 1;
if self.buf_len > 56 {
if h.buf_len > 56 {
// 当前块填不下长度字段,先处理这块,再开一块
for i in self.buf_len..64 {
self.buffer[i] = 0;
for i in h.buf_len..64 {
h.buffer[i] = 0;
}
compress(&mut self.state, &self.buffer);
self.buffer = [0u8; 64];
compress(&mut h.state, &h.buffer);
h.buffer = [0u8; 64];
} else {
for i in self.buf_len..56 {
self.buffer[i] = 0;
for i in h.buf_len..56 {
h.buffer[i] = 0;
}
}
// 最后 8 字节写入总位长(大端)
self.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
compress(&mut self.state, &self.buffer);
h.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
compress(&mut h.state, &h.buffer);
// 输出:8 个 u32 大端序拼接
let mut out = [0u8; 32];
for (i, &v) in self.state.iter().enumerate() {
for (i, &v) in h.state.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&v.to_be_bytes());
}
out
@@ -262,6 +286,41 @@ mod tests {
assert_eq!(mac.len(), 32);
}
/// reset() 后状态恢复为 new() 初始状态
#[test]
fn test_reset_equals_new() {
let mut h = Sm3Hasher::new();
h.update(b"some data");
h.reset();
let digest_after_reset = h.finalize();
let digest_fresh = Sm3Hasher::digest(b"");
assert_eq!(digest_after_reset, digest_fresh);
}
/// finalize_reset() 返回正确摘要,且随后状态已重置
#[test]
fn test_finalize_reset_correctness() {
let mut h = Sm3Hasher::new();
h.update(b"abc");
let d1 = h.finalize_reset();
// d1 应等于 SM3("abc")
assert_eq!(d1, Sm3Hasher::digest(b"abc"));
// 重置后哈希空消息应等于 SM3("")
let d2 = h.finalize();
assert_eq!(d2, Sm3Hasher::digest(b""));
}
/// finalize_reset() 可连续使用两次,结果一致
#[test]
fn test_finalize_reset_repeatable() {
let mut h = Sm3Hasher::new();
h.update(b"test");
let d1 = h.finalize_reset();
h.update(b"test");
let d2 = h.finalize_reset();
assert_eq!(d1, d2);
}
// 辅助:从十六进制字符串构造 [u8; 32]
fn hex_literal(s: &str) -> [u8; 32] {
let mut out = [0u8; 32];
+63
View File
@@ -553,6 +553,69 @@ pub fn sm4_decrypt_ccm(
Ok(plaintext)
}
// ── GCM/CCM 合并格式(TLS 适配)────────────────────────────────────────────────
/// SM4-GCM 加密(合并输出格式:`ciphertext || tag`
///
/// TLS 记录层要求 AEAD 输出为单一缓冲区,此函数将密文和 16 字节 tag 合并返回。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_gcm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Vec<u8> {
let (mut ct, tag) = sm4_encrypt_gcm(key, nonce, aad, plaintext);
ct.extend_from_slice(&tag);
ct
}
/// SM4-GCM 解密(合并输入格式:`ciphertext || tag`
///
/// 输入必须至少 16 字节(tag 长度);先验证 tag 再解密。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_gcm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
if ciphertext_with_tag.len() < 16 {
return Err(crate::error::Error::InvalidInputLength);
}
let ct_len = ciphertext_with_tag.len() - 16;
let ct = &ciphertext_with_tag[..ct_len];
let tag: &[u8; 16] = ciphertext_with_tag[ct_len..].try_into().unwrap();
sm4_decrypt_gcm(key, nonce, aad, ct, tag)
}
/// SM4-CCM 加密(tag_len = 16,合并输出格式)
///
/// TLS 1.3 `TLS_SM4_CCM_SM3` 使用 16 字节 tag。
/// 等同于 `sm4_encrypt_ccm`(其输出已是 ciphertext||tag 合并格式)。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
sm4_encrypt_ccm(key, nonce, aad, plaintext, 16)
}
/// SM4-CCM 解密(tag_len = 16,合并输入格式)
///
/// 等同于 `sm4_decrypt_ccm(..., 16)`。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ccm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
sm4_decrypt_ccm(key, nonce, aad, ciphertext_with_tag, 16)
}
// ── XTS ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘以 α(XTS tweak 更新)