Compare commits
1 Commits
main
...
rustcrypto
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ca734d0d |
+108
-7
@@ -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: 检查 sm3(no_std,无 alloc)
|
||||
run: cargo check -p sm3 --target ${{ matrix.target }} --no-default-features
|
||||
|
||||
- name: 检查 sm4(no_std,无 alloc)
|
||||
run: cargo check -p sm4 --target ${{ matrix.target }} --no-default-features
|
||||
|
||||
- name: 检查 sm2(no_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: 只对纯算法 crate(sm3/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
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
/reference
|
||||
/reference
|
||||
fuzz/target/
|
||||
Generated
+166
-5
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Fuzz target: SM4 加密后解密必须还原原始数据
|
||||
//!
|
||||
//! 验证对任意 key + block,encrypt 后 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");
|
||||
});
|
||||
@@ -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
@@ -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.
|
||||
|
||||
[](https://crates.io/crates/sm2)
|
||||
[](https://docs.rs/sm2)
|
||||
[](../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\|\|s,64 字节)/ 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-2016:SM2 公钥密码算法 第1部分:总则
|
||||
- GB/T 32918.2-2016:SM2 公钥密码算法 第2部分:数字签名算法
|
||||
- GB/T 32918.3-2016:SM2 公钥密码算法 第3部分:密钥交换协议
|
||||
- GB/T 32918.4-2016:SM2 公钥密码算法 第4部分:公钥加密算法
|
||||
- GB/T 32918.5-2017:SM2 公钥密码算法 第5部分:参数定义
|
||||
+533
@@ -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 = 0x02,length 占 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 length(inner < 256 字节)
|
||||
der.extend_from_slice(&r_enc);
|
||||
der.extend_from_slice(&s_enc);
|
||||
der
|
||||
}
|
||||
|
||||
/// 将 DER 编码签名解码为原始 `r||s`(64 字节)
|
||||
///
|
||||
/// # 错误
|
||||
/// 格式不合法时返回 `Error::InvalidSignature`
|
||||
pub fn sig_from_der(der: &[u8]) -> Result<[u8; 64], Error> {
|
||||
let err = || Error::InvalidSignature;
|
||||
|
||||
// SEQUENCE tag
|
||||
let (tag, rest) = split_first(der).ok_or_else(err)?;
|
||||
if *tag != 0x30 {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
// SEQUENCE length
|
||||
let (seq_len, rest) = split_first(rest).ok_or_else(err)?;
|
||||
let seq_len = *seq_len as usize;
|
||||
if rest.len() < seq_len {
|
||||
return Err(err());
|
||||
}
|
||||
let body = &rest[..seq_len];
|
||||
|
||||
// 解析 r
|
||||
let (r_bytes, body) = decode_integer(body).ok_or_else(err)?;
|
||||
|
||||
// 解析 s
|
||||
let (s_bytes, body) = decode_integer(body).ok_or_else(err)?;
|
||||
|
||||
// 不应有多余数据
|
||||
if !body.is_empty() {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
// r 和 s 都必须是正整数,不超过 32 字节
|
||||
if r_bytes.is_empty() || r_bytes.len() > 33 || s_bytes.is_empty() || s_bytes.len() > 33 {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
let mut raw = [0u8; 64];
|
||||
// Reason: DER INTEGER 可能有前缀 0x00(最高位保护),去除后左对齐写入 32 字节槽
|
||||
let r_stripped = strip_leading_zero(r_bytes);
|
||||
let s_stripped = strip_leading_zero(s_bytes);
|
||||
if r_stripped.len() > 32 || s_stripped.len() > 32 {
|
||||
return Err(err());
|
||||
}
|
||||
let r_off = 32 - r_stripped.len();
|
||||
let s_off = 32 - s_stripped.len();
|
||||
raw[r_off..32].copy_from_slice(r_stripped);
|
||||
raw[32 + s_off..64].copy_from_slice(s_stripped);
|
||||
|
||||
Ok(raw)
|
||||
}
|
||||
|
||||
// ── 内部辅助 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 将 32 字节大端整数编码为 DER INTEGER(带 tag 0x02 和 length)
|
||||
#[cfg(feature = "alloc")]
|
||||
fn encode_integer(bytes: &[u8]) -> Vec<u8> {
|
||||
// 去除前导零(至少保留 1 字节)
|
||||
let start = bytes
|
||||
.iter()
|
||||
.position(|&b| b != 0)
|
||||
.unwrap_or(bytes.len() - 1);
|
||||
let val = &bytes[start..];
|
||||
|
||||
// 最高位为 1 时需补 0x00,防止被解析为负数
|
||||
let needs_pad = val[0] & 0x80 != 0;
|
||||
let val_len = val.len() + if needs_pad { 1 } else { 0 };
|
||||
|
||||
let mut enc = Vec::with_capacity(2 + val_len);
|
||||
enc.push(0x02); // INTEGER tag
|
||||
enc.push(val_len as u8); // length
|
||||
if needs_pad {
|
||||
enc.push(0x00);
|
||||
}
|
||||
enc.extend_from_slice(val);
|
||||
enc
|
||||
}
|
||||
|
||||
/// 从字节流中解析一个 DER INTEGER,返回 (value_bytes, 剩余字节)
|
||||
fn decode_integer(data: &[u8]) -> Option<(&[u8], &[u8])> {
|
||||
let (tag, rest) = split_first(data)?;
|
||||
if *tag != 0x02 {
|
||||
return None;
|
||||
}
|
||||
let (len, rest) = split_first(rest)?;
|
||||
let len = *len as usize;
|
||||
if rest.len() < len {
|
||||
return None;
|
||||
}
|
||||
Some((&rest[..len], &rest[len..]))
|
||||
}
|
||||
|
||||
/// 去除前导 0x00 字节
|
||||
fn strip_leading_zero(bytes: &[u8]) -> &[u8] {
|
||||
match bytes.iter().position(|&b| b != 0) {
|
||||
Some(i) => &bytes[i..],
|
||||
None => &bytes[bytes.len().saturating_sub(1)..], // 全零时保留末字节
|
||||
}
|
||||
}
|
||||
|
||||
fn split_first(data: &[u8]) -> Option<(&u8, &[u8])> {
|
||||
data.split_first()
|
||||
}
|
||||
|
||||
// ── DER 长度解码 ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// 解析 DER 长度字段,返回 (length, 剩余字节)
|
||||
///
|
||||
/// 支持:单字节(< 0x80)、两字节(0x81 nn)、三字节(0x82 nn nn)
|
||||
fn parse_length(data: &[u8]) -> Option<(usize, &[u8])> {
|
||||
let (first, rest) = data.split_first()?;
|
||||
if *first < 0x80 {
|
||||
// Reason: 最高位为 0 时,本字节直接表示长度
|
||||
Some((*first as usize, rest))
|
||||
} else if *first == 0x81 {
|
||||
let (len, rest) = rest.split_first()?;
|
||||
Some((*len as usize, rest))
|
||||
} else if *first == 0x82 {
|
||||
if rest.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let len = (rest[0] as usize) << 8 | rest[1] as usize;
|
||||
Some((len, &rest[2..]))
|
||||
} else {
|
||||
// 不支持更长或不定长编码
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析一个 TLV(tag-length-value),返回 (value_bytes, 剩余字节)
|
||||
fn parse_tlv(data: &[u8], expected_tag: u8) -> Option<(&[u8], &[u8])> {
|
||||
let (tag, rest) = data.split_first()?;
|
||||
if *tag != expected_tag {
|
||||
return None;
|
||||
}
|
||||
let (len, rest) = parse_length(rest)?;
|
||||
if rest.len() < len {
|
||||
return None;
|
||||
}
|
||||
Some((&rest[..len], &rest[len..]))
|
||||
}
|
||||
|
||||
// ── 私钥 DER 解析 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// 从 SEC1 DER 解析 SM2 私钥(RFC 5915)
|
||||
///
|
||||
/// 格式:
|
||||
/// ```text
|
||||
/// ECPrivateKey ::= SEQUENCE {
|
||||
/// version INTEGER { ecPrivkeyVer1(1) },
|
||||
/// privateKey OCTET STRING, -- 32 字节原始私钥
|
||||
/// [0] ECParameters OPTIONAL,
|
||||
/// [1] BIT STRING OPTIONAL
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # 错误
|
||||
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
|
||||
pub fn private_key_from_sec1_der(der: &[u8]) -> Result<PrivateKey, Error> {
|
||||
let err = || Error::InvalidPrivateKey;
|
||||
|
||||
// 解析外层 SEQUENCE
|
||||
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
|
||||
|
||||
// version INTEGER,值应为 1(ecPrivkeyVer1)
|
||||
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
|
||||
if ver_bytes != [0x01] {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
// privateKey OCTET STRING(32 字节)
|
||||
let (key_bytes, _rest) = parse_tlv(rest, 0x04).ok_or_else(err)?;
|
||||
if key_bytes.len() != 32 {
|
||||
return Err(err());
|
||||
}
|
||||
let key_arr: &[u8; 32] = key_bytes.try_into().map_err(|_| err())?;
|
||||
|
||||
PrivateKey::from_bytes(key_arr)
|
||||
}
|
||||
|
||||
/// 从 PKCS#8 DER 解析 SM2 私钥(RFC 5958)
|
||||
///
|
||||
/// 格式:
|
||||
/// ```text
|
||||
/// PrivateKeyInfo ::= SEQUENCE {
|
||||
/// version INTEGER (0),
|
||||
/// algorithm AlgorithmIdentifier SEQUENCE { ... },
|
||||
/// privateKey OCTET STRING (SEC1 DER)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # 错误
|
||||
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
|
||||
pub fn private_key_from_pkcs8_der(der: &[u8]) -> Result<PrivateKey, Error> {
|
||||
let err = || Error::InvalidPrivateKey;
|
||||
|
||||
// 解析外层 SEQUENCE(PrivateKeyInfo)
|
||||
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
|
||||
|
||||
// version INTEGER,值应为 0
|
||||
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
|
||||
if ver_bytes != [0x00] {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
// AlgorithmIdentifier SEQUENCE(跳过,不验证 OID)
|
||||
let (_, rest) = parse_tlv(rest, 0x30).ok_or_else(err)?;
|
||||
|
||||
// privateKey OCTET STRING(内含 SEC1 DER)
|
||||
let (sec1_der, _) = parse_tlv(rest, 0x04).ok_or_else(err)?;
|
||||
|
||||
private_key_from_sec1_der(sec1_der)
|
||||
}
|
||||
|
||||
// ── 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 = 1:02 01 01
|
||||
// privateKey OCTET STRING:04 20 <32 bytes>
|
||||
// inner = 3 + 2 + 32 = 37 bytes → SEQUENCE 30 25 ...
|
||||
let mut der = alloc::vec![0x30u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20];
|
||||
der.extend_from_slice(key);
|
||||
der
|
||||
}
|
||||
|
||||
/// 构造最小 PKCS#8 DER(包含虚拟 AlgorithmIdentifier OID)
|
||||
#[cfg(feature = "alloc")]
|
||||
fn make_pkcs8_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
|
||||
let sec1 = make_sec1_der(key);
|
||||
// AlgorithmIdentifier 最小化:30 06 06 01 00 06 01 00(两个 OID,各 1 字节占位)
|
||||
let alg_id: &[u8] = &[0x30, 0x06, 0x06, 0x01, 0x00, 0x06, 0x01, 0x00];
|
||||
// version INTEGER = 0:02 01 00
|
||||
let version: &[u8] = &[0x02, 0x01, 0x00];
|
||||
// privateKey OCTET STRING 包装 sec1
|
||||
let mut priv_oct = alloc::vec![0x04u8, sec1.len() as u8];
|
||||
priv_oct.extend_from_slice(&sec1);
|
||||
// inner = version + alg_id + priv_oct
|
||||
let inner_len = version.len() + alg_id.len() + priv_oct.len();
|
||||
let mut der = alloc::vec![0x30u8, inner_len as u8];
|
||||
der.extend_from_slice(version);
|
||||
der.extend_from_slice(alg_id);
|
||||
der.extend_from_slice(&priv_oct);
|
||||
der
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn test_sec1_der_roundtrip() {
|
||||
let der = make_sec1_der(&RAW_KEY);
|
||||
let key = private_key_from_sec1_der(&der).expect("SEC1 解析应成功");
|
||||
assert_eq!(key.as_bytes(), &RAW_KEY);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn test_pkcs8_der_roundtrip() {
|
||||
let der = make_pkcs8_der(&RAW_KEY);
|
||||
let key = private_key_from_pkcs8_der(&der).expect("PKCS#8 解析应成功");
|
||||
assert_eq!(key.as_bytes(), &RAW_KEY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sec1_der_invalid_tag() {
|
||||
// 首字节不是 SEQUENCE tag
|
||||
let bad = [0x02u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20, 0x00];
|
||||
assert!(private_key_from_sec1_der(&bad).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sec1_der_wrong_version() {
|
||||
// version 应为 1,此处给 0;最后 32 字节填充为 RAW_KEY
|
||||
let mut der = [0u8; 39];
|
||||
der[0] = 0x30;
|
||||
der[1] = 0x25; // SEQUENCE length 37
|
||||
der[2] = 0x02;
|
||||
der[3] = 0x01;
|
||||
der[4] = 0x00; // version = 0(错误,应为 1)
|
||||
der[5] = 0x04;
|
||||
der[6] = 0x20; // OCTET STRING 32 字节
|
||||
der[7..39].copy_from_slice(&RAW_KEY);
|
||||
assert!(private_key_from_sec1_der(&der).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sec1_der_key_too_short() {
|
||||
// privateKey 只有 16 字节(不足 32)
|
||||
let der = [
|
||||
0x30, 0x15, // SEQUENCE 21 字节
|
||||
0x02, 0x01, 0x01, // version = 1
|
||||
0x04, 0x10, // OCTET STRING 16 字节
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
];
|
||||
assert!(private_key_from_sec1_der(&der).is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn test_pkcs8_der_invalid_outer_tag() {
|
||||
let mut der = make_pkcs8_der(&RAW_KEY);
|
||||
der[0] = 0x04; // 破坏外层 SEQUENCE tag
|
||||
assert!(private_key_from_pkcs8_der(&der).is_err());
|
||||
}
|
||||
|
||||
// ── 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||y(65字节)
|
||||
// 确认公钥原始字节出现在 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
@@ -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;
|
||||
// 优先级 2:P == -Q → INFINITY(h==0 且 r≠0)
|
||||
let result = JacobianPoint::conditional_select(
|
||||
&result,
|
||||
&JacobianPoint::INFINITY,
|
||||
h_is_zero & !r_is_zero,
|
||||
);
|
||||
// 优先级 3:P == 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 取 sum,bit=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 优化)────────────────────────────────────
|
||||
|
||||
/// 混合点加 P(Jacobian)+ Q(Affine,Z=1)
|
||||
///
|
||||
/// 相比标准 Jacobian+Jacobian 加法,利用 Z_Q=1 省去:
|
||||
/// - Z2² 计算(1 次 fp_square)
|
||||
/// - X1·Z2² 简化为 X1(0 次乘法)
|
||||
/// - Y1·Z2³ 简化为 Y1(0 次乘法)
|
||||
/// - 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 = X1,s1 = 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·G,i = 0..=15(table[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 仍为 INFINITY,add(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||y(65 字节)解析点
|
||||
///
|
||||
/// 符合 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||x(33 字节)解压缩点
|
||||
///
|
||||
/// 符合 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·Q(Shamir'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+Q(Jacobian,含退化处理)
|
||||
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) 应为无穷远点"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 不可能 panic(is_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 乘法(模 n,Montgomery 形式)
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
@@ -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[..]);
|
||||
}
|
||||
}
|
||||
@@ -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 127(bytes[16] 的最高位)
|
||||
buf[16] |= 0x80;
|
||||
U256::from_be_slice(&buf)
|
||||
}
|
||||
|
||||
// ── EphemeralKey(临时密钥对)────────────────────────────────────────────────────
|
||||
|
||||
/// SM2 密钥交换临时密钥对(离开作用域自动清零)
|
||||
///
|
||||
/// 用于密钥交换协议中的临时私钥和对应公钥。
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct EphemeralKey {
|
||||
r_bytes: [u8; 32],
|
||||
#[zeroize(skip)]
|
||||
r_point: [u8; 65],
|
||||
}
|
||||
|
||||
impl EphemeralKey {
|
||||
/// 生成临时密钥对
|
||||
pub fn generate<R: RngCore>(rng: &mut R) -> Self {
|
||||
loop {
|
||||
let mut r_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut r_bytes);
|
||||
let r = U256::from_be_slice(&r_bytes);
|
||||
if bool::from(r.is_zero()) || r >= GROUP_ORDER_MINUS_1 {
|
||||
r_bytes.zeroize();
|
||||
continue;
|
||||
}
|
||||
let r_jac = JacobianPoint::scalar_mul_g(&r);
|
||||
// Reason: r 在合法范围内,scalar_mul_g 不会产生无穷远点
|
||||
let r_aff = r_jac.to_affine().expect("valid r produces valid point");
|
||||
return EphemeralKey {
|
||||
r_bytes,
|
||||
r_point: r_aff.to_bytes(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 从指定标量创建临时密钥对(测试用)
|
||||
pub fn from_scalar(r: &U256) -> Result<Self, Error> {
|
||||
if bool::from(r.is_zero()) || *r >= GROUP_ORDER_MINUS_1 {
|
||||
return Err(Error::InvalidPrivateKey);
|
||||
}
|
||||
let r_jac = JacobianPoint::scalar_mul_g(r);
|
||||
let r_aff = r_jac.to_affine().map_err(|_| Error::InvalidPrivateKey)?;
|
||||
Ok(EphemeralKey {
|
||||
r_bytes: r.to_be_bytes(),
|
||||
r_point: r_aff.to_bytes(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取临时公钥(发送给对方)
|
||||
pub fn public_key(&self) -> &[u8; 65] {
|
||||
&self.r_point
|
||||
}
|
||||
}
|
||||
|
||||
// ── 简单 ECDH ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 简单 SM2-ECDH 共享密钥计算
|
||||
///
|
||||
/// 计算 shared = my_priv · peer_pub,返回共享点的 x 坐标(32 字节)。
|
||||
/// 适用于 TLS/rustls 等只需要原始 ECDH 共享密钥的场景。
|
||||
///
|
||||
/// # 参数
|
||||
/// - `my_priv`: 己方私钥
|
||||
/// - `peer_pub`: 对方公钥(65 字节,04||x||y)
|
||||
///
|
||||
/// # 错误
|
||||
/// - `InvalidPublicKey`: 公钥格式错误或不在曲线上
|
||||
/// - `PointAtInfinity`: 共享点为无穷远(不应发生于合法输入)
|
||||
pub fn ecdh(my_priv: &crate::PrivateKey, peer_pub: &[u8; 65]) -> Result<[u8; 32], Error> {
|
||||
let peer = AffinePoint::from_bytes(peer_pub)?;
|
||||
let d = U256::from_be_slice(my_priv.as_bytes());
|
||||
let peer_jac = JacobianPoint::from_affine(&peer);
|
||||
let shared = JacobianPoint::scalar_mul(&d, &peer_jac);
|
||||
let shared_aff = shared.to_affine()?;
|
||||
Ok(fp_to_bytes(&shared_aff.x))
|
||||
}
|
||||
|
||||
/// 从变长切片执行 SM2-ECDH(rustls `ActiveKeyExchange::complete` 适配)
|
||||
///
|
||||
/// 等同于 `ecdh`,但接受 `&[u8]` 而非 `&[u8; 65]`,省去调用方的长度转换。
|
||||
///
|
||||
/// # 错误
|
||||
/// - `InvalidInputLength`: peer_pub 长度不等于 65
|
||||
/// - `InvalidPublicKey` / `PointAtInfinity`: 同 `ecdh`
|
||||
pub fn ecdh_from_slice(
|
||||
my_priv: &crate::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 表示发起方 A,false 表示响应方 B
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_shared(
|
||||
is_initiator: bool,
|
||||
klen: usize,
|
||||
id_a: &[u8],
|
||||
id_b: &[u8],
|
||||
pri_key_self: &crate::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 字节应为 0,bytes[16] = 0xFF | 0x80 = 0xFF
|
||||
let mut expected = [0u8; 32];
|
||||
expected[16..32].copy_from_slice(&[0xFF; 16]);
|
||||
expected[16] |= 0x80; // 已经是 0xFF,不变
|
||||
assert_eq!(result, U256::from_be_slice(&expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdh_roundtrip() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let d_b: [u8; 32] = [
|
||||
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
|
||||
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
|
||||
0xea, 0xc1, 0xbc, 0x21,
|
||||
];
|
||||
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
|
||||
let pub_a = pri_a.public_key();
|
||||
let pub_b = pri_b.public_key();
|
||||
|
||||
// A 用 B 的公钥算 ECDH,B 用 A 的公钥算 ECDH,结果应一致
|
||||
let shared_a = ecdh(&pri_a, &pub_b).unwrap();
|
||||
let shared_b = ecdh(&pri_b, &pub_a).unwrap();
|
||||
assert_eq!(shared_a, shared_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdh_invalid_pubkey() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
|
||||
// 无效公钥(全零 y 坐标不在曲线上)
|
||||
let mut bad_pub = [0x04u8; 65];
|
||||
bad_pub[1] = 0x01;
|
||||
assert!(ecdh(&pri_a, &bad_pub).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdh_from_slice_length_check() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
|
||||
// 长度不对应报 InvalidInputLength
|
||||
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 64]).is_err());
|
||||
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 66]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdh_from_slice_equals_ecdh() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let d_b: [u8; 32] = [
|
||||
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
|
||||
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
|
||||
0xea, 0xc1, 0xbc, 0x21,
|
||||
];
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
|
||||
let pub_b = pri_b.public_key();
|
||||
|
||||
let r1 = ecdh(&pri_a, &pub_b).unwrap();
|
||||
let r2 = ecdh_from_slice(&pri_a, &pub_b).unwrap();
|
||||
assert_eq!(r1, r2);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn test_exchange_roundtrip() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let d_b: [u8; 32] = [
|
||||
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
|
||||
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
|
||||
0xea, 0xc1, 0xbc, 0x21,
|
||||
];
|
||||
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
|
||||
let pub_a = pri_a.public_key();
|
||||
let pub_b = pri_b.public_key();
|
||||
|
||||
let id_a = b"Alice@test.com";
|
||||
let id_b = b"Bob@test.com";
|
||||
|
||||
// 生成临时密钥
|
||||
let ra_scalar =
|
||||
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
|
||||
let rb_scalar =
|
||||
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
|
||||
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
|
||||
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
|
||||
|
||||
let result_a = exchange_a(
|
||||
16,
|
||||
id_a,
|
||||
id_b,
|
||||
&pri_a,
|
||||
&pub_a,
|
||||
&pub_b,
|
||||
&eph_a,
|
||||
eph_b.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result_b = exchange_b(
|
||||
16,
|
||||
id_a,
|
||||
id_b,
|
||||
&pri_b,
|
||||
&pub_a,
|
||||
&pub_b,
|
||||
&eph_b,
|
||||
eph_a.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 协商出的密钥应相同
|
||||
assert_eq!(result_a.key, result_b.key);
|
||||
assert!(!result_a.key.is_empty());
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn test_exchange_confirmation() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let d_b: [u8; 32] = [
|
||||
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
|
||||
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
|
||||
0xea, 0xc1, 0xbc, 0x21,
|
||||
];
|
||||
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
|
||||
let pub_a = pri_a.public_key();
|
||||
let pub_b = pri_b.public_key();
|
||||
|
||||
let id_a = b"1234567812345678";
|
||||
let id_b = b"1234567812345678";
|
||||
|
||||
let ra_scalar =
|
||||
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
|
||||
let rb_scalar =
|
||||
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
|
||||
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
|
||||
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
|
||||
|
||||
let result_a = exchange_a(
|
||||
16,
|
||||
id_a,
|
||||
id_b,
|
||||
&pri_a,
|
||||
&pub_a,
|
||||
&pub_b,
|
||||
&eph_a,
|
||||
eph_b.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result_b = exchange_b(
|
||||
16,
|
||||
id_a,
|
||||
id_b,
|
||||
&pri_b,
|
||||
&pub_a,
|
||||
&pub_b,
|
||||
&eph_b,
|
||||
eph_a.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 确认哈希交叉验证:A.s_peer == B.s_self,B.s_peer == A.s_self
|
||||
assert_eq!(result_a.s_peer, result_b.s_self);
|
||||
assert_eq!(result_b.s_peer, result_a.s_self);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn test_exchange_different_ids() {
|
||||
let d_a: [u8; 32] = [
|
||||
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
|
||||
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
|
||||
0x4d, 0xf7, 0xc5, 0xb8,
|
||||
];
|
||||
let d_b: [u8; 32] = [
|
||||
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
|
||||
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
|
||||
0xea, 0xc1, 0xbc, 0x21,
|
||||
];
|
||||
|
||||
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
|
||||
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
|
||||
let pub_a = pri_a.public_key();
|
||||
let pub_b = pri_b.public_key();
|
||||
|
||||
let ra_scalar =
|
||||
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
|
||||
let rb_scalar =
|
||||
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
|
||||
|
||||
// 使用不同 ID 组合
|
||||
let eph_a1 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
|
||||
let eph_b1 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
|
||||
let result_1 = exchange_a(
|
||||
16,
|
||||
b"ID_A_1",
|
||||
b"ID_B_1",
|
||||
&pri_a,
|
||||
&pub_a,
|
||||
&pub_b,
|
||||
&eph_a1,
|
||||
eph_b1.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let eph_a2 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
|
||||
let eph_b2 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
|
||||
let result_2 = exchange_a(
|
||||
16,
|
||||
b"ID_A_2",
|
||||
b"ID_B_2",
|
||||
&pri_a,
|
||||
&pub_a,
|
||||
&pub_b,
|
||||
&eph_a2,
|
||||
eph_b2.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 不同 ID 应产生不同密钥
|
||||
assert_ne!(result_1.key, result_2.key);
|
||||
}
|
||||
}
|
||||
+796
@@ -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||s,64 字节)
|
||||
///
|
||||
/// 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");
|
||||
}
|
||||
}
|
||||
@@ -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.inner,clone 一次即可
|
||||
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`: 消息摘要 e(32 字节,已经过 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 < n(group 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");
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 = []
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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-MAC(RFC 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)
|
||||
}
|
||||
@@ -18,6 +18,7 @@ pub mod hmac;
|
||||
pub mod kx;
|
||||
pub mod sign;
|
||||
pub mod tls13;
|
||||
pub(crate) mod aead;
|
||||
pub mod verify;
|
||||
|
||||
/// 构造国密 `CryptoProvider`
|
||||
|
||||
@@ -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
@@ -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..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);
|
||||
// 使用宏在编译期展开,避免循环计数器开销,使编译器能进行跨轮常量折叠和指令调度。
|
||||
|
||||
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..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);
|
||||
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..15:FF = x^y^z,GG = 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..63:FF = 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
@@ -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
@@ -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
@@ -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::*;
|
||||
|
||||
@@ -1,860 +0,0 @@
|
||||
//! SM4 分组模式(GB/T 32907-2016,GB/T 17964-2021)
|
||||
//!
|
||||
//! 支持:ECB、CBC、OFB、CFB、CTR、GCM(AEAD)、CCM(AEAD)、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 字节 nonce(GCM 标准推荐)
|
||||
/// - `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-MAC(RFC 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 附录 B:CBC 模式测试向量
|
||||
#[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 应为自反模式");
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,9 +1,12 @@
|
||||
//! SM4 国标测试向量(GB/T 32907-2016 附录 A)
|
||||
//!
|
||||
//! A.1 示例1:单次 ECB 加密
|
||||
//! A.1 示例1:单次 ECB 加密(单块)
|
||||
//! A.2 示例2:1,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], "解密后应恢复原文");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user