Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2580571881 |
+20
-1
@@ -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
@@ -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
@@ -422,7 +422,7 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "libsmx"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"crypto-bigint",
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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 = 0x02,length 占 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 length(inner < 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析一个 TLV(tag-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,值应为 1(ecPrivkeyVer1)
|
||||
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
|
||||
if ver_bytes != [0x01] {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
// privateKey OCTET STRING(32 字节)
|
||||
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;
|
||||
|
||||
// 解析外层 SEQUENCE(PrivateKeyInfo)
|
||||
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 = 1:02 01 01
|
||||
// privateKey OCTET STRING:04 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 = 0:02 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());
|
||||
}
|
||||
}
|
||||
@@ -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 127(bytes[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-ECDH(rustls `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 表示发起方 A,false 表示响应方 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 字节应为 0,bytes[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_self,B.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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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];
|
||||
|
||||
@@ -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 更新)
|
||||
|
||||
Reference in New Issue
Block a user