1 Commits

Author SHA1 Message Date
huangxt a4ca734d0d 重构:RustCrypto 兼容接口
主要变更:
- 新增 sm2/、sm3/、sm4/ 独立 crate,实现 RustCrypto traits
  - sm2:实现 signature、elliptic-curve traits
  - sm3:实现 digest、crypto-common traits
  - sm4:实现 cipher、aead traits
- 新增 fuzz/ 模糊测试目标
- 新增 tests/sm2_proptest.rs 属性测试
- 重构 src/sm4/ 使用 RustCrypto AEAD traits
- 更新 CI 工作流支持多 crate 测试
- 更新 .gitignore 忽略 fuzz/target/
2026-03-11 18:32:21 +08:00
36 changed files with 5184 additions and 961 deletions
+108 -7
View File
@@ -88,13 +88,13 @@ jobs:
uses: Swatinem/rust-cache@v2
# Reason: 在所有平台上显式设置 bash 以统一 Windows/Linux/macOS 行为
- name: 运行测试(默认 features = alloc
run: cargo test --all-targets
- name: 运行测试(默认 features,全 workspace
run: cargo test --workspace --all-targets
shell: bash
- name: 运行测试(no_std,无 alloc
# Reason: 验证嵌入式 / WASM / 裸机兼容性
run: cargo test --no-default-features --lib
run: cargo test --workspace --no-default-features --lib
shell: bash
- name: 运行文档测试
@@ -128,13 +128,114 @@ jobs:
# Reason: MSRV 只验证能编译,不运行测试(避免 dev-dependencies 兼容问题)
- name: 检查编译(默认 features
run: cargo check
run: cargo check --workspace
- name: 检查编译(no_std
run: cargo check --no-default-features
run: cargo check --workspace --no-default-features
# =============================================================================
# Job 4: 测试覆盖率(仅 Linux,上传 Codecov
# Job 4: no_std 跨目标编译验证
# =============================================================================
no-std:
name: no_std (${{ matrix.target }})
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
target:
- thumbv7em-none-eabi # ARM Cortex-M4,典型嵌入式目标
- wasm32-unknown-unknown # WebAssembly
steps:
- name: 签出代码
uses: actions/checkout@v4
- name: 安装 Rust 工具链(stable + 目标平台)
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: 缓存 Cargo 编译产物
uses: Swatinem/rust-cache@v2
# Reason: --no-default-features 禁用 alloc,验证裸机 no_std 兼容性
- name: 检查 sm3no_std,无 alloc
run: cargo check -p sm3 --target ${{ matrix.target }} --no-default-features
- name: 检查 sm4no_std,无 alloc
run: cargo check -p sm4 --target ${{ matrix.target }} --no-default-features
- name: 检查 sm2no_std,无 alloc
run: cargo check -p sm2 --target ${{ matrix.target }} --no-default-features
# =============================================================================
# Job 5: Feature Powerset(验证所有 feature 组合均可编译)
# =============================================================================
feature-powerset:
name: Feature Powerset
needs: lint
runs-on: ubuntu-latest
steps:
- name: 签出代码
uses: actions/checkout@v4
- name: 安装 Rust 工具链(stable
uses: dtolnay/rust-toolchain@stable
- name: 缓存 Cargo 编译产物
uses: Swatinem/rust-cache@v2
- name: 安装 cargo-hack
uses: taiki-e/install-action@cargo-hack
# Reason: 验证所有 feature 组合均能编译,防止 feature 互斥或遗漏依赖
- name: 检查 sm3 feature powerset
run: cargo hack check -p sm3 --feature-powerset --no-dev-deps
- name: 检查 sm4 feature powerset
run: cargo hack check -p sm4 --feature-powerset --no-dev-deps
- name: 检查 sm2 feature powerset
run: cargo hack check -p sm2 --feature-powerset --no-dev-deps
# =============================================================================
# Job 6: Miri 内存安全验证
# =============================================================================
miri:
name: Miri
needs: lint
runs-on: ubuntu-latest
steps:
- name: 签出代码
uses: actions/checkout@v4
- name: 安装 Rust nightly + Miri 组件
uses: dtolnay/rust-toolchain@nightly
with:
components: miri
- name: 缓存 Cargo 编译产物
uses: Swatinem/rust-cache@v2
# Reason: 只对纯算法 cratesm3/sm4/sm2)运行 Miri
# 避免对系统调用密集的代码产生误报。
# -Zmiri-strict-provenance 开启严格指针来源检查。
- name: Miri 检查 sm3
run: cargo miri test -p sm3
env:
MIRIFLAGS: "-Zmiri-strict-provenance"
- name: Miri 检查 sm4
run: cargo miri test -p sm4
env:
MIRIFLAGS: "-Zmiri-strict-provenance"
- name: Miri 检查 sm2(仅单元测试,跳过 alloc 密集测试)
run: cargo miri test -p sm2 --lib
env:
MIRIFLAGS: "-Zmiri-strict-provenance"
# =============================================================================
# Job 7: 测试覆盖率(仅 Linux,上传 Codecov
# =============================================================================
coverage:
name: Coverage
@@ -165,7 +266,7 @@ jobs:
# Reason: fail_ci_if_error=false 避免 Codecov 暂时不可用时阻断 CI
# =============================================================================
# Job 5: 性能回归检测(仅 PR 触发)
# Job 8: 性能回归检测(仅 PR 触发)
# =============================================================================
benchmark:
name: Benchmark
+2 -1
View File
@@ -1,2 +1,3 @@
/target
/reference
/reference
fuzz/target/
Generated
+166 -5
View File
@@ -34,7 +34,7 @@ dependencies = [
"ark-serialize",
"ark-std",
"arrayvec",
"digest",
"digest 0.10.7",
"educe",
"itertools 0.13.0",
"num-bigint",
@@ -74,7 +74,7 @@ checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
dependencies = [
"ark-std",
"arrayvec",
"digest",
"digest 0.10.7",
"num-bigint",
]
@@ -100,6 +100,27 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "blobby"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1"
[[package]]
name = "block-buffer"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
@@ -161,6 +182,17 @@ dependencies = [
"half",
]
[[package]]
name = "cipher"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea"
dependencies = [
"blobby",
"crypto-common 0.2.1",
"inout",
]
[[package]]
name = "clap"
version = "4.5.60"
@@ -186,6 +218,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "criterion"
version = "0.5.1"
@@ -273,13 +311,34 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"crypto-common",
"crypto-common 0.1.7",
]
[[package]]
name = "digest"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243"
dependencies = [
"blobby",
"block-buffer",
"const-oid",
"crypto-common 0.2.1",
]
[[package]]
@@ -370,12 +429,36 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "hex-literal"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
[[package]]
name = "hybrid-array"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1"
dependencies = [
"typenum",
]
[[package]]
name = "inout"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
"hybrid-array",
]
[[package]]
name = "is-terminal"
version = "0.4.17"
@@ -444,6 +527,7 @@ dependencies = [
"crypto-bigint",
"getrandom",
"hex",
"proptest",
"rand 0.8.5",
"rand_core 0.6.4",
"rustls",
@@ -557,6 +641,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532"
dependencies = [
"bitflags",
"num-traits",
"rand 0.9.2",
"rand_chacha 0.9.0",
"rand_xorshift",
"unarray",
]
[[package]]
name = "quote"
version = "1.0.45"
@@ -573,7 +671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
@@ -596,6 +694,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -611,6 +719,15 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core 0.9.5",
]
[[package]]
name = "rayon"
version = "1.11.0"
@@ -771,6 +888,44 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
[[package]]
name = "sm2"
version = "0.1.0"
dependencies = [
"crypto-bigint",
"digest 0.11.1",
"hex-literal 0.4.1",
"rand 0.8.5",
"rand_core 0.6.4",
"signature",
"sm3",
"subtle",
"zeroize",
]
[[package]]
name = "sm3"
version = "0.1.0"
dependencies = [
"digest 0.11.1",
"hex-literal 0.4.1",
]
[[package]]
name = "sm4"
version = "0.1.0"
dependencies = [
"cipher",
"hex-literal 0.4.1",
"zeroize",
]
[[package]]
name = "sm9_core"
version = "0.5.0"
@@ -780,7 +935,7 @@ dependencies = [
"ark-ff",
"byteorder",
"crunchy",
"hex-literal",
"hex-literal 1.1.0",
"lazy_static",
"num-traits",
"rand 0.9.2",
@@ -825,6 +980,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.24"
+20
View File
@@ -1,3 +1,22 @@
# ── Workspace 配置 ──────────────────────────────────────────────────────────
[workspace]
resolver = "2"
members = [
".", # libsmx (现有单体 crate,保持向后兼容)
"sm2", # SM2 独立 crate(实现 signature::Signer/Verifier
"sm3", # SM3 独立 crate(实现 digest::Digest
"sm4", # SM4 独立 crate(实现 cipher::BlockCipher
]
[workspace.dependencies]
digest = { version = "0.11.1", default-features = false }
cipher = { version = "0.5.1", default-features = false }
subtle = { version = "2.6", default-features = false }
zeroize = { version = "1.8", default-features = false, features = ["derive"] }
signature = { version = "2.2", default-features = false }
hex-literal = "0.4"
# ── libsmx Package 配置(保留现有不变)────────────────────────────────────
[package]
name = "libsmx"
version = "0.3.0"
@@ -52,6 +71,7 @@ hex = "0.4"
criterion = { version = "0.5", features = ["html_reports"] }
rand = "0.8"
sm9_core = "0.5.0"
proptest = { version = "1", default-features = false, features = ["alloc"] }
# rustls-provider 集成测试依赖(通过 feature 条件编译保护)
rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["log", "webpki"] }
pki-types = { package = "rustls-pki-types", version = "1", features = ["alloc"] }
+26 -10
View File
@@ -1,15 +1,14 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use libsmx::sm4::{sm4_encrypt_ecb, Sm4Key};
use libsmx::sm4::Sm4Key;
fn bench_sm4_ecb(c: &mut Criterion) {
let mut group = c.benchmark_group("SM4-ECB");
fn bench_sm4_block_encrypt(c: &mut Criterion) {
let mut group = c.benchmark_group("SM4-block");
let key = [0u8; 16];
for size in [16usize, 1024, 65536] {
let data = vec![0u8; size];
group.bench_with_input(BenchmarkId::new("encrypt", size), &data, |b, d| {
b.iter(|| sm4_encrypt_ecb(&key, d));
});
}
let sm4 = Sm4Key::new(&key);
group.bench_function("encrypt_block", |b| {
let mut block = [0u8; 16];
b.iter(|| sm4.encrypt_block(&mut block));
});
group.finish();
}
@@ -20,5 +19,22 @@ fn bench_sm4_key_new(c: &mut Criterion) {
});
}
criterion_group!(benches, bench_sm4_ecb, bench_sm4_key_new);
fn bench_sm4_throughput(c: &mut Criterion) {
let mut group = c.benchmark_group("SM4-throughput");
let key = [0u8; 16];
let sm4 = Sm4Key::new(&key);
for size in [16usize, 1024, 65536] {
let mut data = vec![0u8; size];
group.bench_with_input(BenchmarkId::new("encrypt", size), &size, |b, _| {
b.iter(|| {
for chunk in data.chunks_exact_mut(16) {
sm4.encrypt_block(chunk.try_into().unwrap());
}
});
});
}
group.finish();
}
criterion_group!(benches, bench_sm4_block_encrypt, bench_sm4_key_new, bench_sm4_throughput);
criterion_main!(benches);
+37
View File
@@ -0,0 +1,37 @@
[package]
name = "libsmx-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
# Reason: 空 [workspace] 使 fuzz crate 独立于根 workspace
# 这是 cargo-fuzz 的标准做法(需要 nightly 且不应影响 stable CI)。
[workspace]
[dependencies]
libfuzzer-sys = "0.4"
libsmx = { path = ".." }
sm2 = { path = "../sm2" }
sm3 = { path = "../sm3" }
sm4 = { path = "../sm4" }
[[bin]]
name = "fuzz_sm3_digest"
path = "fuzz_targets/fuzz_sm3_digest.rs"
test = false
doc = false
[[bin]]
name = "fuzz_sm4_roundtrip"
path = "fuzz_targets/fuzz_sm4_roundtrip.rs"
test = false
doc = false
[[bin]]
name = "fuzz_sm2_verify"
path = "fuzz_targets/fuzz_sm2_verify.rs"
test = false
doc = false
+22
View File
@@ -0,0 +1,22 @@
//! Fuzz target: SM2 验签不 panic(接受任意字节输入)
//!
//! 对任意输入调用 sm2::verify 不应 panic,只能返回 Ok 或 Err。
//! 验证实现对格式错误输入的健壮性。
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
// 需要至少 32 + 65 + 64 = 161 字节
if data.len() < 161 {
return;
}
let e: &[u8; 32] = data[..32].try_into().unwrap();
let pub_key: &[u8; 65] = data[32..97].try_into().unwrap();
let sig: &[u8; 64] = data[97..161].try_into().unwrap();
// 只要不 panic 即可;Ok 或 Err 都是合法结果
let _ = sm2::verify(e, pub_key, sig);
});
+22
View File
@@ -0,0 +1,22 @@
//! Fuzz target: SM3 一次性哈希与流式哈希必须一致
//!
//! 验证 Sm3::digest(data) == 逐字节 Sm3::update + finalize 的结果相同。
#![no_main]
use libfuzzer_sys::fuzz_target;
use sm3::Digest;
fuzz_target!(|data: &[u8]| {
// 一次性哈希
let hash1 = sm3::Sm3::digest(data);
// 流式哈希(逐字节)
let mut h = sm3::Sm3::new();
for byte in data {
h.update(&[*byte]);
}
let hash2 = h.finalize();
assert_eq!(hash1, hash2, "one-shot and streaming SM3 must agree");
});
+31
View File
@@ -0,0 +1,31 @@
//! Fuzz target: SM4 加密后解密必须还原原始数据
//!
//! 验证对任意 key + blockencrypt 后 decrypt 得到原始 block。
#![no_main]
use libfuzzer_sys::fuzz_target;
use sm4::{Sm4, KeyInit, BlockCipherEncrypt, BlockCipherDecrypt};
use sm4::cipher::array::Array;
fuzz_target!(|data: &[u8]| {
// 需要至少 32 字节(16 字节 key + 16 字节 block
if data.len() < 32 {
return;
}
let key: [u8; 16] = data[..16].try_into().unwrap();
let block_data: [u8; 16] = data[16..32].try_into().unwrap();
let cipher = Sm4::new(&Array::from(key));
let mut block = Array::from(block_data);
let original = block.clone();
// 加密
cipher.encrypt_block(&mut block);
// 解密
cipher.decrypt_block(&mut block);
// 还原检查
assert_eq!(block, original, "SM4 encrypt then decrypt must restore original");
});
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "sm2"
version = "0.1.0"
edition = "2021"
rust-version = "1.83.0"
license = "Apache-2.0"
description = "SM2 (ShangMi 2) elliptic curve cryptography — signature, encryption, key exchange (GB/T 32918-2016). Pure-Rust, no_std."
repository = "https://github.com/kintaiW/libsmx"
documentation = "https://docs.rs/sm2"
readme = "README.md"
categories = ["cryptography", "no-std"]
keywords = ["crypto", "ecc", "sm2", "shangmi", "signature"]
[dependencies]
# SM3 is used by SM2 for Z-value and message digest computation
sm3 = { path = "../sm3" }
# digest::Digest + Update traits (re-exported by sm3, but listed explicitly for clarity)
digest = { workspace = true }
crypto-bigint = { version = "0.6", default-features = false }
subtle = { workspace = true }
zeroize = { workspace = true }
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
# signature trait integration
signature = { workspace = true }
[dev-dependencies]
hex-literal = { workspace = true }
rand = { version = "0.8", default-features = false, features = ["std_rng"] }
[features]
default = ["alloc"]
# alloc: required for encrypt/decrypt (Vec-based ciphertext) and DER encoding
alloc = []
# hazmat: exposes sign_with_k (dangerous raw-k API, for testing only)
hazmat = []
+223
View File
@@ -0,0 +1,223 @@
# sm2
**SM2 椭圆曲线公钥密码算法** — 纯 Rust、`no_std`、常量时间实现,符合 GB/T 32918-2016。
**SM2 Elliptic Curve Public-Key Cryptography** — Pure-Rust, `no_std`, constant-time implementation conforming to GB/T 32918-2016.
[![Crates.io](https://img.shields.io/crates/v/sm2.svg)](https://crates.io/crates/sm2)
[![Docs.rs](https://docs.rs/sm2/badge.svg)](https://docs.rs/sm2)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](../LICENSE)
---
## 目录 / Table of Contents
- [功能 / Features](#功能--features)
- [快速开始 / Quick Start](#快速开始--quick-start)
- [API 概览 / API Overview](#api-概览--api-overview)
- [安全性 / Security](#安全性--security)
- [Feature 标志 / Feature Flags](#feature-标志--feature-flags)
- [依赖 / Dependencies](#依赖--dependencies)
---
## 功能 / Features
**中文:** 本 crate 实现以下 GB/T 32918-2016 标准算法:
**English:** This crate implements the following GB/T 32918-2016 standard algorithms:
| 功能 / Feature | 函数/类型 / Function / Type | 标准 / Standard |
|---|---|---|
| 密钥生成 / Key Generation | `generate_keypair` | GB/T 32918.1 §6.1 |
| 数字签名 / Digital Signature | `sign`, `sign_message`, `SigningKey` | GB/T 32918.2 §6.2 |
| 签名验证 / Verification | `verify`, `verify_message`, `VerifyingKey` | GB/T 32918.2 §6.3 |
| 公钥加密 / Encryption | `encrypt` | GB/T 32918.4 §7.1 |
| 公钥解密 / Decryption | `decrypt` | GB/T 32918.4 §7.2 |
| 密钥交换 / Key Exchange | `key_exchange::ecdh`, `exchange_a/b` | GB/T 32918.3 |
| DER 编解码 / DER Encoding | `der::sig_to_der`, `private_key_from_pkcs8_der` | RFC 5915/5480 |
---
## 快速开始 / Quick Start
`Cargo.toml` 中添加 / Add to your `Cargo.toml`:
```toml
[dependencies]
sm2 = { path = "path/to/sm2" }
# 或 crates.io 发布后 / or after crates.io release:
# sm2 = "0.1"
```
### 签名与验签 / Sign and Verify
```rust
use sm2::{SigningKey, VerifyingKey, DEFAULT_ID, generate_keypair};
use sm2::signature::{Signer, Verifier};
use rand_core::OsRng;
// 生成密钥对 / Generate key pair
let (private_key, public_key_bytes) = generate_keypair(&mut OsRng);
// 创建签名/验证密钥 / Create signing and verifying keys
let signing_key = SigningKey::new(private_key, DEFAULT_ID);
let verifying_key = VerifyingKey::new(public_key_bytes, DEFAULT_ID);
// 签名 / Sign
let message = b"Hello, SM2!";
let signature = signing_key.sign(message);
// 验签 / Verify
verifying_key.verify(message, &signature).expect("验签应通过 / verification should pass");
```
### 底层签名 API / Low-level Signing API
```rust
use sm2::{PrivateKey, generate_keypair, get_z, get_e, sign, verify, DEFAULT_ID};
use rand_core::OsRng;
let (pri_key, pub_key) = generate_keypair(&mut OsRng);
// 计算 Z 值和摘要 / Compute Z-value and digest
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, b"my message");
// 签名 / Sign
let sig = sign(&e, &pri_key, &mut OsRng);
// 验签 / Verify
verify(&e, &pub_key, &sig).expect("ok");
```
### 公钥加解密 / Public-key Encrypt / Decrypt
```rust
# #[cfg(feature = "alloc")]
use sm2::{encrypt, decrypt, generate_keypair};
use rand_core::OsRng;
let (pri_key, pub_key) = generate_keypair(&mut OsRng);
let plaintext = b"secret message";
let ciphertext = encrypt(&pub_key, plaintext, &mut OsRng).unwrap();
let recovered = decrypt(&pri_key, &ciphertext).unwrap();
assert_eq!(recovered, plaintext);
```
密文格式为 `C1 || C3 || C2`(65 + 32 + n 字节),符合 GB/T 32918.4 §6.1。
Ciphertext format: `C1 || C3 || C2` (65 + 32 + n bytes), per GB/T 32918.4 §6.1.
### SM2-ECDH 密钥交换 / Key Exchange
```rust
use sm2::{generate_keypair, key_exchange::ecdh};
use rand_core::OsRng;
let (pri_a, pub_a) = generate_keypair(&mut OsRng);
let (pri_b, pub_b) = generate_keypair(&mut OsRng);
// 双方各自计算,结果一致 / Both parties compute the same shared secret
let shared_a = ecdh(&pri_a, &pub_b).unwrap();
let shared_b = ecdh(&pri_b, &pub_a).unwrap();
assert_eq!(shared_a, shared_b);
```
---
## API 概览 / API Overview
### 类型 / Types
| 类型 / Type | 说明 / Description |
|---|---|
| `PrivateKey` | SM2 私钥(32 字节,离开作用域自动清零)/ SM2 private key (auto-zeroized on drop) |
| `SigningKey<'id>` | 签名密钥(私钥 + 用户 ID/ Signing key (private key + user ID) |
| `VerifyingKey<'id>` | 验证密钥(公钥 + 用户 ID/ Verifying key (public key + user ID) |
| `Sm2Signature` | 签名结果(r\|\|s64 字节)/ Signature (r\|\|s, 64 bytes) |
| `key_exchange::EphemeralKey` | 密钥交换临时密钥对 / Ephemeral key pair for key exchange |
| `Error` | 统一错误类型 / Unified error type |
### 常量 / Constants
| 常量 / Constant | 值 / Value | 说明 / Description |
|---|---|---|
| `DEFAULT_ID` | `b"1234567812345678"` | GB/T 32918.2 §A.2 示例用户 ID / Example user ID from spec |
### 关键函数 / Key Functions
```
generate_keypair(rng) → (PrivateKey, [u8; 65])
get_z(id, pub_key) → [u8; 32]
get_e(z, msg) → [u8; 32]
sign(e, pri_key, rng) → [u8; 64]
sign_message(msg, id, pri, rng) → [u8; 64]
verify(e, pub_key, sig) → Result<(), Error>
verify_message(msg, id, pub, sig) → Result<(), Error>
encrypt(pub_key, msg, rng) → Result<Vec<u8>, Error> // alloc
decrypt(pri_key, ciphertext) → Result<Vec<u8>, Error> // alloc
```
---
## 安全性 / Security
**中文:**
- **常量时间**:所有私钥相关运算均为常量时间(Montgomery 域算术 + `subtle::ConditionallySelectable`
- **标量乘法**:固定迭代 256 位,不跳过前导零,防止时序侧信道
- **自动清零**`PrivateKey` 离开作用域后自动清零([`ZeroizeOnDrop`]
- **无 unsafe**:全 crate 使用 `#![forbid(unsafe_code)]`
- **SM4 S-box**:(通过 `sm4` 依赖)使用布尔电路位切片实现,无查表
**English:**
- **Constant-time**: All secret-dependent operations use Montgomery-domain arithmetic + `subtle::ConditionallySelectable`
- **Scalar multiplication**: Iterates all 256 bits regardless of leading zeros — no timing leakage
- **Auto-zeroize**: `PrivateKey` is automatically cleared on drop via [`ZeroizeOnDrop`]
- **No unsafe code**: The entire crate is `#![forbid(unsafe_code)]`
- **Bitslice S-box**: (via `sm4` dependency) uses boolean-circuit implementation, no table lookups
> **危险 API / Dangerous API**: `sign_with_k` 仅在启用 `hazmat` feature 时可用,用于测试向量验证。误用相同 k 值会泄露私钥。
>
> `sign_with_k` is only available with the `hazmat` feature, intended for test-vector validation only. Reusing k across signatures leaks the private key.
---
## Feature 标志 / Feature Flags
| Feature | 默认启用 / Default | 说明 / Description |
|---|---|---|
| `alloc` | ✅ | 启用 `encrypt`/`decrypt` 和 DER 编码(需要 `Vec`/ Enables `encrypt`/`decrypt` and DER encoding (requires `Vec`) |
| `hazmat` | ❌ | 暴露 `sign_with_k`(危险的固定 k 签名,仅用于测试)/ Exposes `sign_with_k` (dangerous fixed-k signing, test only) |
---
## 依赖 / Dependencies
| Crate | 版本 / Version | 用途 / Purpose |
|---|---|---|
| `sm3` | workspace | SM3 哈希(Z 值、消息摘要、KDF/ SM3 hash (Z-value, digest, KDF) |
| `crypto-bigint` | 0.6 | 常量时间大整数(Montgomery 域)/ Constant-time big integers |
| `subtle` | 2.6 | 常量时间比较 / Constant-time comparisons |
| `zeroize` | 1.8 | 密钥安全清零 / Secure key zeroization |
| `rand_core` | 0.6 | RNG trait(含 OsRng/ RNG traits (including OsRng) |
| `signature` | 2.2 | `Signer`/`Verifier` trait 集成 / `Signer`/`Verifier` trait integration |
---
## 许可证 / License
Apache-2.0 — 见 / see [`LICENSE`](../LICENSE)
---
## 参考标准 / Reference Standards
- GB/T 32918.1-2016SM2 公钥密码算法 第1部分:总则
- GB/T 32918.2-2016SM2 公钥密码算法 第2部分:数字签名算法
- GB/T 32918.3-2016SM2 公钥密码算法 第3部分:密钥交换协议
- GB/T 32918.4-2016SM2 公钥密码算法 第4部分:公钥加密算法
- GB/T 32918.5-2017SM2 公钥密码算法 第5部分:参数定义
+533
View File
@@ -0,0 +1,533 @@
//! 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) }`
//!
//! ## 公钥 SPKI 格式
//! rustls `SigningKey::public_key()` 需要 `SubjectPublicKeyInfoDer`
//! ```text
//! SEQUENCE {
//! SEQUENCE {
//! OID id-ecPublicKey (1.2.840.10045.2.1)
//! OID SM2 (1.2.156.10197.1.301)
//! }
//! BIT STRING (04 || x(32B) || y(32B))
//! }
//! ```
//!
//! ## 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::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)
}
// ── SM2 公钥 SPKI DER 编码 ────────────────────────────────────────────────────
/// 将 SM2 公钥(65 字节,04||x||y)编码为 SubjectPublicKeyInfo DER
///
/// 格式(RFC 5480):
/// ```text
/// SEQUENCE {
/// SEQUENCE {
/// OID 1.2.840.10045.2.1 (id-ecPublicKey, 7 字节)
/// OID 1.2.156.10197.1.301 (SM2, 8 字节)
/// }
/// BIT STRING 0x00 || pub_key (65 字节 + 1 字节前缀)
/// }
/// ```
///
/// 此格式是 rustls `SigningKey::public_key()` 所需的 `SubjectPublicKeyInfoDer`。
#[cfg(feature = "alloc")]
pub fn public_key_to_spki_der(pub_key: &[u8; 65]) -> Vec<u8> {
// OID 1.2.840.10045.2.1 (id-ecPublicKey): 06 07 2a 86 48 ce 3d 02 01
let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
// OID 1.2.156.10197.1.301 (SM2): 06 08 2a 81 1c cf 55 01 82 2d
let oid_sm2: &[u8] = &[0x06, 0x08, 0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d];
// AlgorithmIdentifier SEQUENCE
let alg_inner_len = oid_ec.len() + oid_sm2.len();
let mut alg = Vec::with_capacity(2 + alg_inner_len);
alg.push(0x30);
alg.push(alg_inner_len as u8);
alg.extend_from_slice(oid_ec);
alg.extend_from_slice(oid_sm2);
// BIT STRING: 0x03 <len> 0x00 <pub_key>
// Reason: 0x00 是 unused bits 字段,表示最后一字节无填充位
let bit_str_len = 1 + pub_key.len(); // 0x00 前缀 + 65 字节公钥
let mut bit_str = Vec::with_capacity(2 + bit_str_len);
bit_str.push(0x03);
bit_str.push(bit_str_len as u8);
bit_str.push(0x00); // unused bits = 0
bit_str.extend_from_slice(pub_key);
// 外层 SEQUENCE
let outer_len = alg.len() + bit_str.len();
let mut der = Vec::with_capacity(2 + outer_len);
der.push(0x30);
der.push(outer_len as u8);
der.extend_from_slice(&alg);
der.extend_from_slice(&bit_str);
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());
}
// ── SPKI DER 测试 ──────────────────────────────────────────────────────────
#[cfg(feature = "alloc")]
#[test]
fn test_spki_der_structure() {
use crate::PrivateKey;
let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap();
let pub_key = pri.public_key();
let spki = public_key_to_spki_der(&pub_key);
// 外层 SEQUENCE
assert_eq!(spki[0], 0x30, "外层 tag 应为 SEQUENCE");
// BIT STRING 内包含 04||x||y65字节)
// 确认公钥原始字节出现在 SPKI 中
let pos = spki.windows(65).position(|w| w == pub_key);
assert!(pos.is_some(), "SPKI 应包含原始公钥字节");
}
#[cfg(feature = "alloc")]
#[test]
fn test_spki_der_oid_ec() {
use crate::PrivateKey;
let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap();
let pub_key = pri.public_key();
let spki = public_key_to_spki_der(&pub_key);
// id-ecPublicKey OID bytes
let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
assert!(
spki.windows(oid_ec.len()).any(|w| w == oid_ec),
"SPKI 应包含 id-ecPublicKey OID"
);
}
}
+610
View File
@@ -0,0 +1,610 @@
//! SM2 椭圆曲线点运算(GB/T 32918.1-2016 §4.2
//!
//! 使用 Jacobian 射影坐标(X:Y:Z),仿射坐标满足 x = X/Z², y = Y/Z³。
//! 避免热路径中的 Fp 求逆运算,性能优于仿射坐标加法。
use crypto_bigint::U256;
use subtle::{Choice, ConditionallySelectable};
use crate::error::Error;
use crate::field::{
fp_add, fp_from_bytes, fp_inv, fp_mul, fp_neg, fp_square, fp_sub, fp_to_bytes, Fp, CURVE_A,
CURVE_B, FIELD_MODULUS, GX, GY,
};
// ── 仿射坐标点 ────────────────────────────────────────────────────────────────
/// SM2 曲线上的仿射坐标点(公开类型,用于序列化/反序列化)
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AffinePoint {
/// x 坐标
pub x: Fp,
/// y 坐标
pub y: Fp,
}
// ── Jacobian 射影坐标点(内部运算专用)────────────────────────────────────────
/// SM2 曲线上的 Jacobian 射影坐标点(内部使用)
///
/// 仿射点 (x, y) 对应射影点 (X:Y:Z) 满足 x = X/Z², y = Y/Z³
#[derive(Clone, Copy, Debug)]
pub struct JacobianPoint {
pub(crate) x: Fp,
pub(crate) y: Fp,
pub(crate) z: Fp,
}
// ── Jacobian 常量时间选择 ──────────────────────────────────────────────────────
/// 为 JacobianPoint 实现常量时间条件选择
///
/// Reason: 标量乘中用掩码选择替代 if/else,消除基于标量位的条件分支。
impl ConditionallySelectable for JacobianPoint {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
JacobianPoint {
x: Fp::conditional_select(&a.x, &b.x, choice),
y: Fp::conditional_select(&a.y, &b.y, choice),
z: Fp::conditional_select(&a.z, &b.z, choice),
}
}
}
impl JacobianPoint {
/// 无穷远点(群的单位元),用 Z=0 表示
pub const INFINITY: Self = JacobianPoint {
x: Fp::ONE,
y: Fp::ONE,
z: Fp::ZERO,
};
/// 从仿射坐标构造(Z=1
pub fn from_affine(p: &AffinePoint) -> Self {
JacobianPoint {
x: p.x,
y: p.y,
z: Fp::ONE,
}
}
/// 转换为仿射坐标(需要一次 Fp 求逆,仅在最终输出时使用)
pub fn to_affine(&self) -> Result<AffinePoint, Error> {
if self.is_infinity() {
return Err(Error::PointAtInfinity);
}
let z_inv = fp_inv(&self.z).ok_or(Error::PointAtInfinity)?;
let z_inv2 = fp_square(&z_inv);
let z_inv3 = fp_mul(&z_inv2, &z_inv);
Ok(AffinePoint {
x: fp_mul(&self.x, &z_inv2),
y: fp_mul(&self.y, &z_inv3),
})
}
/// 判断是否为无穷远点(常量时间,公开接口)
pub fn is_infinity(&self) -> bool {
bool::from(self.ct_is_infinity())
}
/// 常量时间无穷远判断(内部辅助,返回 Choice)
///
/// Reason: 返回 Choice 供 conditional_select 直接使用,避免 bool 转换后再转回 Choice
fn ct_is_infinity(&self) -> Choice {
// Reason: 用 ConstantTimeEq 比较所有 32 字节,执行时间与 Z 值无关,
// 替代 Iterator::all 的短路求值(后者泄露 Z 坐标前缀信息)。
use subtle::ConstantTimeEq;
fp_to_bytes(&self.z).ct_eq(&[0u8; 32])
}
/// 点倍运算(Jacobian 坐标,a=-3 优化公式,完全常量时间)
///
/// 公式来自 <https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b>
/// SM2 曲线 a = p-3 ≡ -3 (mod p),使用 a=-3 特化公式降低乘法次数。
///
/// # 安全性
/// 无条件执行完整运算,用 `conditional_select` 处理无穷远退化情况,
/// 消除 `if is_infinity()` 分支对标量前导零位的泄露。
pub fn double(&self) -> Self {
let (x1, y1, z1) = (&self.x, &self.y, &self.z);
let delta = fp_square(z1); // Z1²
let gamma = fp_square(y1); // Y1²
let beta = fp_mul(x1, &gamma); // X1·Y1²
// alpha = 3·(X1-delta)·(X1+delta) [a=-3 优化]
let alpha = fp_mul(&fp_sub(x1, &delta), &fp_add(x1, &delta));
let alpha = fp_add(&fp_add(&alpha, &alpha), &alpha); // 3·alpha
// X3 = alpha² - 8·beta
let x3 = fp_sub(&fp_square(&alpha), &double2(&double1(&beta)));
// Z3 = (Y1+Z1)² - gamma - delta
let z3 = fp_sub(&fp_sub(&fp_square(&fp_add(y1, z1)), &gamma), &delta);
// Y3 = alpha·(4·beta - X3) - 8·gamma²
let gamma2 = fp_square(&gamma);
let y3 = fp_sub(
&fp_mul(&alpha, &fp_sub(&double2(&beta), &x3)),
&double2(&double1(&gamma2)),
);
let d = JacobianPoint {
x: x3,
y: y3,
z: z3,
};
// Reason: 无穷远点的倍点仍为无穷远点;用掩码选择替代 if 分支,
// 避免 scalar_mul 热路径中泄露哪些迭代位为前导零。
JacobianPoint::conditional_select(&d, self, self.ct_is_infinity())
}
/// 点加运算(完全常量时间,无条件分支)
///
/// 公式来自 <https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl>
///
/// # 安全性
/// 采用"计算所有情况 + 掩码选择"策略,消除全部退化情况的条件分支:
/// - P = ∞ → Q(无穷远加法单位元)
/// - Q = ∞ → P
/// - P = Q → double(P)(相同点,用 ct_eq 检测 H==0 且 R==0
/// - P = -Q → ∞(互反点,用 ct_eq 检测 H==0 且 R≠0
/// - 正常情况 → 标准 Jacobian 加法
///
/// Reason: 原实现的 3 处 `if` 分支(is_infinity、H==0、R==0
/// 在 scalar_mul 热路径中泄露标量的汉明重量及位分布。
pub fn add(p: &JacobianPoint, q: &JacobianPoint) -> JacobianPoint {
use subtle::ConstantTimeEq;
let z1sq = fp_square(&p.z);
let z2sq = fp_square(&q.z);
let u1 = fp_mul(&p.x, &z2sq); // X1·Z2²
let u2 = fp_mul(&q.x, &z1sq); // X2·Z1²
let s1 = fp_mul(&p.y, &fp_mul(&q.z, &z2sq)); // Y1·Z2³
let s2 = fp_mul(&q.y, &fp_mul(&p.z, &z1sq)); // Y2·Z1³
let h = fp_sub(&u2, &u1);
let r = fp_sub(&s2, &s1);
// 常量时间零判断(替代 Iterator::all 短路)
let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]);
let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]);
// 无条件执行标准 Jacobian 加法(当 h==0 时结果为垃圾值,后续掩码覆盖)
let h2 = fp_square(&h);
let h3 = fp_mul(&h, &h2);
let u1h2 = fp_mul(&u1, &h2);
// X3 = R² - H³ - 2·U1·H²
let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2));
// Y3 = R·(U1·H² - X3) - S1·H³
let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&s1, &h3));
// Z3 = H·Z1·Z2 (当 H==0 时 z3=0,即 INFINITY,与下面掩码一致)
let z3 = fp_mul(&fp_mul(&h, &p.z), &q.z);
let normal = JacobianPoint {
x: x3,
y: y3,
z: z3,
};
// 预计算 P==Q 退化情况的结果(无条件执行,结果由掩码决定是否使用)
let double_p = p.double();
// 按优先级从低到高用 conditional_select 叠加(后面覆盖前面):
// 优先级 1(最低):正常 Jacobian 加法
let result = normal;
// 优先级 2P == -Q → INFINITYh==0 且 r≠0
let result = JacobianPoint::conditional_select(
&result,
&JacobianPoint::INFINITY,
h_is_zero & !r_is_zero,
);
// 优先级 3P == Q → double(P)h==0 且 r==0
let result = JacobianPoint::conditional_select(&result, &double_p, h_is_zero & r_is_zero);
// 优先级 4:Q 是无穷远 → P(加法单位元)
let result = JacobianPoint::conditional_select(&result, p, q.ct_is_infinity());
// 优先级 5(最高):P 是无穷远 → Q
JacobianPoint::conditional_select(&result, q, p.ct_is_infinity())
}
/// 标量乘 k·P(常量时间,固定 256 位迭代)
///
/// Reason: 固定迭代次数 + `conditional_select` 掩码选择,消除基于标量位的条件分支,
/// 防止时序侧信道攻击。执行路径与标量 k 的值完全无关。
pub fn scalar_mul(k: &U256, p: &JacobianPoint) -> JacobianPoint {
let mut result = JacobianPoint::INFINITY;
// 固定 256 次迭代,不跳过前导零
for byte in &k.to_be_bytes() {
for b in (0..8).rev() {
// 始终执行倍点(与标量位无关)
result = result.double();
// 始终计算加法(与标量位无关)
let sum = JacobianPoint::add(&result, p);
// Reason: 用掩码选择结果,无条件分支:bit=1 取 sumbit=0 取 result
let bit = Choice::from((byte >> b) & 1);
result = JacobianPoint::conditional_select(&result, &sum, bit);
}
}
result
}
/// 基点标量乘 k·G(密钥生成和签名专用,使用 w=4 固定窗口加速)
pub fn scalar_mul_g(k: &U256) -> JacobianPoint {
scalar_mul_g_window(k)
}
}
// ── 辅助倍增函数(用于 Jacobian 公式中的常数倍计算)────────────────────────
#[inline]
fn double1(a: &Fp) -> Fp {
fp_add(a, a)
}
#[inline]
fn double2(a: &Fp) -> Fp {
let t = double1(a);
double1(&t)
}
// ── 混合 Jacobian-仿射加法(q.Z = 1 优化)────────────────────────────────────
/// 混合点加 PJacobian+ QAffineZ=1
///
/// 相比标准 Jacobian+Jacobian 加法,利用 Z_Q=1 省去:
/// - Z2² 计算(1 次 fp_square
/// - X1·Z2² 简化为 X10 次乘法)
/// - Y1·Z2³ 简化为 Y10 次乘法)
/// - Z3 中的 Z2 乘法(Z3 = H·Z1,而非 H·Z1·Z2
///
/// 共节省约 3~4 次域乘法,用于预计算表构建和 multi_scalar_mul 内循环。
///
/// # 安全性
/// 完全常量时间,退化情况处理与 `JacobianPoint::add` 相同。
fn add_mixed(p: &JacobianPoint, q: &AffinePoint) -> JacobianPoint {
use subtle::ConstantTimeEq;
// Z_Q = 1,故 u1 = X1s1 = Y1(无需额外乘法)
let z1sq = fp_square(&p.z); // Z1²
let z1cu = fp_mul(&p.z, &z1sq); // Z1³
let u2 = fp_mul(&q.x, &z1sq); // X2·Z1²
let s2 = fp_mul(&q.y, &z1cu); // Y2·Z1³
let h = fp_sub(&u2, &p.x);
let r = fp_sub(&s2, &p.y);
let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]);
let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]);
let h2 = fp_square(&h);
let h3 = fp_mul(&h, &h2);
let u1h2 = fp_mul(&p.x, &h2);
let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2));
let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&p.y, &h3));
// Reason: Z_Q = 1,故 Z3 = H·Z1·Z2 = H·Z1,节省一次乘法
let z3 = fp_mul(&h, &p.z);
let normal = JacobianPoint {
x: x3,
y: y3,
z: z3,
};
let double_p = p.double();
let result = normal;
let result = JacobianPoint::conditional_select(
&result,
&JacobianPoint::INFINITY,
h_is_zero & !r_is_zero,
);
let result = JacobianPoint::conditional_select(&result, &double_p, h_is_zero & r_is_zero);
// P = INFINITY → 返回 Q(注:预计算表中 Q 绝不是无穷远点,
// 但在通用调用中仍需正确处理)
let q_jac = JacobianPoint::from_affine(q);
JacobianPoint::conditional_select(&result, &q_jac, p.ct_is_infinity())
}
// ── SM2 基点固定窗口标量乘(w=4)─────────────────────────────────────────────
/// 基点固定窗口标量乘 k·G(w=4,预计算 15 个点,常量时间)
///
/// 原理:将 256-bit 标量按 4-bit 切分为 64 个窗口。
/// 每个窗口先执行 4 次倍点,再常量时间查表做一次加法。
/// 共需 256 次 double + 64 次 add,相比双倍-加法的 256 次 add 节省约 75%。
///
/// Reason: 预计算表仅含 G 的已知倍数(公开常量基点),不依赖秘密输入;
/// 窗口值为秘密标量位,但表查找通过 15 次 `conditional_select` 实现,
/// 不含任何数据依赖分支,保持常量时间性质。
fn scalar_mul_g_window(k: &U256) -> JacobianPoint {
use subtle::ConstantTimeEq;
let g_aff = AffinePoint { x: GX, y: GY };
let g_jac = JacobianPoint::from_affine(&g_aff);
// 预计算表:table[i] = i·Gi = 0..=15table[0] = INFINITY,占位不用)
// Reason: 使用 add_mixed 构建表,g_aff 始终 Z=1,节省约 3 次域乘/步
let mut table = [JacobianPoint::INFINITY; 16];
table[1] = g_jac;
for i in 2..=15usize {
table[i] = add_mixed(&table[i - 1], &g_aff);
}
let mut result = JacobianPoint::INFINITY;
for byte in &k.to_be_bytes() {
// ── 高 4 位窗口 ─────────────────────────────────────────────────────
for _ in 0..4 {
result = result.double();
}
let window = byte >> 4;
// 常量时间表查找:遍历 1..=15,用 ct_eq 选出 table[window]
let mut sel = JacobianPoint::INFINITY;
for j in 1u8..=15 {
let eq = window.ct_eq(&j);
sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq);
}
// window=0 时 sel 仍为 INFINITYadd(result, INFINITY) = result
result = JacobianPoint::add(&result, &sel);
// ── 低 4 位窗口 ─────────────────────────────────────────────────────
for _ in 0..4 {
result = result.double();
}
let window = byte & 0xF;
let mut sel = JacobianPoint::INFINITY;
for j in 1u8..=15 {
let eq = window.ct_eq(&j);
sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq);
}
result = JacobianPoint::add(&result, &sel);
}
result
}
// ── AffinePoint 公开接口 ──────────────────────────────────────────────────────
impl AffinePoint {
/// SM2 基点 G
pub fn generator() -> Self {
AffinePoint { x: GX, y: GY }
}
/// 验证点是否在 SM2 曲线上:y² ≡ x³ + ax + b (mod p)
pub fn is_on_curve(&self) -> bool {
let x2 = fp_square(&self.x);
let x3 = fp_mul(&x2, &self.x);
let ax = fp_mul(&CURVE_A, &self.x);
let rhs = fp_add(&fp_add(&x3, &ax), &CURVE_B);
fp_square(&self.y) == rhs
}
/// 从未压缩格式 04||x||y65 字节)解析点
///
/// 符合 GB/T 32918.1-2016 §4.2.9
pub fn from_bytes(bytes: &[u8; 65]) -> Result<Self, Error> {
if bytes[0] != 0x04 {
return Err(Error::InvalidPublicKey);
}
let x_bytes: [u8; 32] = bytes[1..33].try_into().unwrap();
let y_bytes: [u8; 32] = bytes[33..65].try_into().unwrap();
// 检查坐标在 [0, p-1] 范围内
use crypto_bigint::subtle::ConstantTimeGreater;
let x_val = U256::from_be_slice(&x_bytes);
let y_val = U256::from_be_slice(&y_bytes);
if bool::from(x_val.ct_gt(&FIELD_MODULUS))
|| x_val == FIELD_MODULUS
|| bool::from(y_val.ct_gt(&FIELD_MODULUS))
|| y_val == FIELD_MODULUS
{
return Err(Error::InvalidPublicKey);
}
let p = AffinePoint {
x: fp_from_bytes(&x_bytes),
y: fp_from_bytes(&y_bytes),
};
if !p.is_on_curve() {
return Err(Error::InvalidPublicKey);
}
Ok(p)
}
/// 序列化为未压缩格式 04||x||y(65 字节)
pub fn to_bytes(&self) -> [u8; 65] {
let mut out = [0u8; 65];
out[0] = 0x04;
out[1..33].copy_from_slice(&fp_to_bytes(&self.x));
out[33..65].copy_from_slice(&fp_to_bytes(&self.y));
out
}
/// 从压缩格式 02/03||x33 字节)解压缩点
///
/// 符合 GB/T 32918.1-2016 §4.2.10
pub fn decompress(bytes: &[u8; 33]) -> Result<Self, Error> {
let prefix = bytes[0];
if prefix != 0x02 && prefix != 0x03 {
return Err(Error::InvalidPublicKey);
}
let x_bytes: [u8; 32] = bytes[1..33].try_into().unwrap();
use crypto_bigint::subtle::ConstantTimeGreater;
let x_val = U256::from_be_slice(&x_bytes);
if bool::from(x_val.ct_gt(&FIELD_MODULUS)) || x_val == FIELD_MODULUS {
return Err(Error::InvalidPublicKey);
}
let x = fp_from_bytes(&x_bytes);
// 计算 y² = x³ + ax + b
let x2 = fp_square(&x);
let x3 = fp_mul(&x2, &x);
let ax = fp_mul(&CURVE_A, &x);
let y2 = fp_add(&fp_add(&x3, &ax), &CURVE_B);
let y = crate::field::fp_sqrt(&y2).ok_or(Error::InvalidPublicKey)?;
// 按前缀奇偶性选择正确的 y
// prefix 02 → 偶数(LSB=0),prefix 03 → 奇数(LSB=1
let y_lsb = fp_to_bytes(&y)[31] & 1;
let want_odd = prefix & 1;
let y_final = if y_lsb == want_odd { y } else { fp_neg(&y) };
Ok(AffinePoint { x, y: y_final })
}
}
// ── 双标量乘:u·G + v·Q(用于签名验证)─────────────────────────────────────
/// 计算 u·G + v·Q(顺序双标量乘,用于 SM2 验签第 3 步)
/// 双标量乘 u·G + v·QShamir's trick 交错法,用于验签)
///
/// Reason: 验签时 u、v 均为公开值(非秘密),无需常量时间。
/// Shamir's trick 预计算 {P, Q, P+Q},每位只需 1 次 double + 最多 1 次 add
/// 比两次独立标量乘(各 256 次 double + 平均 128 add)快约 25%。
pub fn multi_scalar_mul(u: &U256, v: &U256, q: &AffinePoint) -> Result<AffinePoint, Error> {
// Reason: u、v 均为验签公开值,non-CT 的 match 分支不泄露秘密;
// 使用 add_mixed(Jacobian, Affine) 替代全量 Jacobian add
// 节省约 3 次域乘/步,g 和 q 已是仿射坐标直接传入。
let g = AffinePoint::generator();
let q_jac = JacobianPoint::from_affine(q);
let g_jac = JacobianPoint::from_affine(&g);
// 预计算 G+QJacobian,含退化处理)
let gq_jac = JacobianPoint::add(&g_jac, &q_jac);
let u_bytes = u.to_be_bytes();
let v_bytes = v.to_be_bytes();
let mut result = JacobianPoint::INFINITY;
for i in 0..32 {
let ub = u_bytes[i];
let vb = v_bytes[i];
for b in (0..8).rev() {
result = result.double();
let ui = (ub >> b) & 1;
let vi = (vb >> b) & 1;
// Reason: u、v 公开,match 分支安全;add_mixed 对仿射 g/q 节省域乘,
// gq 为 Jacobian 仍用全量 add(无额外求逆开销)
match (ui, vi) {
(1, 0) => result = add_mixed(&result, &g),
(0, 1) => result = add_mixed(&result, q),
(1, 1) => result = JacobianPoint::add(&result, &gq_jac),
_ => {}
}
}
}
result.to_affine()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::{fp_to_bytes, GX, GY};
#[test]
fn test_generator_on_curve() {
assert!(AffinePoint::generator().is_on_curve());
}
#[test]
fn test_double_stays_on_curve() {
let g = JacobianPoint::from_affine(&AffinePoint::generator());
let g2 = g.double().to_affine().unwrap();
assert!(g2.is_on_curve());
}
#[test]
fn test_add_commutativity() {
let g = JacobianPoint::from_affine(&AffinePoint::generator());
let g2 = g.double();
let p1 = JacobianPoint::add(&g2, &g).to_affine().unwrap();
let p2 = JacobianPoint::add(&g, &g2).to_affine().unwrap();
assert_eq!(fp_to_bytes(&p1.x), fp_to_bytes(&p2.x));
assert_eq!(fp_to_bytes(&p1.y), fp_to_bytes(&p2.y));
assert!(p1.is_on_curve());
}
#[test]
fn test_scalar_mul_one_is_g() {
let g1 = JacobianPoint::scalar_mul_g(&U256::ONE).to_affine().unwrap();
assert_eq!(fp_to_bytes(&g1.x), fp_to_bytes(&GX));
assert_eq!(fp_to_bytes(&g1.y), fp_to_bytes(&GY));
}
#[test]
fn test_serialization_roundtrip() {
let g = AffinePoint::generator();
let bytes = g.to_bytes();
assert_eq!(bytes[0], 0x04);
let g2 = AffinePoint::from_bytes(&bytes).unwrap();
assert_eq!(fp_to_bytes(&g.x), fp_to_bytes(&g2.x));
assert_eq!(fp_to_bytes(&g.y), fp_to_bytes(&g2.y));
}
#[test]
fn test_keypair_on_curve() {
// 测试私钥 → 公钥在曲线上
let k_hex = "f927525e176ae5607c628bc508ec0465ef285b74415bf876130a8a5d004c789e";
let k_bytes: [u8; 32] = {
let mut b = [0u8; 32];
for (i, chunk) in k_hex.as_bytes().chunks(2).enumerate() {
b[i] = u8::from_str_radix(core::str::from_utf8(chunk).unwrap(), 16).unwrap();
}
b
};
let k = U256::from_be_slice(&k_bytes);
let pub_aff = JacobianPoint::scalar_mul_g(&k).to_affine().unwrap();
assert!(pub_aff.is_on_curve());
// 验证 y² = x³ + ax + b
let x2 = fp_square(&pub_aff.x);
let x3 = fp_mul(&x2, &pub_aff.x);
let ax = fp_mul(&CURVE_A, &pub_aff.x);
let rhs = fp_add(&fp_add(&x3, &ax), &CURVE_B);
assert_eq!(rhs, fp_square(&pub_aff.y));
}
/// 验证完备加法公式的退化情况(常量时间 add 的正确性)
#[test]
fn test_add_degenerate_cases() {
let g = JacobianPoint::from_affine(&AffinePoint::generator());
let inf = JacobianPoint::INFINITY;
// ∞ + G = G
let r = JacobianPoint::add(&inf, &g).to_affine().unwrap();
assert_eq!(fp_to_bytes(&r.x), fp_to_bytes(&GX), "∞ + G 的 x 坐标错误");
assert_eq!(fp_to_bytes(&r.y), fp_to_bytes(&GY), "∞ + G 的 y 坐标错误");
// G + ∞ = G
let r = JacobianPoint::add(&g, &inf).to_affine().unwrap();
assert_eq!(fp_to_bytes(&r.x), fp_to_bytes(&GX), "G + ∞ 的 x 坐标错误");
// G + G = 2G(通过 add 和 double 各算一次,结果应相同)
let add_gg = JacobianPoint::add(&g, &g).to_affine().unwrap();
let double_g = g.double().to_affine().unwrap();
assert_eq!(
fp_to_bytes(&add_gg.x),
fp_to_bytes(&double_g.x),
"add(G,G) != double(G) 的 x 坐标"
);
assert_eq!(
fp_to_bytes(&add_gg.y),
fp_to_bytes(&double_g.y),
"add(G,G) != double(G) 的 y 坐标"
);
// G + (-G) = ∞(互反点,y 取负)
let g_neg = JacobianPoint {
x: g.x,
y: fp_neg(&g.y),
z: g.z,
};
assert!(
JacobianPoint::add(&g, &g_neg).is_infinity(),
"G + (-G) 应为无穷远点"
);
}
}
+50
View File
@@ -0,0 +1,50 @@
//! SM2 错误类型
//!
//! SM2 Error types used by all public APIs.
use core::fmt;
/// SM2 操作的统一错误类型
///
/// Unified error type for all SM2 operations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
/// 私钥不合法(d ∉ [1, n-2]/ Invalid private key (d ∉ [1, n-2])
InvalidPrivateKey,
/// 公钥不合法(格式错误或不在曲线上)/ Invalid public key (bad format or not on curve)
InvalidPublicKey,
/// 签名格式不合法 / Invalid signature format
InvalidSignature,
/// 验签失败 / Signature verification failed
VerifyFailed,
/// 解密失败(C3 校验不通过)/ Decryption failed (C3 check failed)
DecryptFailed,
/// 点在无穷远处 / Point at infinity
PointAtInfinity,
/// 输入长度不合法 / Invalid input length
InvalidInputLength,
/// 密钥交换失败 / Key exchange failed
KeyExchangeFailed,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidPrivateKey => f.write_str("SM2: invalid private key"),
Error::InvalidPublicKey => f.write_str("SM2: invalid public key"),
Error::InvalidSignature => f.write_str("SM2: invalid signature format"),
Error::VerifyFailed => f.write_str("SM2: signature verification failed"),
Error::DecryptFailed => f.write_str("SM2: decryption failed"),
Error::PointAtInfinity => f.write_str("SM2: point at infinity"),
Error::InvalidInputLength => f.write_str("SM2: invalid input length"),
Error::KeyExchangeFailed => f.write_str("SM2: key exchange failed"),
}
}
}
/// Bridge to `signature::Error` for `Signer`/`Verifier` trait impls.
impl From<Error> for signature::Error {
fn from(_: Error) -> Self {
signature::Error::new()
}
}
+251
View File
@@ -0,0 +1,251 @@
//! SM2 sm2p256v1 素域 Fp 与标量域 Fn
//!
//! 曲线参数来自 GB/T 32918.1-2016 附录 A。
//! 所有算术通过 `crypto-bigint` 的 `ConstMontyForm` 实现,常量时间。
use crypto_bigint::{impl_modulus, modular::ConstMontyForm, U256};
// ── 模数定义 ──────────────────────────────────────────────────────────────────
// SM2 素数域模数 p
// p = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
impl_modulus!(
Sm2FieldModulus,
U256,
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"
);
// SM2 群阶 n
// n = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
impl_modulus!(
Sm2GroupOrder,
U256,
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"
);
/// SM2 素域元素(基于 Montgomery 形式的常量时间运算)
pub type Fp = ConstMontyForm<Sm2FieldModulus, { U256::LIMBS }>;
/// SM2 标量域元素(群阶 n 上的模运算)
pub type Fn = ConstMontyForm<Sm2GroupOrder, { U256::LIMBS }>;
// ── 曲线参数常量(GB/T 32918.1-2016 附录 A)─────────────────────────────────
/// 曲线系数 a = p - 3
pub const CURVE_A: Fp = Fp::new(&U256::from_be_hex(
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
));
/// 曲线系数 b
pub const CURVE_B: Fp = Fp::new(&U256::from_be_hex(
"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
));
/// 基点 G 的 x 坐标
pub const GX: Fp = Fp::new(&U256::from_be_hex(
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
));
/// 基点 G 的 y 坐标
pub const GY: Fp = Fp::new(&U256::from_be_hex(
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0",
));
/// 域模数 p(用于坐标范围检查)
pub const FIELD_MODULUS: U256 =
U256::from_be_hex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF");
/// 群阶 n(用于标量范围检查)
pub const GROUP_ORDER: U256 =
U256::from_be_hex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123");
/// 群阶 n - 1(私钥合法性检查:d ∈ [1, n-2] → d < n-1
pub const GROUP_ORDER_MINUS_1: U256 =
U256::from_be_hex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54122");
// ── Fp 工具函数 ───────────────────────────────────────────────────────────────
/// 从大端字节构造 Fp(调用方保证 bytes 表示的值 < p
#[inline]
pub fn fp_from_bytes(bytes: &[u8; 32]) -> Fp {
Fp::new(&U256::from_be_slice(bytes))
}
/// 将 Fp 元素转为大端字节
#[inline]
pub fn fp_to_bytes(a: &Fp) -> [u8; 32] {
a.retrieve().to_be_bytes()
}
/// 从大端字节构造 Fn(标量,调用方保证值 < n)
#[inline]
pub fn fn_from_bytes(bytes: &[u8; 32]) -> Fn {
Fn::new(&U256::from_be_slice(bytes))
}
/// 将 Fn 元素转为大端字节
#[inline]
pub fn fn_to_bytes(a: &Fn) -> [u8; 32] {
a.retrieve().to_be_bytes()
}
/// Fp 加法(模 p
#[inline]
pub fn fp_add(a: &Fp, b: &Fp) -> Fp {
a.add(b)
}
/// Fp 减法(模 p
#[inline]
pub fn fp_sub(a: &Fp, b: &Fp) -> Fp {
a.sub(b)
}
/// Fp 乘法(Montgomery 乘,常量时间)
#[inline]
pub fn fp_mul(a: &Fp, b: &Fp) -> Fp {
a.mul(b)
}
/// Fp 取负(模 p
#[inline]
pub fn fp_neg(a: &Fp) -> Fp {
a.neg()
}
/// Fp 平方(常量时间)
#[inline]
pub fn fp_square(a: &Fp) -> Fp {
a.square()
}
/// Fp 求逆(Bernstein-Yang 算法,常量时间)
/// 返回 None 当且仅当 a == 0
pub fn fp_inv(a: &Fp) -> Option<Fp> {
let inv = a.inv();
// CtOption 转换为 Option
if bool::from(inv.is_some()) {
// Reason: ConstantTimeEq 保证此 unwrap 不可能 panicis_some 为真)
Some(inv.unwrap())
} else {
None
}
}
/// Fp 平方根(用于点解压缩)
///
/// SM2 素数 p ≡ 3 (mod 4),故 sqrt(a) = a^((p+1)/4) mod p。
/// 若结果的平方 ≠ a,则 a 不是二次剩余,返回 None。
pub fn fp_sqrt(a: &Fp) -> Option<Fp> {
// (p+1)/4 = 3FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFC000000040000000000000000
// p = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 00000000 FFFFFFFFFFFFFFFF
// p+1 = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 00000001 0000000000000000
// /4 (右移2位) = 3FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFC0000000 40000000 00000000
let exp = U256::from_be_hex("3FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC00000004000000000000000");
let candidate = a.pow(&exp);
// 验证 candidate^2 == a(常量时间比较,ConstMontyForm 的 PartialEq 是常量时间)
if candidate.square() == *a {
Some(candidate)
} else {
None
}
}
/// Fn 加法(模 n
#[inline]
pub fn fn_add(a: &Fn, b: &Fn) -> Fn {
a.add(b)
}
/// Fn 减法(模 n
#[inline]
pub fn fn_sub(a: &Fn, b: &Fn) -> Fn {
a.sub(b)
}
/// Fn 乘法(模 nMontgomery 形式)
#[inline]
pub fn fn_mul(a: &Fn, b: &Fn) -> Fn {
a.mul(b)
}
/// Fn 取负(模 n
#[inline]
pub fn fn_neg(a: &Fn) -> Fn {
a.neg()
}
/// Fn 求逆(Bernstein-Yang 算法,常量时间)
/// 返回 None 当且仅当 a == 0
pub fn fn_inv(a: &Fn) -> Option<Fn> {
let inv = a.inv();
if bool::from(inv.is_some()) {
Some(inv.unwrap())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fp_add_sub_symmetric() {
let a = fp_from_bytes(&[
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
]);
let b = fp_from_bytes(&[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02,
]);
let sum = fp_add(&a, &b);
let diff = fp_sub(&sum, &b);
assert_eq!(fp_to_bytes(&diff), fp_to_bytes(&a));
}
#[test]
fn test_fp_mul_by_one() {
let gx = GX;
let result = fp_mul(&gx, &Fp::ONE);
assert_eq!(fp_to_bytes(&result), fp_to_bytes(&gx));
}
#[test]
fn test_fp_inv_roundtrip() {
let two = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2,
]);
let inv = fp_inv(&two).expect("2 的逆元应存在");
assert_eq!(fp_mul(&two, &inv), Fp::ONE);
}
#[test]
fn test_fp_zero_has_no_inv() {
assert!(fp_inv(&Fp::ZERO).is_none());
}
#[test]
fn test_fp_sqrt_of_four() {
let four = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4,
]);
let root = fp_sqrt(&four).expect("4 应有平方根");
assert_eq!(fp_square(&root), four);
}
#[test]
fn test_fn_inv_roundtrip() {
let three = fn_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3,
]);
let inv = fn_inv(&three).expect("3^-1 应存在");
assert_eq!(fn_mul(&three, &inv), Fn::ONE);
}
}
+67
View File
@@ -0,0 +1,67 @@
//! SM2 KDF 密钥派生函数(GB/T 32918.4-2016 §5.4.3
//!
//! KDF(Z, klen) = ‖_{i=1}^{⌈klen/32⌉} SM3(Z ‖ CT_i)
//! 其中 CT_i 为 32-bit 大端计数器,从 1 开始。
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
// Reason: sm3 sub-crate 只导出 Digest API,通过内联包装器使用
fn sm3_hash(parts: &[&[u8]]) -> [u8; 32] {
use sm3::Digest;
let mut h = sm3::Sm3::new();
for part in parts {
h.update(part);
}
h.finalize().into()
}
/// SM2/SM9 KDF 密钥派生函数
///
/// # 参数
/// - `z`: 输入密钥材料(共享点坐标等)
/// - `klen`: 期望输出字节数
///
/// # 返回
/// 长度为 `klen` 的派生密钥字节序列(`alloc` feature 下可用)
#[cfg(feature = "alloc")]
pub fn kdf(z: &[u8], klen: usize) -> Vec<u8> {
let mut result = Vec::with_capacity(klen + 32);
let mut counter: u32 = 1;
while result.len() < klen {
// 每轮: SM3(Z || CT_i)
result.extend_from_slice(&sm3_hash(&[z, &counter.to_be_bytes()]));
counter += 1;
}
result.truncate(klen);
result
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::*;
#[test]
fn test_kdf_length() {
let z = b"test input";
assert_eq!(kdf(z, 32).len(), 32);
assert_eq!(kdf(z, 48).len(), 48);
assert_eq!(kdf(z, 1).len(), 1);
}
#[test]
fn test_kdf_deterministic() {
let z = b"shared secret";
assert_eq!(kdf(z, 32), kdf(z, 32));
}
#[test]
fn test_kdf_different_lengths() {
// 64 字节输出应与两次 32 字节拼接一致(即第一块完全相同)
let z = b"input";
let k32 = kdf(z, 32);
let k64 = kdf(z, 64);
assert_eq!(&k64[..32], &k32[..]);
}
}
+628
View File
@@ -0,0 +1,628 @@
//! 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::ec::{AffinePoint, JacobianPoint};
use crate::field::{fn_add, fn_mul, fp_to_bytes, Fn, GROUP_ORDER_MINUS_1};
use crate::get_z;
use sm3::Digest as _;
// Reason: sm3 sub-crate 只导出 Digest API;通过此包装器实现流式哈希
struct Sm3H(sm3::Sm3);
impl Sm3H {
fn new() -> Self { Sm3H(sm3::Sm3::new()) }
fn update(&mut self, data: &[u8]) { self.0.update(data); }
fn finalize(self) -> [u8; 32] { self.0.finalize().into() }
}
// ── 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::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::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::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::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::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::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 = Sm3H::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 = Sm3H::new();
h.update(&[0x02]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// SA = SM3(0x03 || yV || hash_v)
let sa = {
let mut h = Sm3H::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::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);
}
}
+796
View File
@@ -0,0 +1,796 @@
//! SM2 椭圆曲线公钥密码算法(GB/T 32918.1-5-2016
//!
//! 本 crate 提供符合 GB/T 32918-2016 的纯 Rust、`no_std` 实现:
//!
//! - **密钥生成**[`generate_keypair`]
//! - **数字签名/验签**[`SigningKey`] / [`VerifyingKey`](实现 `signature::Signer/Verifier`
//! - **公钥加密/解密**[`encrypt`] / [`decrypt`](需 `alloc` feature
//! - **密钥交换**[`key_exchange::ecdh`] / [`key_exchange::exchange_a`]
//! - **DER 编解码**[`der`]
//!
//! ## 安全性声明
//!
//! - 所有私钥操作均为常量时间(Montgomery 域算术 + `subtle::ConditionallySelectable`
//! - 私钥离开作用域后自动清零([`ZeroizeOnDrop`]
//! - 标量乘法固定迭代 256 位,不跳过前导零
//! - `sign_with_k` 危险接口需启用 `hazmat` feature
//!
//! ## 快速开始
//!
//! ```rust
//! use sm2::{SigningKey, VerifyingKey, DEFAULT_ID};
//! use sm2::signature::{Signer, Verifier};
//! use rand_core::OsRng;
//!
//! // 生成密钥对
//! let (pri, pub_bytes) = sm2::generate_keypair(&mut OsRng);
//! let signing = SigningKey::new(pri, DEFAULT_ID);
//! let verifying = VerifyingKey::new(pub_bytes, DEFAULT_ID);
//!
//! // 签名
//! let msg = b"hello SM2";
//! let sig = signing.sign(msg);
//!
//! // 验签
//! verifying.verify(msg, &sig).expect("验签应通过");
//! ```
//!
//! ---
//!
//! SM2 elliptic curve public-key cryptography (GB/T 32918.1-5-2016).
//!
//! This crate provides a pure-Rust, `no_std` implementation of:
//!
//! - **Key generation**: [`generate_keypair`]
//! - **Signing / Verification**: [`SigningKey`] / [`VerifyingKey`]
//! (implement `signature::Signer` / `Verifier`)
//! - **Public-key encryption / decryption**: [`encrypt`] / [`decrypt`]
//! (requires `alloc` feature)
//! - **Key exchange**: [`key_exchange::ecdh`] / [`key_exchange::exchange_a`]
//! - **DER encoding / decoding**: [`der`]
//!
//! ## Security
//!
//! - All secret-dependent operations are constant-time
//! - Private keys are zeroized on drop ([`ZeroizeOnDrop`])
//! - Scalar multiplication iterates all 256 bits regardless of scalar value
//! - `sign_with_k` (dangerous raw-k API) requires the `hazmat` feature
#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#[cfg(feature = "alloc")]
extern crate alloc;
pub mod der;
pub mod ec;
pub mod error;
pub mod field;
pub mod kdf;
pub mod key_exchange;
mod rfc6979;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub use error::Error;
pub use signature;
use crate::ec::{multi_scalar_mul, AffinePoint, JacobianPoint};
use crate::field::{
fn_add, fn_inv, fn_mul, fn_sub, fp_to_bytes, Fn, CURVE_A, CURVE_B, GROUP_ORDER,
GROUP_ORDER_MINUS_1, GX, GY,
};
// ── 内部 SM3 包装 ─────────────────────────────────────────────────────────────
/// 内部轻量哈希上下文(包装 sm3::Sm3 的 Digest API
///
/// Thin wrapper around `sm3::Sm3` exposing a streaming `update`/`finalize` API
/// identical to the original `Sm3Hasher`, so callers need no refactoring.
struct Sm3H(sm3::Sm3);
impl Sm3H {
fn new() -> Self {
use sm3::Digest;
Sm3H(sm3::Sm3::new())
}
fn update(&mut self, data: &[u8]) {
use sm3::Digest;
self.0.update(data);
}
fn finalize(self) -> [u8; 32] {
use sm3::Digest;
self.0.finalize().into()
}
}
// ── 常量 ──────────────────────────────────────────────────────────────────────
/// SM2 默认用户可辨别标识(GB/T 32918.2-2016 §A.2 示例值)
///
/// Default user distinguishable identifier (example from GB/T 32918.2-2016 §A.2).
pub const DEFAULT_ID: &[u8] = b"1234567812345678";
// ── 私钥类型 ──────────────────────────────────────────────────────────────────
/// SM2 私钥(32 字节,离开作用域自动清零)
///
/// SM2 private key (32 bytes). Automatically zeroized on drop.
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct PrivateKey {
bytes: [u8; 32],
}
impl PrivateKey {
/// 从字节构造私钥(验证 d ∈ [1, n-2])
///
/// Construct from bytes, validating d ∈ [1, n-2].
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, Error> {
let d = U256::from_be_slice(bytes);
if bool::from(d.is_zero()) || d >= GROUP_ORDER_MINUS_1 {
return Err(Error::InvalidPrivateKey);
}
Ok(PrivateKey { bytes: *bytes })
}
/// 以字节引用访问私钥
///
/// Access the private key bytes by reference.
pub fn as_bytes(&self) -> &[u8; 32] {
&self.bytes
}
/// 计算对应公钥(65 字节,04||x||y
///
/// Derive the corresponding public key (65 bytes, uncompressed: 04||x||y).
pub fn public_key(&self) -> [u8; 65] {
let d = U256::from_be_slice(&self.bytes);
let pub_jac = JacobianPoint::scalar_mul_g(&d);
// Reason: 私钥合法性已在构造时验证,scalar_mul_g 结果不会是无穷远点
let pub_aff = pub_jac
.to_affine()
.expect("valid private key produces valid public key");
pub_aff.to_bytes()
}
}
// ── 密钥生成 ──────────────────────────────────────────────────────────────────
/// 生成 SM2 密钥对(私钥 + 公钥 65 字节)
///
/// Generate a SM2 key pair (private key + 65-byte public key).
/// Conforms to GB/T 32918.1-2016 §6.1.
pub fn generate_keypair<R: RngCore>(rng: &mut R) -> (PrivateKey, [u8; 65]) {
loop {
let mut d_bytes = [0u8; 32];
rng.fill_bytes(&mut d_bytes);
let d = U256::from_be_slice(&d_bytes);
if bool::from(d.is_zero()) || d >= GROUP_ORDER_MINUS_1 {
d_bytes.zeroize();
continue;
}
// Reason: 私钥满足范围约束,不会失败
let priv_key = PrivateKey { bytes: d_bytes };
let pub_key = priv_key.public_key();
return (priv_key, pub_key);
}
}
// ── Z 值计算(GB/T 32918.2-2016 §5.5)────────────────────────────────────────
/// 计算用户标识的 Z 值
///
/// Z = SM3(ENTL || ID || a || b || Gx || Gy || Px || Py)
///
/// Compute Z-value for the user identity. Conforms to GB/T 32918.2-2016 §5.5.
pub fn get_z(id: &[u8], pub_key: &[u8; 65]) -> [u8; 32] {
let entl = (id.len() * 8) as u16;
let mut h = Sm3H::new();
h.update(&entl.to_be_bytes());
h.update(id);
h.update(&fp_to_bytes(&CURVE_A));
h.update(&fp_to_bytes(&CURVE_B));
h.update(&fp_to_bytes(&GX));
h.update(&fp_to_bytes(&GY));
h.update(&pub_key[1..33]); // Px
h.update(&pub_key[33..65]); // Py
h.finalize()
}
/// 计算消息摘要 e = SM3(Z || M)
///
/// Compute message digest e = SM3(Z || M). Conforms to GB/T 32918.2-2016 §5.5.
pub fn get_e(z: &[u8; 32], msg: &[u8]) -> [u8; 32] {
let mut h = Sm3H::new();
h.update(z);
h.update(msg);
h.finalize()
}
// ── 数字签名(GB/T 32918.2-2016 §6.2)───────────────────────────────────────
/// SM2 签名(使用指定随机数 k)
///
/// Sign using a specified nonce k. **Only expose under `hazmat` feature — misusing k leaks the private key.**
///
/// Sign with a fixed nonce k (for test vectors / hazmat use only).
/// Requires the `hazmat` feature gate.
#[cfg(feature = "hazmat")]
pub fn sign_with_k(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8; 64], Error> {
sign_with_k_inner(e, pri_key, k)
}
/// 内部签名实现(供 `sign` 和 `hazmat::sign_with_k` 共用)
fn sign_with_k_inner(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8; 64], Error> {
let d = U256::from_be_slice(pri_key.as_bytes());
let kg_aff = JacobianPoint::scalar_mul_g(k)
.to_affine()
.map_err(|_| Error::InvalidSignature)?;
let x1 = fp_to_bytes(&kg_aff.x);
let e_val = U256::from_be_slice(e);
let x1_val = U256::from_be_slice(&x1);
let r_fn = fn_add(&Fn::new(&e_val), &Fn::new(&x1_val));
let r = r_fn.retrieve();
if bool::from(r.is_zero()) {
return Err(Error::InvalidSignature);
}
if fn_add(&r_fn, &Fn::new(k)).retrieve().is_zero().into() {
return Err(Error::InvalidSignature);
}
let d_fn = Fn::new(&d);
let one_plus_d = fn_add(&Fn::ONE, &d_fn);
let inv = fn_inv(&one_plus_d).ok_or(Error::InvalidPrivateKey)?;
let rd = fn_mul(&r_fn, &d_fn);
let s_fn = fn_mul(&inv, &fn_sub(&Fn::new(k), &rd));
let s = s_fn.retrieve();
if bool::from(s.is_zero()) {
return Err(Error::InvalidSignature);
}
let mut sig = [0u8; 64];
sig[..32].copy_from_slice(&r.to_be_bytes());
sig[32..].copy_from_slice(&s.to_be_bytes());
Ok(sig)
}
/// SM2 签名(随机 k,标准接口)
///
/// Sign with random nonce k. Accepts pre-computed digest `e = SM3(Z||M)`.
pub fn sign<R: RngCore>(e: &[u8; 32], pri_key: &PrivateKey, rng: &mut R) -> [u8; 64] {
loop {
let mut k_bytes = [0u8; 32];
rng.fill_bytes(&mut k_bytes);
let k = U256::from_be_slice(&k_bytes);
k_bytes.zeroize();
if bool::from(k.is_zero()) || k >= GROUP_ORDER {
continue;
}
if let Ok(sig) = sign_with_k_inner(e, pri_key, &k) {
return sig;
}
}
}
/// SM2 确定性签名(RFC 6979,使用 HMAC-SM3 生成 k
///
/// Sign with deterministic nonce k derived via RFC 6979.
/// Accepts pre-computed digest `e = SM3(Z||M)`.
///
/// # 安全关键点
///
/// 此函数不依赖外部 RNG,消除了 RNG 故障或偏差导致私钥泄露的风险。
/// 对于相同的 (私钥, 消息摘要) 对,签名结果完全确定。
pub fn sign_deterministic(e: &[u8; 32], pri_key: &PrivateKey) -> [u8; 64] {
// Reason: RFC 6979 保证生成的 k 总是满足 0 < k < n
// 并且对于合法私钥,sign_with_k_inner 总会成功(极罕见的 r=0/s=0 情况由 RFC 6979 循环避免)。
// 如果 sign_with_k_inner 失败(理论上极罕见),我们使用不同输入再试。
let k = rfc6979::generate_k(pri_key.as_bytes(), e);
// RFC 6979 生成的 k 在几乎所有情况下都有效,直接调用
if let Ok(sig) = sign_with_k_inner(e, pri_key, &k) {
return sig;
}
// 极罕见的 fallback:用 e+pri_key 的不同组合再生成一个 k
// (实际上 RFC 6979 的循环设计保证不会到这里)
let mut alt_input = [0u8; 32];
for (i, (&a, &b)) in e.iter().zip(pri_key.as_bytes().iter()).enumerate() {
alt_input[i] = a.wrapping_add(b).wrapping_add(1);
}
let k2 = rfc6979::generate_k(pri_key.as_bytes(), &alt_input);
sign_with_k_inner(e, pri_key, &k2).expect("RFC 6979 fallback must succeed")
}
/// SM2 签名(便捷接口,自动计算 Z 值与消息摘要)
///
/// Convenience signing: auto-computes Z = SM3(ENTL||ID||...) and e = SM3(Z||M).
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 确定性签名便捷接口(RFC 6979,无需 RNG
///
/// Convenience deterministic signing: auto-computes Z and e, then uses RFC 6979.
pub fn sign_message_deterministic(msg: &[u8], id: &[u8], pri_key: &PrivateKey) -> [u8; 64] {
let pub_key = pri_key.public_key();
let z = get_z(id, &pub_key);
let e = get_e(&z, msg);
sign_deterministic(&e, pri_key)
}
// ── 签名验证(GB/T 32918.2-2016 §6.3)───────────────────────────────────────
/// SM2 验签
///
/// Verify a SM2 signature. Accepts pre-computed digest `e = SM3(Z||M)`.
pub fn verify(e: &[u8; 32], pub_key: &[u8; 65], sig: &[u8; 64]) -> Result<(), Error> {
let r = U256::from_be_slice(&sig[..32]);
let s = U256::from_be_slice(&sig[32..]);
let n = GROUP_ORDER;
if bool::from(r.is_zero()) || r >= n || bool::from(s.is_zero()) || s >= n {
return Err(Error::InvalidSignature);
}
let t_fn = fn_add(&Fn::new(&r), &Fn::new(&s));
let t = t_fn.retrieve();
if bool::from(t.is_zero()) {
return Err(Error::VerifyFailed);
}
let pa = AffinePoint::from_bytes(pub_key)?;
let point = multi_scalar_mul(&s, &t, &pa)?;
let e_val = U256::from_be_slice(e);
let px_val = U256::from_be_slice(&fp_to_bytes(&point.x));
let r_check = fn_add(&Fn::new(&e_val), &Fn::new(&px_val)).retrieve();
// Reason: 常量时间比较,防时序侧信道
if r.to_be_bytes().ct_eq(&r_check.to_be_bytes()).unwrap_u8() != 1 {
return Err(Error::VerifyFailed);
}
Ok(())
}
/// SM2 验签(便捷接口,自动计算 Z 值与消息要)
///
/// Convenience verification: auto-computes Z and e.
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)
}
// ── 公钥加密(GB/T 32918.4-2016 §7.1)──────────────────────────────────────
/// SM2 公钥加密
///
/// SM2 public-key encryption. Output format: C1||C3||C2 (GB/T 32918.4-2016 §6.1).
/// Requires `alloc` feature.
#[cfg(feature = "alloc")]
pub fn encrypt<R: RngCore>(
pub_key: &[u8; 65],
message: &[u8],
rng: &mut R,
) -> Result<Vec<u8>, Error> {
let pa = AffinePoint::from_bytes(pub_key)?;
loop {
let mut k_bytes = [0u8; 32];
rng.fill_bytes(&mut k_bytes);
let k = U256::from_be_slice(&k_bytes);
k_bytes.zeroize();
if bool::from(k.is_zero()) || k >= GROUP_ORDER {
continue;
}
let c1_aff = match JacobianPoint::scalar_mul_g(&k).to_affine() {
Ok(p) => p,
Err(_) => continue,
};
let c1 = c1_aff.to_bytes();
let pa_jac = JacobianPoint::from_affine(&pa);
let kpa_aff = match JacobianPoint::scalar_mul(&k, &pa_jac).to_affine() {
Ok(p) => p,
Err(_) => continue,
};
let x2 = fp_to_bytes(&kpa_aff.x);
let y2 = fp_to_bytes(&kpa_aff.y);
let mut z_input = [0u8; 64];
z_input[..32].copy_from_slice(&x2);
z_input[32..].copy_from_slice(&y2);
let t = kdf::kdf(&z_input, message.len());
if t.iter().all(|&b| b == 0) {
continue;
}
let c2: Vec<u8> = message.iter().zip(t.iter()).map(|(&m, &k)| m ^ k).collect();
let mut h = Sm3H::new();
h.update(&x2);
h.update(message);
h.update(&y2);
let c3 = h.finalize();
let mut output = Vec::with_capacity(65 + 32 + message.len());
output.extend_from_slice(&c1);
output.extend_from_slice(&c3);
output.extend_from_slice(&c2);
return Ok(output);
}
}
// ── 公钥解密(GB/T 32918.4-2016 §7.2)──────────────────────────────────────
/// SM2 公钥解密(新格式 C1||C3||C2
///
/// SM2 public-key decryption (format C1||C3||C2). Requires `alloc` feature.
#[cfg(feature = "alloc")]
pub fn decrypt(pri_key: &PrivateKey, ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
if ciphertext.len() < 97 {
return Err(Error::InvalidInputLength);
}
let d = U256::from_be_slice(pri_key.as_bytes());
let c1_bytes: [u8; 65] = ciphertext[0..65].try_into().unwrap();
let c1 = AffinePoint::from_bytes(&c1_bytes)?;
let c3_expected: [u8; 32] = ciphertext[65..97].try_into().unwrap();
let c2 = &ciphertext[97..];
let c1_jac = JacobianPoint::from_affine(&c1);
let dc1_aff = JacobianPoint::scalar_mul(&d, &c1_jac).to_affine()?;
let x2 = fp_to_bytes(&dc1_aff.x);
let y2 = fp_to_bytes(&dc1_aff.y);
let mut z_input = [0u8; 64];
z_input[..32].copy_from_slice(&x2);
z_input[32..].copy_from_slice(&y2);
let t = kdf::kdf(&z_input, c2.len());
if t.iter().all(|&b| b == 0) {
return Err(Error::DecryptFailed);
}
let m: Vec<u8> = c2.iter().zip(t.iter()).map(|(&c, &k)| c ^ k).collect();
let mut h = Sm3H::new();
h.update(&x2);
h.update(&m);
h.update(&y2);
let c3_computed = h.finalize();
// Reason: 先验证 C3 再返回明文,防止 chosen-ciphertext 攻击
if c3_expected.ct_eq(&c3_computed).unwrap_u8() != 1 {
return Err(Error::DecryptFailed);
}
Ok(m)
}
// ── signature::Signer / Verifier trait 实现 ──────────────────────────────────
/// SM2 签名结果(r||s64 字节)
///
/// SM2 signature (r||s, 64 bytes).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Sm2Signature {
bytes: [u8; 64],
}
impl Sm2Signature {
/// 从 64 字节原始 r||s 构造签名
///
/// Construct from raw 64-byte r||s.
pub fn from_bytes(bytes: [u8; 64]) -> Self {
Sm2Signature { bytes }
}
/// 以字节切片返回签名
///
/// Return the signature as a byte slice.
pub fn as_bytes(&self) -> &[u8; 64] {
&self.bytes
}
}
impl AsRef<[u8]> for Sm2Signature {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
// ── SigningKey ────────────────────────────────────────────────────────────────
/// SM2 签名密钥(私钥 + 用户标识)
///
/// SM2 signing key: wraps a private key with a user identity string.
/// Implements [`signature::Signer<Sm2Signature>`].
pub struct SigningKey<'id> {
private_key: PrivateKey,
/// 用户可辨别标识 / User distinguishable identifier
pub id: &'id [u8],
}
impl<'id> SigningKey<'id> {
/// 构造签名密钥
///
/// Construct a signing key from a private key and user ID.
pub fn new(private_key: PrivateKey, id: &'id [u8]) -> Self {
SigningKey { private_key, id }
}
/// 获取对应的公钥字节(65 字节,04||x||y
///
/// Derive the corresponding public key bytes (65 bytes, uncompressed).
pub fn public_key_bytes(&self) -> [u8; 65] {
self.private_key.public_key()
}
}
impl<'id> signature::Signer<Sm2Signature> for SigningKey<'id> {
fn try_sign(&self, msg: &[u8]) -> Result<Sm2Signature, signature::Error> {
// Reason: sign_message 需要 RngCore;此处用 OsRng 退化实现
// 在 no_std 环境中,若无 OsRng 可用,调用方应直接调用 sign/sign_message
use rand_core::OsRng;
let sig_bytes = sign_message(msg, self.id, &self.private_key, &mut OsRng);
Ok(Sm2Signature { bytes: sig_bytes })
}
}
// ── VerifyingKey ──────────────────────────────────────────────────────────────
/// SM2 验证密钥(公钥 + 用户标识)
///
/// SM2 verifying key: wraps a public key with a user identity string.
/// Implements [`signature::Verifier<Sm2Signature>`].
pub struct VerifyingKey<'id> {
public_key: [u8; 65],
/// 用户可辨别标识 / User distinguishable identifier
pub id: &'id [u8],
}
impl<'id> VerifyingKey<'id> {
/// 构造验证密钥
///
/// Construct a verifying key from a public key and user ID.
pub fn new(public_key: [u8; 65], id: &'id [u8]) -> Self {
VerifyingKey { public_key, id }
}
/// 验证公钥是否在 SM2 曲线上
///
/// Returns `Ok(())` if the public key is a valid SM2 curve point.
pub fn validate(&self) -> Result<(), Error> {
AffinePoint::from_bytes(&self.public_key).map(|_| ())
}
}
impl<'id> signature::Verifier<Sm2Signature> for VerifyingKey<'id> {
fn verify(&self, msg: &[u8], signature: &Sm2Signature) -> Result<(), signature::Error> {
verify_message(msg, self.id, &self.public_key, &signature.bytes)
.map_err(signature::Error::from)
}
}
// ── 单元测试 ──────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
// 共用测试私钥(来自 libsmx 内部测试)
const 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,
];
const K_BYTES: [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,
];
// 测试专用的确定性 RNG(使用固定字节池)
struct FakeRng([u8; 32]);
impl rand_core::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_private_key_from_bytes_valid() {
PrivateKey::from_bytes(&D_BYTES).expect("合法私钥应成功构造");
}
#[test]
fn test_private_key_from_bytes_zero() {
assert!(PrivateKey::from_bytes(&[0u8; 32]).is_err(), "全零私钥应拒绝");
}
#[test]
fn test_public_key_on_curve() {
let pri = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_bytes = pri.public_key();
let point = AffinePoint::from_bytes(&pub_bytes).expect("公钥应在曲线上");
assert!(point.is_on_curve());
}
#[test]
fn test_get_z_deterministic() {
let pub_key = [0x04u8; 65];
let z1 = get_z(DEFAULT_ID, &pub_key);
let z2 = get_z(DEFAULT_ID, &pub_key);
assert_eq!(z1, z2);
}
#[test]
fn test_sign_verify_roundtrip() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let msg = b"hello sm2";
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, msg);
let k = U256::from_be_slice(&K_BYTES);
let sig = sign_with_k_inner(&e, &pri_key, &k).expect("签名应成功");
verify(&e, &pub_key, &sig).expect("验签应通过");
}
#[test]
fn test_sign_message_verify_message() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng(K_BYTES);
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_rejects_tampered_sig() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let msg = b"hello sm2";
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, msg);
let k = U256::from_be_slice(&K_BYTES);
let mut sig = sign_with_k_inner(&e, &pri_key, &k).unwrap();
sig[0] ^= 0x01;
assert!(verify(&e, &pub_key, &sig).is_err());
}
#[test]
fn test_verify_rejects_wrong_id() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng(K_BYTES);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
assert!(verify_message(msg, b"wrong-id", &pub_key, &sig).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_encrypt_decrypt_roundtrip() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let msg = b"Hello, SM2 encryption!";
let mut rng = FakeRng(K_BYTES);
let ciphertext = encrypt(&pub_key, msg, &mut rng).expect("加密应成功");
let plaintext = decrypt(&pri_key, &ciphertext).expect("解密应成功");
assert_eq!(plaintext, msg);
}
#[cfg(feature = "alloc")]
#[test]
fn test_decrypt_rejects_tampered_ciphertext() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng(K_BYTES);
let mut ct = encrypt(&pub_key, b"test", &mut rng).unwrap();
ct[70] ^= 0xFF;
assert!(decrypt(&pri_key, &ct).is_err());
}
// ── signature trait 测试 ────────────────────────────────────────────────
#[test]
fn test_signing_key_verifying_key_roundtrip() {
use signature::{Signer, Verifier};
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_bytes = pri_key.public_key();
let signing = SigningKey::new(pri_key, DEFAULT_ID);
let verifying = VerifyingKey::new(pub_bytes, DEFAULT_ID);
let msg = b"signature trait roundtrip";
let sig = signing.sign(msg);
verifying.verify(msg, &sig).expect("SigningKey/VerifyingKey 验签应通过");
}
#[test]
fn test_verifying_key_validate() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_bytes = pri_key.public_key();
let vk = VerifyingKey::new(pub_bytes, DEFAULT_ID);
assert!(vk.validate().is_ok());
}
/// RFC 6979 确定性签名:结果可被 verify 通过
#[test]
fn test_sign_deterministic_verify() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let msg = b"RFC 6979 deterministic test";
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, msg);
let sig = sign_deterministic(&e, &pri_key);
verify(&e, &pub_key, &sig).expect("deterministic sign must verify");
}
/// RFC 6979 确定性签名:相同输入总产生相同签名
#[test]
fn test_sign_deterministic_reproducible() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let msg = b"reproducibility test";
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, msg);
let sig1 = sign_deterministic(&e, &pri_key);
let sig2 = sign_deterministic(&e, &pri_key);
assert_eq!(sig1, sig2, "RFC 6979 signatures must be reproducible");
verify(&e, &pub_key, &sig1).expect("must verify");
}
/// sign_message_deterministic 便捷接口
#[test]
fn test_sign_message_deterministic() {
let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap();
let pub_key = pri_key.public_key();
let msg = b"sign_message_deterministic test";
let sig = sign_message_deterministic(msg, DEFAULT_ID, &pri_key);
verify_message(msg, DEFAULT_ID, &pub_key, &sig).expect("deterministic message sig must verify");
}
}
+176
View File
@@ -0,0 +1,176 @@
//! RFC 6979 确定性 k 值生成(使用 HMAC-SM3
//!
//! 实现 RFC 6979 §3.2 的 HMAC-DRBG,以 SM3 作为哈希函数。
//!
//! # 安全关键点
//!
//! 此模块消除了 SM2 签名对外部 RNG 的依赖。对于相同的 (私钥, 消息摘要) 对,
//! 生成的 k 值完全确定,从根本上消除了 RNG 故障或偏差导致私钥泄露的风险。
//!
//! # 参考
//!
//! - RFC 6979 §3.2: <https://www.rfc-editor.org/rfc/rfc6979#section-3.2>
use crypto_bigint::{Zero, U256};
use sm3::Digest;
use zeroize::Zeroize;
use crate::field::GROUP_ORDER;
// SM3 输出长度(字节)
const HASH_LEN: usize = 32;
/// 内部 HMAC-SM3 实现(仅供 RFC 6979 使用)
///
/// HMAC(K, m) = SM3((K ^ opad) || SM3((K ^ ipad) || m))
///
/// Reason: sm3 sub-crate 没有导出 HMAC,我们在内部实现以避免新增依赖。
/// 块大小 64 字节,与 SM3 的 BlockSize 一致。
struct HmacSm3 {
/// 外层密钥 K ^ opad(已预处理)
opad_key: [u8; 64],
/// 内层哈希上下文(已吸收 K ^ ipad)
inner: sm3::Sm3,
}
impl HmacSm3 {
/// 以 key 初始化 HMAC-SM3 上下文
fn new(key: &[u8; 32]) -> Self {
let mut ipad_key = [0x36u8; 64];
let mut opad_key = [0x5cu8; 64];
// Reason: key 长度 32 字节 < 块大小 64,直接 XOR 前 32 字节
for (i, &b) in key.iter().enumerate() {
ipad_key[i] ^= b;
opad_key[i] ^= b;
}
let mut inner = sm3::Sm3::new();
inner.update(&ipad_key);
ipad_key.zeroize();
HmacSm3 { opad_key, inner }
}
/// 向内层哈希追加数据
fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
/// 计算 HMAC 值(消耗 self
fn finalize(self) -> [u8; HASH_LEN] {
// Reason: Drop trait 阻止直接 move self.innerclone 一次即可
let inner_hash: [u8; HASH_LEN] = self.inner.clone().finalize().into();
let mut outer = sm3::Sm3::new();
outer.update(&self.opad_key);
outer.update(&inner_hash);
outer.finalize().into()
}
}
impl Drop for HmacSm3 {
fn drop(&mut self) {
self.opad_key.zeroize();
}
}
/// HMAC-SM3 一次性计算:`HMAC(key, msg1 || msg2 || ...)`
fn hmac(key: &[u8; 32], parts: &[&[u8]]) -> [u8; HASH_LEN] {
let mut mac = HmacSm3::new(key);
for part in parts {
mac.update(part);
}
mac.finalize()
}
/// RFC 6979 §3.2 确定性 k 值生成
///
/// 输入:
/// - `x`: 私钥字节(32 字节,big-endian
/// - `h1`: 消息摘要 e32 字节,已经过 Z||M 预处理的 SM3 输出)
///
/// 输出:满足 `0 < k < n` 的确定性标量 k
///
/// # 安全关键点
///
/// 对于相同的 (x, h1) 输入,此函数总是返回相同的 k,
/// 从根本上消除了签名过程对 RNG 质量的依赖。
pub(crate) fn generate_k(x: &[u8; 32], h1: &[u8; 32]) -> U256 {
// RFC 6979 §3.2 步骤 b/c: 初始化 V 和 K
//
// V = 0x01 0x01 ... 0x01 (hlen 个字节)
// K = 0x00 0x00 ... 0x00 (hlen 个字节)
let mut v = [0x01u8; HASH_LEN];
let mut k = [0x00u8; HASH_LEN];
// 步骤 d: K = HMAC_K(V || 0x00 || x || h1)
k = hmac(&k, &[&v, &[0x00u8], x, h1]);
// 步骤 e: V = HMAC_K(V)
v = hmac(&k, &[&v]);
// 步骤 f: K = HMAC_K(V || 0x01 || x || h1)
k = hmac(&k, &[&v, &[0x01u8], x, h1]);
// 步骤 g: V = HMAC_K(V)
v = hmac(&k, &[&v]);
// 步骤 h: 循环生成候选 k
loop {
// h2: V = HMAC_K(V)
v = hmac(&k, &[&v]);
// bits2int(V): 直接作为 big-endian 256-bit 整数
let candidate = U256::from_be_slice(&v);
// 检查 0 < k < ngroup order
// Reason: CT 比较 — candidate.is_zero() 和 candidate >= GROUP_ORDER
let is_zero: bool = candidate.is_zero().into();
let ge_n: bool = (candidate >= GROUP_ORDER).into();
if !is_zero && !ge_n {
// 找到合法 k,清零临时数据后返回
v.zeroize();
k.zeroize();
return candidate;
}
// 步骤 h3 更新(不满足时继续)
k = hmac(&k, &[&v, &[0x00u8]]);
v = hmac(&k, &[&v]);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::GROUP_ORDER;
/// 确定性验证:相同输入总产生相同 k
#[test]
fn test_generate_k_deterministic() {
let x = [0x01u8; 32];
let h1 = [0x02u8; 32];
let k1 = generate_k(&x, &h1);
let k2 = generate_k(&x, &h1);
assert_eq!(k1, k2, "RFC 6979 k must be deterministic");
}
/// k 必须在有效范围 (0, n)
#[test]
fn test_generate_k_range() {
let x = [0x42u8; 32];
let h1 = [0xABu8; 32];
let k = generate_k(&x, &h1);
assert!(!bool::from(k.is_zero()), "k must not be zero");
assert!(k < GROUP_ORDER, "k must be less than group order");
}
/// 不同消息产生不同 k
#[test]
fn test_generate_k_different_msgs() {
let x = [0x01u8; 32];
let h1 = [0x01u8; 32];
let h2 = [0x02u8; 32];
let k1 = generate_k(&x, &h1);
let k2 = generate_k(&x, &h2);
assert_ne!(k1, k2, "different messages must produce different k values");
}
}
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "sm3"
version = "0.1.0"
edition = "2021"
rust-version = "1.83.0"
license = "Apache-2.0"
description = "SM3 (ShangMi 3) hash function — GB/T 32905-2016. Pure-Rust, no_std, implements digest::Digest."
repository = "https://github.com/kintaiW/libsmx"
documentation = "https://docs.rs/sm3"
categories = ["cryptography", "no-std"]
keywords = ["crypto", "hash", "sm3", "shangmi", "digest"]
readme = "README.md"
[dependencies]
digest = { workspace = true, features = ["block-api"] }
[dev-dependencies]
hex-literal = { workspace = true }
digest = { workspace = true, features = ["dev"] }
[features]
default = []
oid = ["digest/oid"]
+98
View File
@@ -0,0 +1,98 @@
//! SM3 block-level core (low-level internal type).
//!
//! Users should use [`Sm3`] from the crate root instead.
use core::fmt;
use digest::{
HashMarker,
block_api::{
AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser,
Eager, FixedOutputCore, OutputSizeUser, UpdateCore,
},
typenum::{U32, U64, Unsigned},
};
use crate::compress;
// ── Sm3Core ───────────────────────────────────────────────────────────────────
/// Low-level SM3 block-processing core.
///
/// Implements the `digest::block_api` low-level traits so that the
/// [`digest::buffer_fixed!`] macro can wrap it into a fully-featured [`Sm3`].
#[derive(Clone)]
pub struct Sm3Core {
state: [u32; 8],
/// Number of **complete** 64-byte blocks already compressed.
block_len: u64,
}
impl HashMarker for Sm3Core {}
impl BlockSizeUser for Sm3Core {
/// SM3 processes 512-bit (64-byte) blocks.
type BlockSize = U64;
}
impl BufferKindUser for Sm3Core {
/// Eager: compress each full block immediately.
type BufferKind = Eager;
}
impl OutputSizeUser for Sm3Core {
/// SM3 produces a 256-bit (32-byte) digest.
type OutputSize = U32;
}
impl Default for Sm3Core {
fn default() -> Self {
Self { state: compress::IV, block_len: 0 }
}
}
impl digest::Reset for Sm3Core {
fn reset(&mut self) {
*self = Self::default();
}
}
impl UpdateCore for Sm3Core {
#[inline]
fn update_blocks(&mut self, blocks: &[Block<Self>]) {
// Reason: only complete blocks are counted here; the partial tail is
// held by the surrounding BlockBuffer and counted in finalize.
self.block_len += blocks.len() as u64;
for block in blocks {
// Reason: hybrid_array::Array<u8, U64> implements Deref<Target=[u8]>,
// so we get a &[u8] slice then cast to &[u8; 64] via try_into.
let b: &[u8; 64] = (&**block).try_into().unwrap();
compress::compress(&mut self.state, b);
}
}
}
impl FixedOutputCore for Sm3Core {
#[inline]
fn finalize_fixed_core(&mut self, buffer: &mut Buffer<Self>, out: &mut digest::Output<Self>) {
// Total bit length = (complete blocks × 64 + partial tail) × 8
let bs = U64::U64;
let bit_len = 8 * (buffer.get_pos() as u64 + bs * self.block_len);
// GB/T 32905 §5.3.1 padding: 0x80, zeros, 64-bit big-endian bit count
buffer.len64_padding_be(bit_len, |block| {
let b: &[u8; 64] = (&**block).try_into().unwrap();
compress::compress(&mut self.state, b);
});
// Serialize state as big-endian u32 words
for (chunk, &word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
chunk.copy_from_slice(&word.to_be_bytes());
}
}
}
impl AlgorithmName for Sm3Core {
fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Sm3")
}
}
+81
View File
@@ -0,0 +1,81 @@
//! SM3 compression function (GB/T 32905-2016 §5)
/// SM3 initial hash values (IV), GB/T 32905 §4.3
pub(super) const IV: [u32; 8] = [
0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E,
];
/// Round constants T_j (GB/T 32905 §4.2), precomputed to avoid runtime branches.
///
/// Reason: Eliminates the `if j < 16` branch in each round; the compiler
/// embeds these as immediates with zero runtime rotation overhead.
const T: [u32; 64] = {
let mut t = [0u32; 64];
let mut j = 0usize;
while j < 16 {
t[j] = 0x79CC4519u32.rotate_left(j as u32);
j += 1;
}
while j < 64 {
t[j] = 0x7A879D8Au32.rotate_left((j % 32) as u32);
j += 1;
}
t
};
/// Permutation function P0 (GB/T 32905 §4.5)
#[inline(always)]
fn p0(x: u32) -> u32 {
x ^ x.rotate_left(9) ^ x.rotate_left(17)
}
/// Permutation function P1 (GB/T 32905 §4.5)
#[inline(always)]
fn p1(x: u32) -> u32 {
x ^ x.rotate_left(15) ^ x.rotate_left(23)
}
/// SM3 compression function: processes one 64-byte block, updates `state`.
///
/// Reason: Two-segment loop (j=0..15 and j=16..63) eliminates runtime `if`
/// branches inside ff/gg/T; W' is inlined as `w[j] ^ w[j+4]`.
pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) {
// ── Message expansion ────────────────────────────────────────────────────
let mut w = [0u32; 68];
for i in 0..16 {
w[i] = u32::from_be_bytes(block[i * 4..i * 4 + 4].try_into().unwrap());
}
for i in 16..68 {
let v = w[i - 16] ^ w[i - 9] ^ w[i - 3].rotate_left(15);
w[i] = p1(v) ^ w[i - 13].rotate_left(7) ^ w[i - 6];
}
// ── Compression: 64 rounds ───────────────────────────────────────────────
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
// j = 0..15: FF = x^y^z, GG = x^y^z
for j in 0..16 {
let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = (a ^ b ^ c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]);
let tt2 = (e ^ f ^ g).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]);
d = c; c = b.rotate_left(9); b = a; a = tt1;
h = g; g = f.rotate_left(19); f = e; e = p0(tt2);
}
// j = 16..63: FF = majority(x,y,z), GG = choice(x,y,z)
for j in 16..64 {
let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = ((a & b) | (a & c) | (b & c))
.wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]);
let tt2 = ((e & f) | (!e & g))
.wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]);
d = c; c = b.rotate_left(9); b = a; a = tt1;
h = g; g = f.rotate_left(19); f = e; e = p0(tt2);
}
state[0] ^= a; state[1] ^= b; state[2] ^= c; state[3] ^= d;
state[4] ^= e; state[5] ^= f; state[6] ^= g; state[7] ^= h;
}
+124
View File
@@ -0,0 +1,124 @@
//! SM3 cryptographic hash function (GB/T 32905-2016).
//!
//! This crate provides a [`Digest`]-compatible SM3 implementation suitable for
//! use anywhere in the RustCrypto ecosystem.
//!
//! ## Security
//!
//! SM3 is standardised by the Chinese National Standard (GB/T 32905-2016) and
//! provides a 256-bit (32-byte) digest. It has a similar structure to SHA-256
//! but uses different constants, mixing functions, and message scheduling.
//!
//! ## Usage
//!
//! ```rust
//! use sm3::{Sm3, Digest};
//!
//! let hash = Sm3::digest(b"abc");
//! assert_eq!(
//! hash[..],
//! hex_literal::hex!("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"),
//! );
//! ```
#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod compress;
/// Block-level SM3 core — low-level building block, not for direct use.
///
/// Prefer the top-level [`Sm3`] type which provides the full `Digest` API.
pub mod block_api;
pub use digest::{self, Digest};
// Re-export the core type for users who need low-level access (e.g. HMAC cores).
pub use block_api::Sm3Core;
// Generate the buffered `Sm3` wrapper using the digest crate macro.
// BaseFixedTraits provides: Debug, BlockSizeUser, OutputSizeUser, CoreProxy, Update, FixedOutput.
// We also add AlgorithmName, Default, Clone, HashMarker, Reset, FixedOutputReset explicitly.
// Reason: FixedHashTraits additionally requires SerializableState and ZeroizeOnDrop which
// are non-trivial to implement safely; BaseFixedTraits is sufficient for the Digest interface.
digest::buffer_fixed!(
/// SM3 hash function (GB/T 32905-2016).
///
/// Implements [`Digest`] and is a drop-in for SHA-256 in generic protocols.
pub struct Sm3(block_api::Sm3Core);
impl: BaseFixedTraits AlgorithmName Default Clone HashMarker Reset FixedOutputReset;
);
#[cfg(test)]
mod tests {
use super::*;
use digest::Digest;
use hex_literal::hex;
/// GB/T 32905-2016 Appendix A, Example 1: SM3("abc")
#[test]
fn test_vector_abc() {
let hash = Sm3::digest(b"abc");
let expected = hex!("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0");
assert_eq!(hash[..], expected[..]);
}
/// GB/T 32905-2016 Appendix A, Example 2: 64-byte repeated string
#[test]
fn test_vector_64bytes() {
let msg = b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd";
let hash = Sm3::digest(msg);
let expected = hex!("debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732");
assert_eq!(hash[..], expected[..]);
}
/// Empty input
#[test]
fn test_vector_empty() {
let hash = Sm3::digest(b"");
let expected = hex!("1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b");
assert_eq!(hash[..], expected[..]);
}
/// Cross-block boundary: 65 bytes (one full block + 1 byte tail)
#[test]
fn test_cross_block_boundary() {
let data = [0x61u8; 65]; // 65 x 'a'
let once = Sm3::digest(&data);
// Streaming 1 byte at a time must match one-shot
let mut h = Sm3::new();
for b in &data {
h.update(&[*b]);
}
assert_eq!(once, h.finalize());
}
/// Streaming must match one-shot for an arbitrary input
#[test]
fn test_streaming_matches_oneshot() {
let data = b"hello world, streaming SM3 test";
let once = Sm3::digest(data);
let mut h = Sm3::new();
for chunk in data.chunks(7) {
h.update(chunk);
}
assert_eq!(once, h.finalize());
}
/// Clone mid-stream must produce the same result
#[test]
fn test_clone_midstream() {
let mut h1 = Sm3::new();
h1.update(b"hello");
let h2 = h1.clone();
h1.update(b" world");
let mut h3 = h2;
h3.update(b" world");
assert_eq!(h1.finalize(), h3.finalize());
}
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "sm4"
version = "0.1.0"
edition = "2021"
rust-version = "1.83.0"
license = "Apache-2.0"
description = "SM4 (ShangMi 4) block cipher with constant-time bitslice S-box — GB/T 32907-2016"
repository = "https://github.com/kintaiW/libsmx"
categories = ["cryptography", "no-std::no-alloc"]
keywords = ["crypto", "cipher", "sm4", "shangmi", "block-cipher"]
[dependencies]
cipher = { workspace = true }
zeroize = { workspace = true }
[dev-dependencies]
hex-literal = { workspace = true }
cipher = { workspace = true, features = ["dev"] }
[features]
default = []
zeroize = []
+286
View File
@@ -0,0 +1,286 @@
//! SM4 S-box constants and internal cipher functions (GB/T 32907-2016).
//!
//! All operations are constant-time boolean-circuit implementations.
// ── System constants ──────────────────────────────────────────────────────────
/// System parameter FK (GB/T 32907 §A.1)
pub(super) const FK: [u32; 4] = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
/// Constant key CK (GB/T 32907 §A.1)
#[rustfmt::skip]
pub(super) const CK: [u32; 32] = [
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
];
// ── Boolean-circuit S-box ─────────────────────────────────────────────────────
/// SM4 S-box — pure boolean circuit (zero memory access, cache-timing immune).
///
/// Input/output linear layers + GF(2^4) inversion in boolean circuit form.
/// Source: emmansun/sm4bs (sbox64), extracted and validated against all 256 values.
///
/// ⚠️ Security: Uses only AND/XOR/OR/NOT. No table lookups, no memory reads.
#[allow(dead_code)]
#[inline]
pub(super) fn sbox_ct(x: u8) -> u8 {
let b0 = x & 1;
let b1 = (x >> 1) & 1;
let b2 = (x >> 2) & 1;
let b3 = (x >> 3) & 1;
let b4 = (x >> 4) & 1;
let b5 = (x >> 5) & 1;
let b6 = (x >> 6) & 1;
let b7 = (x >> 7) & 1;
let t1 = b7 ^ b5;
let t2 = 1 ^ (b5 ^ b1);
let g5 = 1 ^ b0;
let t3 = 1 ^ (b0 ^ t2);
let t4 = b6 ^ b2;
let t5 = b3 ^ t3;
let t6 = b4 ^ t1;
let t7 = b1 ^ t5;
let t8 = b1 ^ t4;
let t9 = t6 ^ t8;
let t10 = t6 ^ t7;
let t11 = 1 ^ (b3 ^ t1);
let t12 = 1 ^ (b6 ^ t9);
let g0 = t10;
let g1 = t7;
let g2 = t4 ^ t10;
let g3 = t5;
let g4 = t2;
let g6 = t11 ^ t2;
let g7 = t12 ^ (t11 ^ t2);
let m0 = t6; let m1 = t3; let m2 = t8;
let m3 = t3 ^ t12; let m4 = t4; let m5 = t11;
let m6 = b1; let m7 = t11 ^ m3; let m8 = t9; let m9 = t12;
let t2t = m0 & m1; let t3t = g0 & g4; let t4t = g3 & g7;
let t7t = g3 | g7; let t11t = m4 & m5; let t10t = m3 & m2;
let t12t = m3 | m2; let t6t = g6 | g2; let t9t = m6 | m7;
let t5t = m8 & m9; let t8t = m8 | m9;
let t14t = t3t ^ t2t; let t16t = t5t ^ t14t;
let t20t = t16t ^ t7t; let t17t = t9t ^ t10t;
let t18t = t11t ^ t12t;
let p2 = t20t ^ t18t; let p0 = t6t ^ t16t;
let t1t = g5 & g1; let t13t = t1t ^ t2t;
let t15t = t13t ^ t4t;
let p3 = (t6t ^ t15t) ^ t17t; let p1 = t8t ^ t15t;
let t0m = p1 & p2; let t1m = p3 & p0; let t2m = p0 & p2;
let t3m = p1 & p3; let t4m = t0m & t2m;
let t5m = t1m ^ t3m; let t6m = t5m | p0; let t7m = t2m | p3;
let l3 = t4m ^ t6m; let t9m = t7m ^ t3m;
let l0 = t0m ^ t9m; let t11m = p2 | t5m;
let l1 = t11m ^ t1m; let t12m = p1 | t2m; let l2 = t12m ^ t5m;
let k4 = l2 ^ l3; let k3 = l1 ^ l3; let k2 = l0 ^ l2;
let k0 = l0 ^ l1; let k1 = k2 ^ k3;
let e0 = m1 & k0; let e1 = g5 & l1; let r0 = e0 ^ e1;
let e2 = g4 & l0; let r1 = e2 ^ e1;
let e3 = m7 & k3; let e4 = m5 & k2; let r2 = e3 ^ e4;
let e5 = m3 & k1; let r3 = e5 ^ e4;
let e6 = m9 & k4; let e7 = g7 & l3; let r4 = e6 ^ e7;
let e8 = g6 & l2; let r5 = e8 ^ e7;
let e9 = m0 & k0; let e10 = g1 & l1; let r6 = e9 ^ e10;
let e11 = g0 & l0; let r7 = e11 ^ e10;
let e12 = m6 & k3; let e13 = m4 & k2; let r8 = e12 ^ e13;
let e14 = m2 & k1; let r9 = e14 ^ e13;
let e15 = m8 & k4; let e16 = g3 & l3; let r10 = e15 ^ e16;
let e17 = g2 & l2; let r11 = e17 ^ e16;
let t1o = r7 ^ r9; let t2o = r1 ^ t1o; let t3o = r3 ^ t2o;
let t4o = r5 ^ r3; let t5o = r4 ^ t4o; let t6o = r0 ^ r4;
let t7o = r11 ^ r7;
let b5o = t1o ^ t4o; let b2o = t1o ^ t6o;
let t10o = r2 ^ t5o; let b3o = r10 ^ r8;
let b1o = 1 ^ (t3o ^ b3o); let b6o = t10o ^ b1o;
let b4o = 1 ^ (t3o ^ t7o); let b0o = t6o ^ b4o;
let b7o = 1 ^ (r10 ^ r6);
b0o | (b1o << 1) | (b2o << 2) | (b3o << 3) | (b4o << 4) | (b5o << 5) | (b6o << 6) | (b7o << 7)
}
/// SM4 τ transform: 4-byte u32 bitslice S-box (constant-time, 4-way parallel).
///
/// Packs 4 bytes' bit-planes into 4 u32 lanes, runs the boolean circuit once,
/// then unpacks — equivalent to ~3-4x speedup over 4 independent `sbox_ct` calls.
///
/// ⚠️ Security: Inherits full constant-time properties of `sbox_ct`.
#[inline]
pub(super) fn tau(a: u32) -> u32 {
let bytes = a.to_be_bytes();
// Pack: bits[i] low 4 = bit-i of [byte0, byte1, byte2, byte3]
let mut bits = [0u32; 8];
for (i, bit) in bits.iter_mut().enumerate() {
*bit = ((bytes[0] >> i) & 1) as u32
| (((bytes[1] >> i) & 1) as u32) << 1
| (((bytes[2] >> i) & 1) as u32) << 2
| (((bytes[3] >> i) & 1) as u32) << 3;
}
let [b0, b1, b2, b3, b4, b5, b6, b7] = bits;
// Boolean circuit (identical to sbox_ct, but NOT uses 0xF instead of 1)
let t1 = b7 ^ b5;
let t2 = 0xF ^ (b5 ^ b1);
let g5 = 0xF ^ b0;
let t3 = 0xF ^ (b0 ^ t2);
let t4 = b6 ^ b2;
let t5 = b3 ^ t3;
let t6 = b4 ^ t1;
let t7 = b1 ^ t5;
let t8 = b1 ^ t4;
let t9 = t6 ^ t8;
let t10 = t6 ^ t7;
let t11 = 0xF ^ (b3 ^ t1);
let t12 = 0xF ^ (b6 ^ t9);
let g0 = t10; let g1 = t7; let g2 = t4 ^ t10; let g3 = t5;
let g4 = t2; let g6 = t11 ^ t2; let g7 = t12 ^ (t11 ^ t2);
let m0 = t6; let m1 = t3; let m2 = t8; let m3 = t3 ^ t12;
let m4 = t4; let m5 = t11; let m6 = b1; let m7 = t11 ^ m3;
let m8 = t9; let m9 = t12;
let t2t = m0 & m1; let t3t = g0 & g4; let t4t = g3 & g7;
let t7t = g3 | g7; let t11t = m4 & m5; let t10t = m3 & m2;
let t12t = m3 | m2; let t6t = g6 | g2; let t9t = m6 | m7;
let t5t = m8 & m9; let t8t = m8 | m9;
let t14t = t3t ^ t2t; let t16t = t5t ^ t14t;
let t20t = t16t ^ t7t; let t17t = t9t ^ t10t;
let t18t = t11t ^ t12t;
let p2 = t20t ^ t18t; let p0 = t6t ^ t16t;
let t1t = g5 & g1; let t13t = t1t ^ t2t;
let t15t = t13t ^ t4t;
let p3 = (t6t ^ t15t) ^ t17t; let p1 = t8t ^ t15t;
let t0m = p1 & p2; let t1m = p3 & p0; let t2m = p0 & p2;
let t3m = p1 & p3; let t4m = t0m & t2m;
let t5m = t1m ^ t3m; let t6m = t5m | p0; let t7m = t2m | p3;
let l3 = t4m ^ t6m; let t9m = t7m ^ t3m;
let l0 = t0m ^ t9m; let t11m = p2 | t5m;
let l1 = t11m ^ t1m; let t12m = p1 | t2m; let l2 = t12m ^ t5m;
let k4 = l2 ^ l3; let k3 = l1 ^ l3; let k2 = l0 ^ l2;
let k0 = l0 ^ l1; let k1 = k2 ^ k3;
let e0 = m1 & k0; let e1 = g5 & l1; let r0 = e0 ^ e1;
let e2 = g4 & l0; let r1 = e2 ^ e1;
let e3 = m7 & k3; let e4 = m5 & k2; let r2 = e3 ^ e4;
let e5 = m3 & k1; let r3 = e5 ^ e4;
let e6 = m9 & k4; let e7 = g7 & l3; let r4 = e6 ^ e7;
let e8 = g6 & l2; let r5 = e8 ^ e7;
let e9 = m0 & k0; let e10 = g1 & l1; let r6 = e9 ^ e10;
let e11 = g0 & l0; let r7 = e11 ^ e10;
let e12 = m6 & k3; let e13 = m4 & k2; let r8 = e12 ^ e13;
let e14 = m2 & k1; let r9 = e14 ^ e13;
let e15 = m8 & k4; let e16 = g3 & l3; let r10 = e15 ^ e16;
let e17 = g2 & l2; let r11 = e17 ^ e16;
let t1o = r7 ^ r9; let t2o = r1 ^ t1o; let t3o = r3 ^ t2o;
let t4o = r5 ^ r3; let t5o = r4 ^ t4o; let t6o = r0 ^ r4;
let t7o = r11 ^ r7;
let b5o = t1o ^ t4o; let b2o = t1o ^ t6o;
let t10o = r2 ^ t5o; let b3o = r10 ^ r8;
let b1o = 0xF ^ (t3o ^ b3o); let b6o = t10o ^ b1o;
let b4o = 0xF ^ (t3o ^ t7o); let b0o = t6o ^ b4o;
let b7o = 0xF ^ (r10 ^ r6);
// Unpack: 8 u32 low-4 bits -> 4 output bytes
let ob = [b0o, b1o, b2o, b3o, b4o, b5o, b6o, b7o];
let mut out = [0u8; 4];
for (i, &v) in ob.iter().enumerate() {
out[0] |= ((v & 1) as u8) << i;
out[1] |= (((v >> 1) & 1) as u8) << i;
out[2] |= (((v >> 2) & 1) as u8) << i;
out[3] |= (((v >> 3) & 1) as u8) << i;
}
u32::from_be_bytes(out)
}
// ── Round functions ───────────────────────────────────────────────────────────
/// Encryption round function T (GB/T 32907 §6.2.1)
#[inline]
pub(super) fn t_enc(a: u32) -> u32 {
let b = tau(a);
b ^ b.rotate_left(2) ^ b.rotate_left(10) ^ b.rotate_left(18) ^ b.rotate_left(24)
}
/// Key expansion round function T' (GB/T 32907 §6.2.2)
#[inline]
fn t_key(a: u32) -> u32 {
let b = tau(a);
b ^ b.rotate_left(13) ^ b.rotate_left(23)
}
// ── Key expansion ─────────────────────────────────────────────────────────────
/// SM4 key expansion (GB/T 32907 §6.2.2)
pub(super) fn expand_key(key: &[u8; 16], rk: &mut [u32; 32]) {
let mk = [
u32::from_be_bytes(key[0..4].try_into().unwrap()),
u32::from_be_bytes(key[4..8].try_into().unwrap()),
u32::from_be_bytes(key[8..12].try_into().unwrap()),
u32::from_be_bytes(key[12..16].try_into().unwrap()),
];
let mut k = [mk[0] ^ FK[0], mk[1] ^ FK[1], mk[2] ^ FK[2], mk[3] ^ FK[3]];
for i in 0..32 {
let tmp = k[(i + 1) % 4] ^ k[(i + 2) % 4] ^ k[(i + 3) % 4] ^ CK[i];
rk[i] = k[i % 4] ^ t_key(tmp);
k[i % 4] = rk[i];
}
}
// ── Block load/store ──────────────────────────────────────────────────────────
#[inline]
pub(super) fn load_block(b: &[u8; 16]) -> [u32; 4] {
[
u32::from_be_bytes(b[0..4].try_into().unwrap()),
u32::from_be_bytes(b[4..8].try_into().unwrap()),
u32::from_be_bytes(b[8..12].try_into().unwrap()),
u32::from_be_bytes(b[12..16].try_into().unwrap()),
]
}
#[inline]
pub(super) fn store_block(b: &mut [u8; 16], x: &[u32; 4]) {
b[0..4].copy_from_slice(&x[0].to_be_bytes());
b[4..8].copy_from_slice(&x[1].to_be_bytes());
b[8..12].copy_from_slice(&x[2].to_be_bytes());
b[12..16].copy_from_slice(&x[3].to_be_bytes());
}
// ── Encryption / Decryption rounds ────────────────────────────────────────────
/// 32-round SM4 encryption (round keys in forward order)
pub(super) fn encrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) {
for &rk_i in rk.iter() {
let tmp = x[1] ^ x[2] ^ x[3] ^ rk_i;
let next = x[0] ^ t_enc(tmp);
x[0] = x[1]; x[1] = x[2]; x[2] = x[3]; x[3] = next;
}
x.reverse(); // GB/T 32907 §6.2.1: output = (X35, X34, X33, X32)
}
/// 32-round SM4 decryption (round keys in reverse order)
pub(super) fn decrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) {
for i in (0..32).rev() {
let tmp = x[1] ^ x[2] ^ x[3] ^ rk[i];
let next = x[0] ^ t_enc(tmp);
x[0] = x[1]; x[1] = x[2]; x[2] = x[3]; x[3] = next;
}
x.reverse();
}
+254
View File
@@ -0,0 +1,254 @@
//! SM4 block cipher (GB/T 32907-2016).
//!
//! This crate provides a [`cipher::BlockCipherEncrypt`] / [`cipher::BlockCipherDecrypt`]
//! compatible SM4 implementation suitable for use in the RustCrypto ecosystem.
//!
//! ## Security
//!
//! SM4 is standardised by the Chinese National Standard (GB/T 32907-2016).
//! This implementation uses a **boolean-circuit bitslice S-box** (zero memory
//! accesses) rather than a lookup table, making it immune to cache-timing
//! side-channel attacks.
//!
//! ## Usage
//!
//! ```rust
//! use sm4::Sm4;
//! use sm4::cipher::{BlockCipherEncrypt, BlockCipherDecrypt, KeyInit};
//!
//! let key = [0u8; 16];
//! let plaintext = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
//! 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10];
//! let expected = [0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e,
//! 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46];
//!
//! let cipher = Sm4::new(&plaintext.into());
//! let mut block = plaintext.into();
//! cipher.encrypt_block(&mut block);
//! assert_eq!(block[..], expected[..]);
//! ```
#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod consts;
pub use cipher::{self, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
use cipher::{
AlgorithmName, Block, BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend,
BlockCipherEncClosure, BlockSizeUser, InOut, KeySizeUser, ParBlocksSizeUser,
consts::{U1, U16},
};
use core::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};
// ── Key type alias ────────────────────────────────────────────────────────────
/// SM4 key type: 128 bits (16 bytes).
pub type Sm4Key = cipher::Key<Sm4>;
// ── Sm4 struct ────────────────────────────────────────────────────────────────
/// SM4 block cipher (GB/T 32907-2016).
///
/// Implements [`BlockCipherEncrypt`] and [`BlockCipherDecrypt`] from the
/// `cipher` crate. Construct with [`KeyInit::new`].
#[derive(Clone)]
pub struct Sm4 {
/// 32 round keys derived from the 128-bit master key.
rk: [u32; 32],
}
impl Drop for Sm4 {
fn drop(&mut self) {
self.rk.zeroize();
}
}
impl ZeroizeOnDrop for Sm4 {}
// ── KeySizeUser / BlockSizeUser / ParBlocksSizeUser ───────────────────────────
impl KeySizeUser for Sm4 {
/// SM4 requires a 128-bit (16-byte) key.
type KeySize = U16;
}
impl BlockSizeUser for Sm4 {
/// SM4 operates on 128-bit (16-byte) blocks.
type BlockSize = U16;
}
impl ParBlocksSizeUser for Sm4 {
/// No parallel blocks: each block is processed independently.
type ParBlocksSize = U1;
}
// ── KeyInit ───────────────────────────────────────────────────────────────────
impl KeyInit for Sm4 {
fn new(key: &Sm4Key) -> Self {
let mut rk = [0u32; 32];
// Reason: Sm4Key = Array<u8, U16> which Derefs to [u8; 16] via as_slice().
consts::expand_key(<&[u8; 16]>::try_from(key.as_slice()).unwrap(), &mut rk);
Self { rk }
}
}
// ── AlgorithmName / Debug ─────────────────────────────────────────────────────
impl AlgorithmName for Sm4 {
fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Sm4")
}
}
impl fmt::Debug for Sm4 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Sm4 { ... }")
}
}
// ── BlockCipherEncrypt ────────────────────────────────────────────────────────
impl BlockCipherEncrypt for Sm4 {
fn encrypt_with_backend(&self, f: impl BlockCipherEncClosure<BlockSize = Self::BlockSize>) {
f.call(&Sm4EncBackend(self));
}
}
// ── BlockCipherDecrypt ────────────────────────────────────────────────────────
impl BlockCipherDecrypt for Sm4 {
fn decrypt_with_backend(&self, f: impl BlockCipherDecClosure<BlockSize = Self::BlockSize>) {
f.call(&Sm4DecBackend(self));
}
}
// ── Encryption backend ────────────────────────────────────────────────────────
struct Sm4EncBackend<'a>(&'a Sm4);
impl BlockSizeUser for Sm4EncBackend<'_> {
type BlockSize = U16;
}
impl ParBlocksSizeUser for Sm4EncBackend<'_> {
type ParBlocksSize = U1;
}
impl BlockCipherEncBackend for Sm4EncBackend<'_> {
#[inline]
fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
let mut x =
consts::load_block(<&[u8; 16]>::try_from(block.get_in().as_slice()).unwrap());
consts::encrypt_rounds(&mut x, &self.0.rk);
consts::store_block(
<&mut [u8; 16]>::try_from(block.get_out().as_mut_slice()).unwrap(),
&x,
);
}
}
// ── Decryption backend ────────────────────────────────────────────────────────
struct Sm4DecBackend<'a>(&'a Sm4);
impl BlockSizeUser for Sm4DecBackend<'_> {
type BlockSize = U16;
}
impl ParBlocksSizeUser for Sm4DecBackend<'_> {
type ParBlocksSize = U1;
}
impl BlockCipherDecBackend for Sm4DecBackend<'_> {
#[inline]
fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
let mut x =
consts::load_block(<&[u8; 16]>::try_from(block.get_in().as_slice()).unwrap());
consts::decrypt_rounds(&mut x, &self.0.rk);
consts::store_block(
<&mut [u8; 16]>::try_from(block.get_out().as_mut_slice()).unwrap(),
&x,
);
}
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
use hex_literal::hex;
/// GB/T 32907-2016 Appendix A, Example 1
/// Key = 0123456789ABCDEFFEDCBA9876543210
/// Plaintext = 0123456789ABCDEFFEDCBA9876543210
/// Ciphertext = 681EDF34D206965E86B3E94F536E4246
#[test]
fn test_vector_appendix_a() {
let key = hex!("0123456789ABCDEFFEDCBA9876543210");
let plaintext = hex!("0123456789ABCDEFFEDCBA9876543210");
let ciphertext = hex!("681EDF34D206965E86B3E94F536E4246");
let cipher = Sm4::new(&key.into());
let mut block: Block<Sm4> = plaintext.into();
cipher.encrypt_block(&mut block);
assert_eq!(block[..], ciphertext[..], "encryption mismatch");
cipher.decrypt_block(&mut block);
assert_eq!(block[..], plaintext[..], "decryption mismatch");
}
/// GB/T 32907-2016 Appendix A, Example 2: 1 000 000 iterations
/// Repeatedly encrypt the same plaintext 10^6 times.
/// Result must be 595298C7C6FD271F0402F804C33D3F66.
#[test]
#[ignore = "slow (1M iterations)"]
fn test_vector_1m_iterations() {
let key = hex!("0123456789ABCDEFFEDCBA9876543210");
let expected = hex!("595298C7C6FD271F0402F804C33D3F66");
let cipher = Sm4::new(&key.into());
let mut block: Block<Sm4> = hex!("0123456789ABCDEFFEDCBA9876543210").into();
for _ in 0..1_000_000 {
cipher.encrypt_block(&mut block);
}
assert_eq!(block[..], expected[..]);
}
/// All-zero key, all-zero block: encrypt then decrypt must restore plaintext.
#[test]
fn test_all_zeros_roundtrip() {
let key = [0u8; 16];
let plaintext = [0u8; 16];
let cipher = Sm4::new(&key.into());
let mut block: Block<Sm4> = plaintext.into();
cipher.encrypt_block(&mut block);
cipher.decrypt_block(&mut block);
assert_eq!(block[..], plaintext[..]);
}
/// Arbitrary key/plaintext: roundtrip must restore the original.
#[test]
fn test_roundtrip() {
let key = hex!("FEDCBA98765432100123456789ABCDEF");
let plaintext = hex!("AABBCCDDEEFF00112233445566778899");
let cipher = Sm4::new(&key.into());
let mut block: Block<Sm4> = plaintext.into();
cipher.encrypt_block(&mut block);
assert_ne!(block[..], plaintext[..], "ciphertext must differ from plaintext");
cipher.decrypt_block(&mut block);
assert_eq!(block[..], plaintext[..], "roundtrip must restore plaintext");
}
}
+310
View File
@@ -0,0 +1,310 @@
//! SM4-GCM / SM4-CCM AEAD 实现(rustls TLS 1.3 专用)
//!
//! 此模块仅供 `rustls_provider` 内部使用,不对外公开。
//! GCM/CCM 是 TLS 1.3 密码套件的必要组成,与通用工作模式不同,
//! 它们与 TLS 记录层协议强耦合,因此保留在 rustls_provider 内。
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use subtle::ConstantTimeEq;
use crate::sm4::cipher::{encrypt_block_raw, Sm4Key};
use crate::error::Error;
// ── GF(2^128) 乘法 ────────────────────────────────────────────────────────────
/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间,u64 优化)
///
/// Reason: GHASH 密钥 H 来自 SM4_K(0^128),属秘密值;使用掩码算术替代
/// 条件分支,消除 cache-timing 和 branch-timing 侧信道。
fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
let mut z = [0u64; 2];
let mut v = [
u64::from_be_bytes(y[0..8].try_into().unwrap()),
u64::from_be_bytes(y[8..16].try_into().unwrap()),
];
for &byte_xi in x.iter() {
for bit_idx in (0..8).rev() {
let mask = 0u64.wrapping_sub(((byte_xi >> bit_idx) & 1) as u64);
z[0] ^= v[0] & mask;
z[1] ^= v[1] & mask;
let lsb = v[1] & 1;
let carry = v[0] & 1;
v[0] >>= 1;
v[1] = (v[1] >> 1) | (carry << 63);
let reduce_mask = 0u64.wrapping_sub(lsb);
v[0] ^= 0xE100_0000_0000_0000u64 & reduce_mask;
}
}
let mut out = [0u8; 16];
out[0..8].copy_from_slice(&z[0].to_be_bytes());
out[8..16].copy_from_slice(&z[1].to_be_bytes());
out
}
/// GHASH 认证函数(NIST SP 800-38D §6.4
fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] {
let mut y = [0u8; 16];
for chunk in aad.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 { y[i] ^= block[i]; }
y = gf128_mul(&y, h);
}
for chunk in ciphertext.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 { y[i] ^= block[i]; }
y = gf128_mul(&y, h);
}
let mut len_block = [0u8; 16];
len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes());
for i in 0..16 { y[i] ^= len_block[i]; }
gf128_mul(&y, h)
}
/// GCM 计数器递增(仅最后 4 字节,GCM 标准)
#[inline]
fn gcm_ctr_inc(counter: &mut [u8; 16]) {
// Reason: GCM 规范中计数器字段只占最后 4 字节(大端 32 位)
for i in (12..16).rev() {
counter[i] = counter[i].wrapping_add(1);
if counter[i] != 0 { break; }
}
}
// ── GCM ───────────────────────────────────────────────────────────────────────
/// SM4-GCM 加密(AEAD),返回 `(密文, 16字节认证标签)`
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_gcm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> (Vec<u8>, [u8; 16]) {
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let h = encrypt_block_raw(rk, &[0u8; 16]);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut ctr = j0;
gcm_ctr_inc(&mut ctr);
let ciphertext: Vec<u8> = {
let mut out = Vec::with_capacity(plaintext.len());
let mut counter = ctr;
for chunk in plaintext.chunks(16) {
let ks = encrypt_block_raw(rk, &counter);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
gcm_ctr_inc(&mut counter);
}
out
};
let ghash_val = ghash(&h, aad, &ciphertext);
let ej0 = encrypt_block_raw(rk, &j0);
let mut tag = [0u8; 16];
for i in 0..16 { tag[i] = ghash_val[i] ^ ej0[i]; }
(ciphertext, tag)
}
/// SM4-GCM 解密(AEAD),先验证 tag 再解密
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_gcm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8; 16],
) -> Result<Vec<u8>, Error> {
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let h = encrypt_block_raw(rk, &[0u8; 16]);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
// Reason: 先验证 tag 再解密,防止 padding oracle 和选择密文攻击
let ghash_val = ghash(&h, aad, ciphertext);
let ej0 = encrypt_block_raw(rk, &j0);
let mut expected_tag = [0u8; 16];
for i in 0..16 { expected_tag[i] = ghash_val[i] ^ ej0[i]; }
if expected_tag.ct_eq(tag).unwrap_u8() == 0 {
return Err(Error::AuthTagMismatch);
}
let mut ctr = j0;
gcm_ctr_inc(&mut ctr);
let mut plaintext = Vec::with_capacity(ciphertext.len());
let mut counter = ctr;
for chunk in ciphertext.chunks(16) {
let ks = encrypt_block_raw(rk, &counter);
for (i, &b) in chunk.iter().enumerate() {
plaintext.push(b ^ ks[i]);
}
gcm_ctr_inc(&mut counter);
}
Ok(plaintext)
}
// ── CCM ───────────────────────────────────────────────────────────────────────
/// 构造 CCM CBC-MACRFC 3610
fn ccm_cbc_mac(
rk: &[u32; 32],
nonce: &[u8; 12],
aad: &[u8],
message: &[u8],
tag_len: usize,
) -> Result<[u8; 16], Error> {
let q = 3usize;
let has_aad = !aad.is_empty();
let flags = ((has_aad as u8) << 6) | (((tag_len - 2) / 2) as u8) << 3 | (q as u8 - 1);
let mut b0 = [0u8; 16];
b0[0] = flags;
b0[1..13].copy_from_slice(nonce);
let msg_len = message.len() as u32;
b0[13] = (msg_len >> 16) as u8;
b0[14] = (msg_len >> 8) as u8;
b0[15] = msg_len as u8;
let mut x = encrypt_block_raw(rk, &b0);
if has_aad {
let aad_len = aad.len();
let prefix_len = 2 + aad_len;
let padded_len = prefix_len.div_ceil(16) * 16;
let mut aad_buf = [0u8; 512];
// Reason: 超过 510 字节需要 4 字节长度编码(RFC 3610 §2.2),
// 当前实现仅支持 2 字节编码,超限时必须拒绝而非静默跳过 AAD。
if prefix_len > aad_buf.len() {
return Err(Error::InvalidInputLength);
}
aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes());
aad_buf[2..2 + aad_len].copy_from_slice(aad);
for chunk in aad_buf[..padded_len].chunks(16) {
let block: [u8; 16] = chunk.try_into().unwrap();
for i in 0..16 { x[i] ^= block[i]; }
x = encrypt_block_raw(rk, &x);
}
}
for chunk in message.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 { x[i] ^= block[i]; }
x = encrypt_block_raw(rk, &x);
}
Ok(x)
}
/// SM4-CCM 加密(AEAD),输出 `密文 || tag`
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
tag_len: usize,
) -> Result<Vec<u8>, Error> {
assert!(
(4..=16).contains(&tag_len) && tag_len % 2 == 0,
"CCM tag_len 须为 4~16 的偶数"
);
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len)?;
let mut a0 = [0u8; 16];
a0[0] = 2u8;
a0[1..13].copy_from_slice(nonce);
let s0 = encrypt_block_raw(rk, &a0);
let mut enc_tag = [0u8; 16];
for i in 0..tag_len { enc_tag[i] = t[i] ^ s0[i]; }
let mut out = Vec::with_capacity(plaintext.len() + tag_len);
for (block_idx, chunk) in plaintext.chunks(16).enumerate() {
let mut a_i = a0;
let ctr_val = (block_idx as u32) + 1;
a_i[13] = (ctr_val >> 16) as u8;
a_i[14] = (ctr_val >> 8) as u8;
a_i[15] = ctr_val as u8;
let ks = encrypt_block_raw(rk, &a_i);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
}
out.extend_from_slice(&enc_tag[..tag_len]);
Ok(out)
}
/// SM4-CCM 解密(AEAD),先验证 tag 再返回明文
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ccm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
tag_len: usize,
) -> Result<Vec<u8>, Error> {
if ciphertext_with_tag.len() < tag_len {
return Err(Error::InvalidInputLength);
}
let ct = &ciphertext_with_tag[..ciphertext_with_tag.len() - tag_len];
let received_tag = &ciphertext_with_tag[ciphertext_with_tag.len() - tag_len..];
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let mut a0 = [0u8; 16];
a0[0] = 2u8;
a0[1..13].copy_from_slice(nonce);
let s0 = encrypt_block_raw(rk, &a0);
let mut plaintext = Vec::with_capacity(ct.len());
for (block_idx, chunk) in ct.chunks(16).enumerate() {
let mut a_i = a0;
let ctr_val = (block_idx as u32) + 1;
a_i[13] = (ctr_val >> 16) as u8;
a_i[14] = (ctr_val >> 8) as u8;
a_i[15] = ctr_val as u8;
let ks = encrypt_block_raw(rk, &a_i);
for (i, &b) in chunk.iter().enumerate() {
plaintext.push(b ^ ks[i]);
}
}
let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len)?;
let mut expected_tag = [0u8; 16];
for i in 0..tag_len { expected_tag[i] = t[i] ^ s0[i]; }
// Reason: 先验证后解密,防止选择密文攻击
if expected_tag[..tag_len].ct_eq(received_tag).unwrap_u8() == 0 {
return Err(Error::AuthTagMismatch);
}
Ok(plaintext)
}
+1
View File
@@ -18,6 +18,7 @@ pub mod hmac;
pub mod kx;
pub mod sign;
pub mod tls13;
pub(crate) mod aead;
pub mod verify;
/// 构造国密 `CryptoProvider`
+1 -1
View File
@@ -14,7 +14,7 @@ use rustls::error::Error;
use rustls::version::TLS13_VERSION;
use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls13CipherSuite};
use crate::sm4::{sm4_decrypt_ccm, sm4_decrypt_gcm, sm4_encrypt_ccm, sm4_encrypt_gcm};
use super::aead::{sm4_decrypt_ccm, sm4_decrypt_gcm, sm4_encrypt_ccm, sm4_encrypt_gcm};
// ── HKDF(零代码复用 rustls 内置 HkdfUsingHmac)──────────────────────────────
+42 -48
View File
@@ -44,6 +44,8 @@ fn p1(x: u32) -> u32 {
/// - 轮函数分两段(j=0..15 和 j=16..63),消除 ff/gg 中的 `if j < 16` 运行时分支
/// - T_j 常量使用预计算表,消除旋转运算
/// - W' 数组内联为 w[j] ^ w[j+4],避免额外分配
/// - `#[inline(always)]` 允许调用方(通常是循环)内联后做跨块优化
#[inline(always)]
pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) {
// ── 消息扩展 ─────────────────────────────────────────────────────────────
// W[0..15]: 直接从块加载(大端)
@@ -61,58 +63,50 @@ pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) {
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
// Reason: 将 64 轮分两段展开,消除 ff/gg/T 中的 if 分支。
// j = 0..15FF = x^y^zGG = x^y^z
for j in 0..16 {
let ss1 = a
.rotate_left(12)
.wrapping_add(e)
.wrapping_add(T[j])
.rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = (a ^ b ^ c)
.wrapping_add(d)
.wrapping_add(ss2)
.wrapping_add(w[j] ^ w[j + 4]);
let tt2 = (e ^ f ^ g)
.wrapping_add(h)
.wrapping_add(ss1)
.wrapping_add(w[j]);
d = c;
c = b.rotate_left(9);
b = a;
a = tt1;
h = g;
g = f.rotate_left(19);
f = e;
e = p0(tt2);
// 使用宏在编译期展开,避免循环计数器开销,使编译器能进行跨轮常量折叠和指令调度。
macro_rules! round_xor {
($j:expr) => {{
let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[$j]).rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = (a ^ b ^ c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[$j] ^ w[$j + 4]);
let tt2 = (e ^ f ^ g).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[$j]);
d = c; c = b.rotate_left(9); b = a; a = tt1;
h = g; g = f.rotate_left(19); f = e; e = p0(tt2);
}};
}
// j = 16..63FF = majority(x,y,z)GG = choice(x,y,z)
for j in 16..64 {
let ss1 = a
.rotate_left(12)
.wrapping_add(e)
.wrapping_add(T[j])
.rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = ((a & b) | (a & c) | (b & c))
.wrapping_add(d)
.wrapping_add(ss2)
.wrapping_add(w[j] ^ w[j + 4]);
let tt2 = ((e & f) | (!e & g))
.wrapping_add(h)
.wrapping_add(ss1)
.wrapping_add(w[j]);
d = c;
c = b.rotate_left(9);
b = a;
a = tt1;
h = g;
g = f.rotate_left(19);
f = e;
e = p0(tt2);
macro_rules! round_maj {
($j:expr) => {{
let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[$j]).rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = ((a & b) | (a & c) | (b & c)).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[$j] ^ w[$j + 4]);
let tt2 = ((e & f) | (!e & g)).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[$j]);
d = c; c = b.rotate_left(9); b = a; a = tt1;
h = g; g = f.rotate_left(19); f = e; e = p0(tt2);
}};
}
// j = 0..15FF = x^y^zGG = x^y^z
round_xor!(0); round_xor!(1); round_xor!(2); round_xor!(3);
round_xor!(4); round_xor!(5); round_xor!(6); round_xor!(7);
round_xor!(8); round_xor!(9); round_xor!(10); round_xor!(11);
round_xor!(12); round_xor!(13); round_xor!(14); round_xor!(15);
// j = 16..63FF = majority(x,y,z)GG = choice(x,y,z)
round_maj!(16); round_maj!(17); round_maj!(18); round_maj!(19);
round_maj!(20); round_maj!(21); round_maj!(22); round_maj!(23);
round_maj!(24); round_maj!(25); round_maj!(26); round_maj!(27);
round_maj!(28); round_maj!(29); round_maj!(30); round_maj!(31);
round_maj!(32); round_maj!(33); round_maj!(34); round_maj!(35);
round_maj!(36); round_maj!(37); round_maj!(38); round_maj!(39);
round_maj!(40); round_maj!(41); round_maj!(42); round_maj!(43);
round_maj!(44); round_maj!(45); round_maj!(46); round_maj!(47);
round_maj!(48); round_maj!(49); round_maj!(50); round_maj!(51);
round_maj!(52); round_maj!(53); round_maj!(54); round_maj!(55);
round_maj!(56); round_maj!(57); round_maj!(58); round_maj!(59);
round_maj!(60); round_maj!(61); round_maj!(62); round_maj!(63);
state[0] ^= a;
state[1] ^= b;
state[2] ^= c;
+10 -5
View File
@@ -39,13 +39,13 @@ pub const DIGEST_LEN: usize = 32;
#[derive(Clone)]
pub struct Sm3Hasher {
/// 当前状态(8 × u32
state: [u32; 8],
pub(super) state: [u32; 8],
/// 未处理的字节缓冲区(最多 64 字节)
buffer: [u8; 64],
pub(super) buffer: [u8; 64],
/// 缓冲区已填充字节数
buf_len: usize,
pub(super) buf_len: usize,
/// 已处理的总位数(用于最终填充)
bit_len: u64,
pub(super) bit_len: u64,
}
impl Sm3Hasher {
@@ -285,7 +285,12 @@ impl HmacSm3 {
impl zeroize::Zeroize for HmacSm3 {
fn zeroize(&mut self) {
self.opad_key.zeroize();
// inner 的 Sm3Hasher 不含密钥材料,无需特殊清零
// Reason: inner 在 new() 中已处理含密钥的 ipad 前缀,
// 其 state/buffer 字段间接含密钥材料,必须一并清零。
self.inner.state.zeroize();
self.inner.buffer.zeroize();
self.inner.buf_len = 0;
self.inner.bit_len = 0;
}
}
+4 -2
View File
@@ -425,7 +425,8 @@ impl Sm4Key {
store_block(block, &x);
}
/// 获取轮密钥引用(仅供 modes 子模块使用)
/// 获取轮密钥引用(仅供 rustls_provider::aead 使用)
#[cfg(feature = "rustls-provider")]
pub(crate) fn round_keys(&self) -> &[u32; 32] {
&self.rk
}
@@ -496,7 +497,8 @@ fn decrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) {
x.reverse();
}
/// 辅助:加密独立块(不缓存轮密钥,供 modes 一次性使用)
/// 辅助:加密独立块(供 rustls_provider::aead 使用)
#[cfg(feature = "rustls-provider")]
pub(crate) fn encrypt_block_raw(rk: &[u32; 32], block: &[u8; 16]) -> [u8; 16] {
let mut x = load_block(block);
encrypt_rounds(&mut x, rk);
+4 -4
View File
@@ -1,8 +1,8 @@
//! SM4 分组密码(GB/T 32907-2016
//! 实现见各子模块。
//!
//! 提供核心块加密功能。工作模式(CBC/CTR/GCM 等)请使用 RustCrypto 生态的
//! `cbc`、`ctr`、`aes-gcm` 等 crate 与 [`Sm4Key`] 组合使用。
mod cipher;
mod modes;
pub(crate) mod cipher;
pub use cipher::Sm4Key;
pub use modes::*;
-860
View File
@@ -1,860 +0,0 @@
//! SM4 分组模式(GB/T 32907-2016GB/T 17964-2021
//!
//! 支持:ECB、CBC、OFB、CFB、CTR、GCMAEAD)、CCMAEAD)、XTS
//!
//! # 安全说明
//!
//! - GCM/CCM 认证标签比较使用 `subtle::ConstantTimeEq`,防止时序侧信道
//! - CCM 严格遵循"先验证后解密"原则(Encrypt-then-MAC 的接收端验证)
//! - 所有密钥材料通过 [`Sm4Key`] 在 Drop 时自动清零
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use subtle::ConstantTimeEq;
use super::cipher::{encrypt_block_raw, Sm4Key};
// ── ECB ──────────────────────────────────────────────────────────────────────
/// SM4-ECB 加密(无填充,`data` 必须为 16 字节整倍数)
///
/// # 参数
/// - `key`: 16 字节密钥
/// - `data`: 明文(长度须为 16 的倍数)
///
/// # 返回
/// 密文字节向量
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ecb(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
data.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
sm4.encrypt_block(&mut block);
block
})
.collect()
}
/// SM4-ECB 解密(无填充,`data` 必须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ecb(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
data.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
sm4.decrypt_block(&mut block);
block
})
.collect()
}
// ── CBC ──────────────────────────────────────────────────────────────────────
/// SM4-CBC 加密(`plaintext.len()` 须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_cbc(key: &[u8; 16], iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut prev = *iv;
plaintext
.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
let len = chunk.len().min(16);
block[..len].copy_from_slice(&chunk[..len]);
for i in 0..16 {
block[i] ^= prev[i];
}
sm4.encrypt_block(&mut block);
prev = block;
block
})
.collect()
}
/// SM4-CBC 解密(`ciphertext.len()` 须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_cbc(key: &[u8; 16], iv: &[u8; 16], ciphertext: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut prev = *iv;
ciphertext
.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
let ct = block;
sm4.decrypt_block(&mut block);
for i in 0..16 {
block[i] ^= prev[i];
}
prev = ct;
block
})
.collect()
}
// ── OFB ──────────────────────────────────────────────────────────────────────
/// SM4-OFB 加密/解密(自反模式,加解密逻辑相同)
#[cfg(feature = "alloc")]
pub fn sm4_crypt_ofb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut feedback = *iv;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
sm4.encrypt_block(&mut feedback);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ feedback[i]);
}
}
out
}
// ── CFB ──────────────────────────────────────────────────────────────────────
/// SM4-CFB 加密
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_cfb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut feedback = *iv;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut ks = feedback;
sm4.encrypt_block(&mut ks);
let mut ct_block = [0u8; 16];
for (i, &b) in chunk.iter().enumerate() {
ct_block[i] = b ^ ks[i];
}
feedback = ct_block;
out.extend_from_slice(&ct_block[..chunk.len()]);
}
out
}
/// SM4-CFB 解密
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_cfb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut feedback = *iv;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut ks = feedback;
sm4.encrypt_block(&mut ks);
let mut ct_block = [0u8; 16];
ct_block[..chunk.len()].copy_from_slice(chunk);
// Reason: CFB 解密中 feedback 使用密文块,而非明文块
feedback = ct_block;
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
}
out
}
// ── CTR ──────────────────────────────────────────────────────────────────────
/// CTR 计数器递增(全 128 位大端)
#[inline]
fn ctr_inc(counter: &mut [u8; 16]) {
for i in (0..16).rev() {
counter[i] = counter[i].wrapping_add(1);
if counter[i] != 0 {
break;
}
}
}
/// SM4-CTR 加密/解密(自反模式)
#[cfg(feature = "alloc")]
pub fn sm4_crypt_ctr(key: &[u8; 16], nonce: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut counter = *nonce;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut ks = counter;
sm4.encrypt_block(&mut ks);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
ctr_inc(&mut counter);
}
out
}
// ── GCM ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间,u64 优化)
///
/// # 安全性
/// 使用掩码算术替代秘密依赖的条件分支,消除时序侧信道:
/// - `mask_xi`:由当前标量位生成的 u64 全掩码,替代 `if bit == 1`
/// - `reduce_mask`:由 LSB 生成的 u64 全掩码,替代 `if lsb == 1`
///
/// # 性能优化
/// 将内部状态从 `[u8; 16]` 改为 `[u64; 2]`(大端),使每次迭代的
/// XOR/移位/规约从 16 次字节操作降至 ~6 次 64 位操作,约 4-6× 提速。
///
/// Reason: GHASH 密钥 H 来自 SM4_K(0^128),属秘密值;原条件分支泄露 H 的汉明重量,
/// 是 cache-timing 和 branch-timing 攻击的经典目标(参见 Bricout 等 2016)。
/// u64 向量化保持完全常量时间,同时大幅减少指令数。
fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
// Reason: 将 16 字节表示为 2 个大端 u64,便于用 64 位操作替代逐字节循环,
// XOR/移位从 16 次字节操作缩减至 2 次 u64 操作,指令数降低约 8×。
let mut z = [0u64; 2];
let mut v = [
u64::from_be_bytes(y[0..8].try_into().unwrap()),
u64::from_be_bytes(y[8..16].try_into().unwrap()),
];
for &byte_xi in x.iter() {
for bit_idx in (0..8).rev() {
// Reason: 0u64.wrapping_sub(1) = 0xFFFF...wrapping_sub(0) = 0x0000...
// 单次 u64 掩码覆盖原来 16 次 u8 掩码操作
let mask = 0u64.wrapping_sub(((byte_xi >> bit_idx) & 1) as u64);
z[0] ^= v[0] & mask;
z[1] ^= v[1] & mask;
// GF(2^128) 右移 1 位(= 乘以 x),带规约多项式 x^128+x^7+x^2+x+1
// Reason: v[0] 的 bit 0= 大端第 64 位)移入 v[1] 的 bit 63
// v[1] 的 bit 0= GF 元素 x^0 系数)移出后触发规约。
let lsb = v[1] & 1;
let carry = v[0] & 1;
v[0] >>= 1;
v[1] = (v[1] >> 1) | (carry << 63);
// Reason: 规约项 0xE1_00...00 对应 x^7+x^2+x+1 写入最高字节(v[0] MSB 端),
// 掩码替代 if lsb,执行路径完全相同
let reduce_mask = 0u64.wrapping_sub(lsb);
v[0] ^= 0xE100_0000_0000_0000u64 & reduce_mask;
}
}
let mut out = [0u8; 16];
out[0..8].copy_from_slice(&z[0].to_be_bytes());
out[8..16].copy_from_slice(&z[1].to_be_bytes());
out
}
/// GHASH 认证函数(NIST SP 800-38D §6.4
fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] {
let mut y = [0u8; 16];
for chunk in aad.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
y[i] ^= block[i];
}
y = gf128_mul(&y, h);
}
for chunk in ciphertext.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
y[i] ^= block[i];
}
y = gf128_mul(&y, h);
}
let mut len_block = [0u8; 16];
len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes());
for i in 0..16 {
y[i] ^= len_block[i];
}
gf128_mul(&y, h)
}
/// GCM 计数器递增(仅最后 4 字节,GCM 标准)
#[inline]
fn gcm_ctr_inc(counter: &mut [u8; 16]) {
// Reason: GCM 规范中 J0 的计数器字段只占最后 4 字节(大端 32 位)
for i in (12..16).rev() {
counter[i] = counter[i].wrapping_add(1);
if counter[i] != 0 {
break;
}
}
}
/// SM4-GCM 加密(AEAD
///
/// # 参数
/// - `key`: 16 字节密钥
/// - `nonce`: 12 字节 nonceGCM 标准推荐)
/// - `aad`: 附加认证数据(不加密,但参与认证)
/// - `plaintext`: 明文
///
/// # 返回
/// `(密文, 16字节认证标签)`
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_gcm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> (Vec<u8>, [u8; 16]) {
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let h = encrypt_block_raw(rk, &[0u8; 16]);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut ctr = j0;
gcm_ctr_inc(&mut ctr);
let ciphertext: Vec<u8> = {
let mut out = Vec::with_capacity(plaintext.len());
let mut counter = ctr;
for chunk in plaintext.chunks(16) {
let ks = encrypt_block_raw(rk, &counter);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
gcm_ctr_inc(&mut counter);
}
out
};
let ghash_val = ghash(&h, aad, &ciphertext);
let ej0 = encrypt_block_raw(rk, &j0);
let mut tag = [0u8; 16];
for i in 0..16 {
tag[i] = ghash_val[i] ^ ej0[i];
}
(ciphertext, tag)
}
/// SM4-GCM 解密(AEAD
///
/// **先验证认证标签,验证通过后才解密。**
///
/// # 错误
/// 返回 [`crate::error::Error::AuthTagMismatch`] 当标签验证失败。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_gcm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8; 16],
) -> Result<Vec<u8>, crate::error::Error> {
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let h = encrypt_block_raw(rk, &[0u8; 16]);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
// Reason: 先验证 tag 再解密,防止 padding oracle 和选择密文攻击
let ghash_val = ghash(&h, aad, ciphertext);
let ej0 = encrypt_block_raw(rk, &j0);
let mut expected_tag = [0u8; 16];
for i in 0..16 {
expected_tag[i] = ghash_val[i] ^ ej0[i];
}
// 常量时间 tag 比较,防止时序侧信道
if expected_tag.ct_eq(tag).unwrap_u8() == 0 {
return Err(crate::error::Error::AuthTagMismatch);
}
let mut ctr = j0;
gcm_ctr_inc(&mut ctr);
let mut plaintext = Vec::with_capacity(ciphertext.len());
let mut counter = ctr;
for chunk in ciphertext.chunks(16) {
let ks = encrypt_block_raw(rk, &counter);
for (i, &b) in chunk.iter().enumerate() {
plaintext.push(b ^ ks[i]);
}
gcm_ctr_inc(&mut counter);
}
Ok(plaintext)
}
// ── CCM ──────────────────────────────────────────────────────────────────────
/// 构造 CCM CBC-MACRFC 3610
///
/// # 错误
/// `aad` 超过 510 字节时返回 `Error::InvalidInputLength`(当前实现仅支持 2 字节长度编码)。
fn ccm_cbc_mac(
rk: &[u32; 32],
nonce: &[u8; 12],
aad: &[u8],
message: &[u8],
tag_len: usize,
) -> Result<[u8; 16], crate::error::Error> {
let q = 3usize; // nonce=12B 时 q=15-12=3
let has_aad = !aad.is_empty();
let flags = ((has_aad as u8) << 6) | (((tag_len - 2) / 2) as u8) << 3 | (q as u8 - 1);
let mut b0 = [0u8; 16];
b0[0] = flags;
b0[1..13].copy_from_slice(nonce);
let msg_len = message.len() as u32;
b0[13] = (msg_len >> 16) as u8;
b0[14] = (msg_len >> 8) as u8;
b0[15] = msg_len as u8;
let mut x = encrypt_block_raw(rk, &b0);
if has_aad {
let aad_len = aad.len();
// Reason: CCM AAD 前缀 2 字节长度 + AAD 数据,补零至 16 字节对齐
let prefix_len = 2 + aad_len;
let padded_len = prefix_len.div_ceil(16) * 16;
let mut aad_buf = [0u8; 512]; // 足够大的栈缓冲区(支持 AAD ≤ 510 字节)
// Reason: 超过 510 字节需要 4 字节长度编码(RFC 3610 §2.2),
// 当前实现仅支持 2 字节编码,超限时必须拒绝而非静默跳过 AAD。
// 静默跳过会导致认证标签不包含 AAD,攻击者可随意篡改 AAD 而不被检测。
if prefix_len > aad_buf.len() {
return Err(crate::error::Error::InvalidInputLength);
}
aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes());
aad_buf[2..2 + aad_len].copy_from_slice(aad);
for chunk in aad_buf[..padded_len].chunks(16) {
let block: [u8; 16] = chunk.try_into().unwrap();
for i in 0..16 {
x[i] ^= block[i];
}
x = encrypt_block_raw(rk, &x);
}
}
for chunk in message.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
x[i] ^= block[i];
}
x = encrypt_block_raw(rk, &x);
}
Ok(x)
}
/// SM4-CCM 加密(AEAD
///
/// # 参数
/// - `nonce`: 12 字节
/// - `tag_len`: 认证标签长度,须为 4/6/8/10/12/14/16 之一
///
/// # 返回
/// 密文 || 认证标签(`tag_len` 字节)
///
/// # 错误
/// - `aad` 超过 510 字节时返回 `Error::InvalidInputLength`
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
tag_len: usize,
) -> Result<Vec<u8>, crate::error::Error> {
assert!(
(4..=16).contains(&tag_len) && tag_len % 2 == 0,
"CCM tag_len 须为 4~16 的偶数"
);
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len)?;
let mut a0 = [0u8; 16];
a0[0] = 2u8; // q-1 = 3-1 = 2
a0[1..13].copy_from_slice(nonce);
let s0 = encrypt_block_raw(rk, &a0);
let mut enc_tag = [0u8; 16];
for i in 0..tag_len {
enc_tag[i] = t[i] ^ s0[i];
}
let mut out = Vec::with_capacity(plaintext.len() + tag_len);
for (block_idx, chunk) in plaintext.chunks(16).enumerate() {
let mut a_i = a0;
let ctr_val = (block_idx as u32) + 1;
a_i[13] = (ctr_val >> 16) as u8;
a_i[14] = (ctr_val >> 8) as u8;
a_i[15] = ctr_val as u8;
let ks = encrypt_block_raw(rk, &a_i);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
}
out.extend_from_slice(&enc_tag[..tag_len]);
Ok(out)
}
/// SM4-CCM 解密(AEAD
///
/// **先验证认证标签,验证通过后才解密。**
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ccm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
tag_len: usize,
) -> Result<Vec<u8>, crate::error::Error> {
if ciphertext_with_tag.len() < tag_len {
return Err(crate::error::Error::InvalidInputLength);
}
let ct = &ciphertext_with_tag[..ciphertext_with_tag.len() - tag_len];
let received_tag = &ciphertext_with_tag[ciphertext_with_tag.len() - tag_len..];
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let mut a0 = [0u8; 16];
a0[0] = 2u8;
a0[1..13].copy_from_slice(nonce);
let s0 = encrypt_block_raw(rk, &a0);
// Step 1: CTR 解密密文(得到候选明文)
let mut plaintext = Vec::with_capacity(ct.len());
for (block_idx, chunk) in ct.chunks(16).enumerate() {
let mut a_i = a0;
let ctr_val = (block_idx as u32) + 1;
a_i[13] = (ctr_val >> 16) as u8;
a_i[14] = (ctr_val >> 8) as u8;
a_i[15] = ctr_val as u8;
let ks = encrypt_block_raw(rk, &a_i);
for (i, &b) in chunk.iter().enumerate() {
plaintext.push(b ^ ks[i]);
}
}
// Step 2: 对候选明文重新计算 CBC-MAC
let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len)?;
let mut expected_tag = [0u8; 16];
for i in 0..tag_len {
expected_tag[i] = t[i] ^ s0[i];
}
// Step 3: 常量时间比较,验证通过才返回明文
// Reason: 先验证后解密,防止选择密文攻击
if expected_tag[..tag_len].ct_eq(received_tag).unwrap_u8() == 0 {
return Err(crate::error::Error::AuthTagMismatch);
}
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 更新)
fn xts_mul_alpha(tweak: &mut [u8; 16]) {
// Reason: XTS 使用反射位序的 GF(2^128),对应右移 + 0xE1 规约
let carry = tweak[15] & 1;
for i in (1..16).rev() {
tweak[i] = (tweak[i] >> 1) | ((tweak[i - 1] & 1) << 7);
}
tweak[0] >>= 1;
if carry == 1 {
tweak[0] ^= 0xE1;
}
}
/// SM4-XTS 加密(磁盘加密模式,GB/T 17964-2021
///
/// # 参数
/// - `key1`: 数据加密密钥(16 字节)
/// - `key2`: tweak 加密密钥(16 字节)
/// - `tweak_sector`: 扇区号(16 字节,通常为扇区编号的小端表示)
/// - `data`: 明文(须为 16 字节整倍数,不支持非对齐输入)
///
/// # 错误
/// `data` 为空或长度不是 16 的整倍数时返回 `Error::InvalidInputLength`。
///
/// # 注意
/// XTS 的 ciphertext stealing(非对齐末尾块处理)超出本实现范围,
/// 调用方须保证输入对齐;非对齐时须先在应用层填充后再调用。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_xts(
key1: &[u8; 16],
key2: &[u8; 16],
tweak_sector: &[u8; 16],
data: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
// Reason: 非对齐输入在旧实现中被静默丢弃(最后不足 16 字节块跳过),
// 导致密文比明文短而调用方无感知。拒绝非对齐输入防止数据静默丢失。
if data.is_empty() || data.len() % 16 != 0 {
return Err(crate::error::Error::InvalidInputLength);
}
let sm4_1 = Sm4Key::new(key1);
let sm4_2 = Sm4Key::new(key2);
let mut tweak = *tweak_sector;
sm4_2.encrypt_block(&mut tweak);
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.encrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
}
Ok(out)
}
/// SM4-XTS 解密(磁盘加密模式,GB/T 17964-2021
///
/// # 错误
/// `data` 为空或长度不是 16 的整倍数时返回 `Error::InvalidInputLength`。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_xts(
key1: &[u8; 16],
key2: &[u8; 16],
tweak_sector: &[u8; 16],
data: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
// Reason: 同 sm4_encrypt_xts,拒绝非对齐输入防止数据静默丢失。
if data.is_empty() || data.len() % 16 != 0 {
return Err(crate::error::Error::InvalidInputLength);
}
let sm4_1 = Sm4Key::new(key1);
let sm4_2 = Sm4Key::new(key2);
let mut tweak = *tweak_sector;
sm4_2.encrypt_block(&mut tweak);
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.decrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
}
Ok(out)
}
// ── 测试 ──────────────────────────────────────────────────────────────────────
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::*;
/// GB/T 32907-2016 附录 BCBC 模式测试向量
#[test]
fn test_cbc_vector() {
let key = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let iv = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let plain = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let ct = sm4_encrypt_cbc(&key, &iv, &plain);
let pt = sm4_decrypt_cbc(&key, &iv, &ct);
assert_eq!(pt, plain, "CBC 往返解密失败");
}
/// GCM 加解密往返测试
#[test]
fn test_gcm_roundtrip() {
let key = [0u8; 16];
let nonce = [1u8; 12];
let aad = b"additional data";
let plain = b"hello gcm world!";
let (ct, tag) = sm4_encrypt_gcm(&key, &nonce, aad, plain);
let pt = sm4_decrypt_gcm(&key, &nonce, aad, &ct, &tag).unwrap();
assert_eq!(pt, plain, "GCM 往返解密失败");
}
/// GCM tag 篡改检测
#[test]
fn test_gcm_tag_tamper() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let (ct, mut tag) = sm4_encrypt_gcm(&key, &nonce, b"", b"secret");
tag[0] ^= 1;
assert!(
sm4_decrypt_gcm(&key, &nonce, b"", &ct, &tag).is_err(),
"篡改 tag 后应返回错误"
);
}
/// CCM 加解密往返测试
#[test]
fn test_ccm_roundtrip() {
let key = [0u8; 16];
let nonce = [2u8; 12];
let aad = b"ccm aad";
let plain = b"ccm plaintext!!!";
let ct = sm4_encrypt_ccm(&key, &nonce, aad, plain, 16).unwrap();
let pt = sm4_decrypt_ccm(&key, &nonce, aad, &ct, 16).unwrap();
assert_eq!(pt, plain, "CCM 往返解密失败");
}
/// CCM tag 篡改检测(先验证后解密原则验证)
#[test]
fn test_ccm_tag_tamper() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let mut ct = sm4_encrypt_ccm(&key, &nonce, b"", b"secret data here", 16).unwrap();
// 篡改 tag(最后 16 字节)
let last = ct.len() - 1;
ct[last] ^= 1;
assert!(
sm4_decrypt_ccm(&key, &nonce, b"", &ct, 16).is_err(),
"篡改 CCM tag 后应返回错误"
);
}
/// CCM AAD 超限应返回错误(而非静默跳过)
#[test]
fn test_ccm_aad_too_long() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let big_aad = [0u8; 511]; // 超过 510 字节限制
assert!(
sm4_encrypt_ccm(&key, &nonce, &big_aad, b"data", 16).is_err(),
"AAD 超过 510 字节时应返回 InvalidInputLength"
);
}
/// XTS 加解密往返测试
#[test]
fn test_xts_roundtrip() {
let key1 = [0x11u8; 16];
let key2 = [0x22u8; 16];
let tweak = [0u8; 16];
let plain = [0x42u8; 32]; // 2 个 16 字节块
let ct = sm4_encrypt_xts(&key1, &key2, &tweak, &plain).unwrap();
let pt = sm4_decrypt_xts(&key1, &key2, &tweak, &ct).unwrap();
assert_eq!(pt, plain, "XTS 往返解密失败");
}
/// XTS 非对齐数据应返回错误
#[test]
fn test_xts_non_aligned_rejected() {
let key1 = [0u8; 16];
let key2 = [0u8; 16];
let tweak = [0u8; 16];
// 空输入
assert!(
sm4_encrypt_xts(&key1, &key2, &tweak, b"").is_err(),
"空输入应返回 InvalidInputLength"
);
// 非 16 倍数
assert!(
sm4_encrypt_xts(&key1, &key2, &tweak, b"not-aligned-data").is_ok(),
"正好 16 字节不应返回错误"
);
assert!(
sm4_encrypt_xts(&key1, &key2, &tweak, &[0u8; 17]).is_err(),
"17 字节应返回 InvalidInputLength"
);
assert!(
sm4_decrypt_xts(&key1, &key2, &tweak, &[0u8; 15]).is_err(),
"15 字节应返回 InvalidInputLength"
);
}
/// OFB 自反性验证
#[test]
fn test_ofb_self_inverse() {
let key = [0xABu8; 16];
let iv = [0x12u8; 16];
let plain = b"ofb test message";
let ct = sm4_crypt_ofb(&key, &iv, plain);
let pt = sm4_crypt_ofb(&key, &iv, &ct);
assert_eq!(pt, plain, "OFB 应为自反模式");
}
}
+87
View File
@@ -0,0 +1,87 @@
//! SM2 属性测试 / Property-based tests for SM2
//!
//! 使用 proptest 验证:任意随机私钥生成的签名均可被对应公钥验证。
//! Tests that for arbitrary random key bytes, sign-then-verify always succeeds.
use libsmx::sm2::{get_e, get_z, sign, verify, PrivateKey, DEFAULT_ID};
use proptest::prelude::*;
proptest! {
/// 任意合法私钥:签名后验签必须通过
///
/// Reason: 使用原始字节数组作为策略输入(proptest 只需字节组具有 Debug),
/// 在测试体内调用 from_bytes 过滤非法值,合法时执行验证逻辑。
#[test]
fn prop_sign_verify_roundtrip(
key_bytes in prop::array::uniform32(1u8..=0xFFu8),
msg in prop::collection::vec(any::<u8>(), 0..256),
) {
let pri_key = match PrivateKey::from_bytes(&key_bytes) {
Ok(k) => k,
Err(_) => return Ok(()), // 非法私钥直接跳过
};
let pub_key = pri_key.public_key();
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, &msg);
let mut rng = rand::thread_rng();
let sig = sign(&e, &pri_key, &mut rng);
prop_assert!(verify(&e, &pub_key, &sig).is_ok(),
"sign-then-verify failed for a valid key");
}
/// 不同消息的签名不能交叉验证
#[test]
fn prop_different_msg_rejected(
key_bytes in prop::array::uniform32(1u8..=0xFFu8),
msg1 in prop::collection::vec(any::<u8>(), 1..64),
msg2 in prop::collection::vec(any::<u8>(), 1..64),
) {
prop_assume!(msg1 != msg2);
let pri_key = match PrivateKey::from_bytes(&key_bytes) {
Ok(k) => k,
Err(_) => return Ok(()),
};
let pub_key = pri_key.public_key();
let z = get_z(DEFAULT_ID, &pub_key);
let e1 = get_e(&z, &msg1);
let e2 = get_e(&z, &msg2);
let mut rng = rand::thread_rng();
let sig1 = sign(&e1, &pri_key, &mut rng);
// 用 msg1 的签名验证 msg2 应失败
prop_assert!(verify(&e2, &pub_key, &sig1).is_err(),
"signature for msg1 must not verify msg2");
}
/// 篡改签名任意字节后验签应失败(或恰好产生另一合法签名,极罕见)
#[test]
fn prop_tampered_sig_no_panic(
key_bytes in prop::array::uniform32(1u8..=0xFFu8),
msg in prop::collection::vec(any::<u8>(), 1..128),
tamper_idx in 0usize..64,
tamper_xor in 1u8..=0xFFu8,
) {
let pri_key = match PrivateKey::from_bytes(&key_bytes) {
Ok(k) => k,
Err(_) => return Ok(()),
};
let pub_key = pri_key.public_key();
let z = get_z(DEFAULT_ID, &pub_key);
let e = get_e(&z, &msg);
let mut rng = rand::thread_rng();
let mut sig = sign(&e, &pri_key, &mut rng);
sig[tamper_idx] ^= tamper_xor;
// 仅断言不 panic,不断言一定失败(极罕见情况下可能仍然合法)
let _ = verify(&e, &pub_key, &sig);
}
}
+34 -18
View File
@@ -1,9 +1,12 @@
//! SM4 国标测试向量(GB/T 32907-2016 附录 A
//!
//! A.1 示例1:单次 ECB 加密
//! A.1 示例1:单次 ECB 加密(单块)
//! A.2 示例21,000,000 次迭代 ECB 加密(验证算法迭代正确性)
//!
//! 注:原来使用 `sm4_encrypt_ecb` 的向量测试已迁移为直接使用 `Sm4Key::encrypt_block`
//! 与 RustCrypto 生态的 `sm4` 子 crate 行为一致。
use libsmx::sm4::{sm4_decrypt_ecb, sm4_encrypt_ecb};
use libsmx::sm4::Sm4Key;
/// GB/T 32907-2016 附录 A.1
/// 密钥:0123456789abcdeffedcba9876543210
@@ -12,15 +15,20 @@ use libsmx::sm4::{sm4_decrypt_ecb, sm4_encrypt_ecb};
#[test]
fn test_sm4_ecb_vector_a1_single() {
let key = hex::decode("0123456789abcdeffedcba9876543210").unwrap();
let plaintext = hex::decode("0123456789abcdeffedcba9876543210").unwrap();
let expected_ct = hex::decode("681edf34d206965e86b3e94f536e4246").unwrap();
let key_arr: [u8; 16] = key.try_into().unwrap();
let ct = sm4_encrypt_ecb(&key_arr, &plaintext);
assert_eq!(ct, expected_ct, "GB/T 32907 附录 A.1 加密失败");
let sm4 = Sm4Key::new(&key_arr);
let pt = sm4_decrypt_ecb(&key_arr, &ct);
assert_eq!(pt, plaintext, "GB/T 32907 附录 A.1 解密失败");
let mut block = hex::decode("0123456789abcdeffedcba9876543210").unwrap();
let block_arr: &mut [u8; 16] = block.as_mut_slice().try_into().unwrap();
sm4.encrypt_block(block_arr);
let expected = hex::decode("681edf34d206965e86b3e94f536e4246").unwrap();
assert_eq!(block_arr, expected.as_slice(), "GB/T 32907 附录 A.1 加密失败");
sm4.decrypt_block(block_arr);
let plaintext = hex::decode("0123456789abcdeffedcba9876543210").unwrap();
assert_eq!(block_arr, plaintext.as_slice(), "GB/T 32907 附录 A.1 解密失败");
}
/// GB/T 32907-2016 附录 A.2
@@ -28,28 +36,36 @@ fn test_sm4_ecb_vector_a1_single() {
/// 明文:0123456789abcdeffedcba9876543210(反复迭代 1,000,000 次)
/// 密文:595298c7c6fd271f0402f804c33d3f66
#[test]
#[ignore = "slow (1M iterations)"]
fn test_sm4_ecb_vector_a2_million_iterations() {
let key: [u8; 16] = hex::decode("0123456789abcdeffedcba9876543210")
.unwrap()
.try_into()
.unwrap();
let sm4 = Sm4Key::new(&key);
let mut data: Vec<u8> = hex::decode("0123456789abcdeffedcba9876543210").unwrap();
let mut block: [u8; 16] = hex::decode("0123456789abcdeffedcba9876543210")
.unwrap()
.try_into()
.unwrap();
for _ in 0..1_000_000 {
data = sm4_encrypt_ecb(&key, &data);
sm4.encrypt_block(&mut block);
}
let expected = hex::decode("595298c7c6fd271f0402f804c33d3f66").unwrap();
assert_eq!(data, expected, "GB/T 32907 附录 A.2 百万次迭代失败");
assert_eq!(&block, expected.as_slice(), "GB/T 32907 附录 A.2 百万次迭代失败");
}
/// ECB 解密是加密的逆操作(往返测试
/// 加解密往返测试
#[test]
fn test_sm4_ecb_roundtrip() {
fn test_sm4_block_roundtrip() {
let key = [0x01u8; 16];
let plaintext = b"SM4 ECB test!!!\x00";
let ct = sm4_encrypt_ecb(&key, plaintext);
let pt = sm4_decrypt_ecb(&key, &ct);
assert_eq!(pt.as_slice(), plaintext.as_slice());
let sm4 = Sm4Key::new(&key);
let plaintext = *b"SM4 ECB test!!!!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let mut block: [u8; 16] = plaintext[..16].try_into().unwrap();
sm4.encrypt_block(&mut block);
assert_ne!(block, plaintext[..16], "密文应与明文不同");
sm4.decrypt_block(&mut block);
assert_eq!(block, plaintext[..16], "解密后应恢复原文");
}