Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
ae323cc
sm4: add bouncycastle-sm4, a constant-time SM4 block cipher ported fr…
dghgit Sep 5, 2026
7d7d2db
sm4: override encrypt_4blocks / decrypt_4blocks with the eight-lane c…
dghgit Sep 5, 2026
9738d32
sm4: use a four-lane u16 circuit
dghgit Sep 5, 2026
32dabf5
sm4: every CBC and ECB alias takes <Dir, Pad> over the shared PaddedM…
dghgit Sep 8, 2026
47316e7
sm4: the ElectronicCodeBook trait is the only public route to the per…
dghgit Sep 7, 2026
de6945a
sm4: consolidate the bc-java attribution
dghgit Sep 19, 2026
3f0274d
mem_usage_benches: fence bench_sm4_mem_usage's shell recipes as ```text
dghgit Sep 19, 2026
2446e24
sm4: add the SM4_CFB, SM4_CFB8 and SM4_CTR aliases and their sm4-cfb …
dghgit Sep 20, 2026
9bf6cfe
sm4: make the new CLI stream-mode test harnesses tolerate a broken pi…
dghgit Sep 20, 2026
3988cf3
sm4: give sm4_cbc_cli_tests.rs the same broken-pipe tolerance the str…
dghgit Sep 20, 2026
516b2a0
sm4: drop references to ElectronicCodeBook's batch-method defaults, w…
dghgit Sep 25, 2026
acbbbfa
sm4: adapt to feature/simple-ciphers -- the API changes this branch a…
dghgit Sep 28, 2026
9a35249
sm4: adapt to feature/simple-ciphers 0fb4e79 -- the stream-mode alias…
dghgit Sep 29, 2026
942bb8a
sm4: adopt the hazmat layout (#156 step 5)
dghgit Oct 1, 2026
2731733
sm4: project SM4_CBC through core's sealed Direction::Select
dghgit Oct 1, 2026
da173b1
sm4: what the linear replay dropped -- the merge-carried adaptations …
dghgit Oct 25, 2026
fe2b9e8
sm4: onto the key objects -- SM4Key<M> hands out the mode workers, th…
dghgit Oct 25, 2026
d4fc1f9
sm4: the key carries only the roles the aliases use -- the AEAD, ECB …
dghgit Oct 25, 2026
e61e7ed
sm4: the engine and the keys compare in constant time
dghgit Oct 25, 2026
d09edb1
sm4: review fixes -- the key's leftover MAC-key path goes, the stack …
dghgit Oct 28, 2026
515dbcc
cli: the sm4 commands take the aes128 contract -- PKCS7, --iv/--iv-fi…
dghgit Oct 28, 2026
0c82221
sm4: the Wycheproof SM4-GCM and SM4-CCM suites, through the mode trai…
dghgit Oct 28, 2026
7209641
sm4: the stack table sums the frame chain, as the AES table does
dghgit Oct 30, 2026
80477f2
sm4: a key checks the key type its mode asks for before the permutati…
dghgit Oct 30, 2026
043c928
sm4: SM4_GCM through the key, and an sm4-gcm command
dghgit Oct 31, 2026
6f2a775
sm4: the PR #142 findings that apply here -- the mode and key docs si…
dghgit Nov 3, 2026
c9e3ebd
sm4: the merge-time adaptations to feature/key-rework-memory, carried…
dghgit Nov 3, 2026
2d7c749
sm4: the declared-length AEAD pair returns to the key, so SM4Key<CCM<…
dghgit Nov 4, 2026
6c52958
cli: sm4-ccm, the aes*-ccm command over the SM4 key (the CCM runner a…
dghgit Nov 4, 2026
6dc6e19
cli: the sm4-ccm help check is one assertion, not a loop over one name
dghgit Nov 4, 2026
bc764f2
Merge release/0.1.3alpha (029a17b0, the squash merge of PR #190) into…
dghgit Nov 4, 2026
41431f8
cli: the SM4 commands' help takes the AES wording for the input rule …
dghgit Nov 4, 2026
b36ec53
sm4: add bouncycastle-sm4, a constant-time SM4 block cipher ported fr…
dghgit Sep 5, 2026
154b010
sm4: override encrypt_4blocks / decrypt_4blocks with the eight-lane c…
dghgit Sep 5, 2026
db7647c
sm4: use a four-lane u16 circuit
dghgit Sep 5, 2026
720128d
sm4: every CBC and ECB alias takes <Dir, Pad> over the shared PaddedM…
dghgit Sep 8, 2026
9b55a86
sm4: the ElectronicCodeBook trait is the only public route to the per…
dghgit Sep 7, 2026
0953b36
sm4: consolidate the bc-java attribution
dghgit Sep 19, 2026
7aaee94
mem_usage_benches: fence bench_sm4_mem_usage's shell recipes as ```text
dghgit Sep 19, 2026
a8e376d
sm4: add the SM4_CFB, SM4_CFB8 and SM4_CTR aliases and their sm4-cfb …
dghgit Sep 20, 2026
47128d3
sm4: make the new CLI stream-mode test harnesses tolerate a broken pi…
dghgit Sep 20, 2026
d5a173a
sm4: give sm4_cbc_cli_tests.rs the same broken-pipe tolerance the str…
dghgit Sep 20, 2026
12071eb
sm4: drop references to ElectronicCodeBook's batch-method defaults, w…
dghgit Sep 25, 2026
c2753e5
sm4: adapt to feature/simple-ciphers -- the API changes this branch a…
dghgit Sep 28, 2026
60dd759
sm4: adapt to feature/simple-ciphers 0fb4e79 -- the stream-mode alias…
dghgit Sep 29, 2026
d16ccb4
sm4: adopt the hazmat layout (#156 step 5)
dghgit Oct 1, 2026
b52d0d6
sm4: project SM4_CBC through core's sealed Direction::Select
dghgit Oct 1, 2026
8c88892
sm4: what the linear replay dropped -- the merge-carried adaptations …
dghgit Oct 25, 2026
2334efb
sm4: onto the key objects -- SM4Key<M> hands out the mode workers, th…
dghgit Oct 25, 2026
4fdcac2
sm4: the key carries only the roles the aliases use -- the AEAD, ECB …
dghgit Oct 25, 2026
6d301fc
sm4: the engine and the keys compare in constant time
dghgit Oct 25, 2026
fb8ad54
sm4: review fixes -- the key's leftover MAC-key path goes, the stack …
dghgit Oct 28, 2026
7b0b178
cli: the sm4 commands take the aes128 contract -- PKCS7, --iv/--iv-fi…
dghgit Oct 28, 2026
00e4376
sm4: the Wycheproof SM4-GCM and SM4-CCM suites, through the mode trai…
dghgit Oct 28, 2026
01839fd
sm4: the stack table sums the frame chain, as the AES table does
dghgit Oct 30, 2026
9054c3b
sm4: a key checks the key type its mode asks for before the permutati…
dghgit Oct 30, 2026
0153163
sm4: SM4_GCM through the key, and an sm4-gcm command
dghgit Oct 31, 2026
39d2899
sm4: the PR #142 findings that apply here -- the mode and key docs si…
dghgit Nov 3, 2026
2730ef3
sm4: the merge-time adaptations to feature/key-rework-memory, carried…
dghgit Nov 3, 2026
2702b75
sm4: the declared-length AEAD pair returns to the key, so SM4Key<CCM<…
dghgit Nov 4, 2026
ce84194
cli: sm4-ccm, the aes*-ccm command over the SM4 key (the CCM runner a…
dghgit Nov 4, 2026
6b8303d
cli: the sm4-ccm help check is one assertion, not a loop over one name
dghgit Nov 4, 2026
49e27f3
cli: the SM4 commands' help takes the AES wording for the input rule …
dghgit Nov 4, 2026
2883ea7
Merge branch 'feature/sm4' of git.bouncycastle.org:bc-rust into featu…
Nov 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bouncycastle-rng = { path = "./crypto/rng" }
bouncycastle-sha2 = { path = "./crypto/sha2" }
bouncycastle-sha3 = { path = "./crypto/sha3" }
bouncycastle-sm3 = { path = "./crypto/sm3" }
bouncycastle-sm4 = { path = "./crypto/sm4" }
bouncycastle-utils = { path = "./crypto/utils" }


Expand Down Expand Up @@ -75,3 +76,4 @@ bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
bouncycastle-sm3.workspace = true
bouncycastle-sm4.workspace = true
4 changes: 2 additions & 2 deletions cli/src/aes_ccm_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ use crate::helpers::block_mode_helpers::{CipherDirection, load_key};
/// The one-shots this command uses are on the key's role traits, and the key type carries the
/// nonce and tag lengths as const parameters, so a generic `run` over the key length needs a way
/// to name the key type for an `(N, T)` it chooses at run time: this generic associated type.
trait CcmKeys<const KEY_LEN: usize> {
pub(crate) trait CcmKeys<const KEY_LEN: usize> {
/// The key type at nonce length `N` and tag length `T`.
type Key<const N: usize, const T: usize>: FromKeyMaterial<KEY_LEN>
+ AeadDeclaredEncryptionKey<KEY_LEN, N, T, T>
Expand Down Expand Up @@ -227,7 +227,7 @@ fn load_aad(aad: &Option<String>, aad_file: &Option<String>) -> Vec<u8> {
/// be matched into one of the permitted instantiations. The two nested matches are the price of
/// that, and they are exhaustive over A.1's sets: 7 nonce lengths x 7 tag lengths.
#[allow(clippy::too_many_arguments)] // the three key lengths' inputs, already parsed
fn run<F: CcmKeys<KEY_LEN>, const KEY_LEN: usize>(
pub(crate) fn run<F: CcmKeys<KEY_LEN>, const KEY_LEN: usize>(
action: &CipherDirection,
key: &KeyMaterial<KEY_LEN>,
nonce: &Option<String>,
Expand Down
337 changes: 337 additions & 0 deletions cli/src/main.rs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions cli/src/sm4_cbc_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! SM4-CBC encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the IV convention, key loading and stdin framing are all
//! in [`crate::helpers::block_mode_helpers`], shared with the `aes*-cbc` and `aes*-ecb`
//! commands. See that module for the command-line contract. Input is padded with PKCS7, so it
//! may be any length.
//!
//! `sm4-cbc` is the same command as `aes128-cbc` over the SM4 permutation (GB/T 32907-2016;
//! 16-byte key, 16-byte block), so every remark there applies unchanged. CBC (NIST SP 800-38A
//! Sec 6.2) provides confidentiality only: neither the ciphertext nor the IV is authenticated,
//! and a flipped ciphertext bit flips the same bit of the *next* block's plaintext (Appendix D).
//! Do not decrypt data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{
CipherDirection, decrypt_stream, encrypt_stream, load_iv, load_key_object, start_encryptor,
};
use bouncycastle::cipher::modes::CBC;
use bouncycastle::cipher::padding::PKCS7;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
use bouncycastle::sm4::{BLOCK_LEN, KEY_LEN, SM4Key};

/// Dispatches to the shared streaming loops under the key's padded CBC mode.
pub(crate) fn sm4_cbc_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
iv: &Option<String>,
iv_file: &Option<String>,
output_hex: bool,
) {
let key = load_key_object::<SM4Key<CBC<PKCS7>>, KEY_LEN>(key, key_file, "SM4");
let iv = load_iv::<BLOCK_LEN>(iv, iv_file);
match action {
CipherDirection::Encrypt => {
let (enc, generated_iv) = start_encryptor(iv, |rng| key.create_encryptor(rng));
encrypt_stream::<_, BLOCK_LEN>(enc, generated_iv.as_ref(), output_hex, None)
}
CipherDirection::Decrypt => {
decrypt_stream::<_, BLOCK_LEN>(iv, |iv| key.create_decryptor(iv), output_hex, None)
}
}
}
42 changes: 42 additions & 0 deletions cli/src/sm4_ccm_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! SM4-CCM authenticated encryption and decryption: the `aes*-ccm` command over the SM4 key
//! (GB/T 32907-2016; with the default 12-byte nonce and 16-byte tag it is RFC 8998 Sec 3.5.2's
//! `AEAD_SM4_CCM`), with the same contract -- the whole input read first, the nonce supplied
//! rather than generated, `ciphertext || tag` out. See [`crate::aes_ccm_cmd`] for why CCM cannot
//! stream and for the nonce and length rules; this file only names the SM4 key type for that
//! module's runner.

use crate::aes_ccm_cmd::{CcmKeys, run};
use crate::helpers::block_mode_helpers::{CipherDirection, load_key};
use bouncycastle::cipher::modes::CCM;
use bouncycastle::sm4::{KEY_LEN, SM4Key};

struct Sm4;

impl CcmKeys<KEY_LEN> for Sm4 {
type Key<const N: usize, const T: usize> = SM4Key<CCM<N, T>>;
}

/// SM4 CCM. See the module docs and the subcommand help.
#[allow(clippy::too_many_arguments)]
pub(crate) fn sm4_ccm_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
nonce: &Option<String>,
nonce_file: &Option<String>,
aad: &Option<String>,
aad_file: &Option<String>,
tag_len: usize,
output_hex: bool,
) {
run::<Sm4, KEY_LEN>(
action,
&load_key::<KEY_LEN>(key, key_file, "SM4"),
nonce,
nonce_file,
aad,
aad_file,
tag_len,
output_hex,
);
}
36 changes: 36 additions & 0 deletions cli/src/sm4_cfb8_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! SM4-CFB8 encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the IV convention, key loading and stdin framing are in
//! [`crate::helpers::stream_mode_helpers`] (and [`crate::helpers::block_mode_helpers`] for the
//! key and IV loaders), shared with the `aes*-cfb8` commands. See those modules for the
//! command-line contract.
//!
//! `sm4-cfb8` is the same command as `aes128-cfb8` over the SM4 permutation (GB/T 32907-2016;
//! 16-byte key, 16-byte block), so every remark there applies unchanged. The segment size is one
//! byte: this is draft-ribose-cfrg-sm4-10's **SM4-CFB-8** (Sec 8.5.1), a different and
//! non-interoperable mode from the SM4-CFB-128 of `sm4-cfb`, and it costs a full SM4 call per byte,
//! sixteen times the work. Prefer `sm4-cfb` unless a byte-granular self-synchronising stream is
//! required or the format demands CFB8.
//!
//! # Warning
//!
//! CFB8 provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the IV is authenticated. Do not decrypt data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::cipher::modes::CFB8;
use bouncycastle::sm4::{BLOCK_LEN, KEY_LEN, SM4Key};

/// Dispatches to the shared streaming loops under the key's `CFB8` mode.
pub(crate) fn sm4_cfb8_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
iv: &Option<String>,
iv_file: &Option<String>,
output_hex: bool,
) {
let key = load_key_object::<SM4Key<CFB8>, KEY_LEN>(key, key_file, "SM4");
run_stream_mode::<_, KEY_LEN, BLOCK_LEN>(action, &key, load_iv(iv, iv_file), output_hex)
}
40 changes: 40 additions & 0 deletions cli/src/sm4_cfb_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! SM4-CFB128 encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the IV convention, key loading and stdin framing are in
//! [`crate::helpers::stream_mode_helpers`] (and [`crate::helpers::block_mode_helpers`] for the
//! key and IV loaders), shared with the `aes*-cfb` commands. See those modules for the
//! command-line contract.
//!
//! `sm4-cfb` is the same command as `aes128-cfb` over the SM4 permutation (GB/T 32907-2016;
//! 16-byte key, 16-byte block), so every remark there applies unchanged. The segment size is the
//! full block, i.e. draft-ribose-cfrg-sm4-10's **SM4-CFB-128** (Sec 8.5.1); the draft's 8-bit
//! variant is a different, non-interoperable mode and has its own command, `sm4-cfb8`.
//!
//! CFB is a stream cipher, so this command accepts input of any length and pads nothing; the
//! ciphertext is exactly as long as the plaintext.
//!
//! # Warning
//!
//! CFB provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the IV is authenticated. Flipping a ciphertext bit flips the *same* bit of the plaintext in the
//! *same* block (NIST SP 800-38A Appendix D, Table D.2), at the cost of randomising the next one,
//! so an attacker edits the block they aimed at. Do not decrypt data you have not authenticated
//! separately.

use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::cipher::modes::CFB;
use bouncycastle::sm4::{BLOCK_LEN, KEY_LEN, SM4Key};

/// Dispatches to the shared streaming loops under the key's `CFB` mode.
pub(crate) fn sm4_cfb_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
iv: &Option<String>,
iv_file: &Option<String>,
output_hex: bool,
) {
let key = load_key_object::<SM4Key<CFB>, KEY_LEN>(key, key_file, "SM4");
run_stream_mode::<_, KEY_LEN, BLOCK_LEN>(action, &key, load_iv(iv, iv_file), output_hex)
}
44 changes: 44 additions & 0 deletions cli/src/sm4_ctr_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! SM4-CTR encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the nonce convention, key loading and stdin framing are in
//! [`crate::helpers::stream_mode_helpers`] (and [`crate::helpers::block_mode_helpers`] for the
//! key and nonce loaders), shared with the `aes*-ctr` commands. See those modules for the
//! command-line contract.
//!
//! `sm4-ctr` is the same command as `aes128-ctr` over the SM4 permutation (GB/T 32907-2016;
//! 16-byte key, 16-byte block), so every remark there applies unchanged, including the split of
//! the counter block: the nonce is **12 bytes** and the counter the remaining 4, giving 2^32
//! blocks -- 64 GiB -- in a single message. draft-ribose-cfrg-sm4-10 Sec 8.7 takes the counter
//! sequence as an input and requires only that it "does not repeat within the block size", so
//! this is one admissible choice; it is NIST SP 800-38A Appendix B.2's.
//!
//! `encrypt` writes that 12-byte nonce as the first bytes of its output and `decrypt` reads it
//! back, exactly as the other modes do with their IVs; note that it is 12 bytes here, not 16.
//!
//! # Warning
//!
//! CTR provides confidentiality only and is the most malleable mode here: flipping any ciphertext
//! bit flips exactly the corresponding plaintext bit and affects nothing else (SP 800-38A
//! Appendix D, Table D.2), so an attacker can edit the plaintext at will with no garbling to give
//! it away. A repeated nonce under one key is fatal rather than merely unwise -- the same keystream
//! twice leaks the XOR of the two messages -- which is why the nonce is drawn from the OS-backed
//! DRBG unless `--iv` / `--iv-file` supplies one, and then its uniqueness under the key is the
//! caller's to keep. Do not decrypt data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::cipher::modes::CTR;
use bouncycastle::sm4::{CTR_NONCE_LEN, KEY_LEN, SM4Key};

/// Dispatches to the shared streaming loops under the key's `CTR` mode.
pub(crate) fn sm4_ctr_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
iv: &Option<String>,
iv_file: &Option<String>,
output_hex: bool,
) {
let key = load_key_object::<SM4Key<CTR>, KEY_LEN>(key, key_file, "SM4");
run_stream_mode::<_, KEY_LEN, CTR_NONCE_LEN>(action, &key, load_iv(iv, iv_file), output_hex)
}
37 changes: 37 additions & 0 deletions cli/src/sm4_gcm_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! SM4-GCM authenticated encryption and decryption, streaming stdin to stdout.
//!
//! Only the mode wiring lives here: the IV/tag framing, AAD loading and stdin streaming are in
//! [`crate::helpers::aead_cipher_helpers`], shared with the `aes*-gcm` commands. See that module
//! for the command-line contract (`IV || ciphertext || tag`, the AAD flags, and why a tag failure
//! may be reported after plaintext has already reached stdout).
//!
//! `sm4-gcm` is the same command as `aes128-gcm` over the SM4 permutation (GB/T 32907-2016;
//! 16-byte key, 16-byte block), which is RFC 8998 Sec 3.5.1's `AEAD_SM4_GCM`: a 12-byte nonce and
//! a 16-byte tag. Every remark there applies unchanged, above all that a repeated IV under one
//! key lets an attacker recover the hash subkey (SP 800-38D Appendix A).

use crate::helpers::aead_cipher_helpers::{decrypt_gcm, encrypt_gcm, load_aad};
use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object};
use bouncycastle::cipher::modes::{GCM, GCM_NONCE_LEN};
use bouncycastle::sm4::{KEY_LEN, SM4Key};

/// Dispatches to the shared AEAD streaming loops with `Gcm`'s 128-bit tag.
#[allow(clippy::too_many_arguments)] // one flag per input, each inline or from a file
pub(crate) fn sm4_gcm_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
iv: &Option<String>,
iv_file: &Option<String>,
aad: &Option<String>,
aad_file: &Option<String>,
output_hex: bool,
) {
let key = load_key_object::<SM4Key<GCM>, KEY_LEN>(key, key_file, "SM4");
let nonce = load_iv::<GCM_NONCE_LEN>(iv, iv_file);
let aad = load_aad(aad, aad_file);
match action {
CipherDirection::Encrypt => encrypt_gcm::<_, KEY_LEN, 16>(&key, nonce, &aad, output_hex),
CipherDirection::Decrypt => decrypt_gcm::<_, KEY_LEN, 16>(&key, nonce, &aad, output_hex),
}
}
Loading
Loading