Skip to content
Open
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
31affff
sha2: partial-bit messages, compile-time IVs, CAVP SHAVS tests (PR #88)
dghgit Sep 6, 2026
aec1ae4
sha3: partial-byte fixes, CAVP SHA3VS tests, mem-usage bench, release…
dghgit Sep 6, 2026
d604906
sha2, hmac: add SHA-512/224 and SHA-512/256 (FIPS 180-4 s. 5.3.6) and…
dghgit Sep 6, 2026
a57d508
rng: use core::fmt in hash_drbg80090a.rs; the only part of PRs #92-#9…
dghgit Sep 6, 2026
fa0be5d
sm3: add bouncycastle-sm3 (GB/T 32905-2016) and HMAC-SM3 with factory…
dghgit Sep 6, 2026
34d7953
Partial bytes follow ASN.1 BIT STRING order (X.690 s. 8.6.2): message…
dghgit Sep 6, 2026
1140a61
release notes: SM3 partial bytes follow the ASN.1 BIT STRING order li…
dghgit Sep 6, 2026
1ad97fd
sha2, sm3: document the surviving cargo-mutants equivalences at their…
dghgit Sep 6, 2026
fe6fd58
core: split BlockCipher into block-aligned BlockCipherEncryptor/Decry…
dghgit Sep 6, 2026
58a1fed
aes-lowmemory: add bouncycastle-aes-lowmemory, a constant-time, table…
dghgit Sep 6, 2026
aa9454d
modes: add BlockPermutation trait, bouncycastle-modes with AES CBC, a…
dghgit Sep 6, 2026
f56802e
padding: add Padding trait and bouncycastle-padding (PKCS7, PaddedEnc…
dghgit Sep 6, 2026
78a4021
core, modes, aes-lowmemory: in-place block cipher API with compile-ti…
dghgit Sep 6, 2026
c5f60fb
modes: add AES CFB128 mode with AES_CFB_* aliases, aes*-cfb CLI subco…
dghgit Sep 6, 2026
2c0567e
core: ElectronicCodeBook (was BlockPermutation), slice block hooks, b…
dghgit Sep 6, 2026
157b1c8
modes: add Ecb (SP 800-38A Sec 6.1) with AES_ECB_* aliases and aes*-e…
dghgit Sep 6, 2026
f6cb787
padding: add NoPadding (errors when asked to pad) with Padding::ALWAY…
dghgit Sep 6, 2026
a1c4e41
skills: add commit-range-report, a Markdown report of a commit range …
dghgit Sep 6, 2026
9c65521
mldsa, mlkem: replace the const-generic turbofish with sealed MLDSAPa…
ounsworth Sep 7, 2026
57dd3d0
core: replace StreamCipher with the split StreamCipherEncryptor / Str…
dghgit Sep 6, 2026
97ac6e3
modes: Cfb becomes a stream cipher taking any length with no padding,…
dghgit Sep 6, 2026
5c73617
release notes: CFB becomes a stream cipher with a short final segment…
dghgit Sep 6, 2026
e2b534d
modes: pin the single-call vs chunked equivalence of Cfb and Cfb8 aga…
dghgit Sep 6, 2026
f72bfe6
modes: add Ctr (SP 800-38A Sec 6.5), a stream cipher whose nonce leng…
dghgit Sep 6, 2026
0404ab9
modes: cross-check Ctr against BC Java's SICBlockCipher, which shares…
dghgit Sep 6, 2026
921e2b5
sha2: partial-bit messages, compile-time IVs, CAVP SHAVS tests (PR #88)
dghgit Sep 6, 2026
7b4e7fc
sha3: partial-byte fixes, CAVP SHA3VS tests, mem-usage bench, release…
dghgit Sep 6, 2026
c34c2f9
sha2, hmac: add SHA-512/224 and SHA-512/256 (FIPS 180-4 s. 5.3.6) and…
dghgit Sep 6, 2026
7df74a6
rng: use core::fmt in hash_drbg80090a.rs; the only part of PRs #92-#9…
dghgit Sep 6, 2026
ac896e2
sm3: add bouncycastle-sm3 (GB/T 32905-2016) and HMAC-SM3 with factory…
dghgit Sep 6, 2026
f34858d
Partial bytes follow ASN.1 BIT STRING order (X.690 s. 8.6.2): message…
dghgit Sep 6, 2026
46e2e79
release notes: SM3 partial bytes follow the ASN.1 BIT STRING order li…
dghgit Sep 6, 2026
607cfa7
sha2, sm3: document the surviving cargo-mutants equivalences at their…
dghgit Sep 6, 2026
4ca1274
core: split BlockCipher into block-aligned BlockCipherEncryptor/Decry…
dghgit Sep 6, 2026
a9627f6
aes-lowmemory: add bouncycastle-aes-lowmemory, a constant-time, table…
dghgit Sep 6, 2026
f403921
modes: add BlockPermutation trait, bouncycastle-modes with AES CBC, a…
dghgit Sep 6, 2026
e018929
padding: add Padding trait and bouncycastle-padding (PKCS7, PaddedEnc…
dghgit Sep 6, 2026
17372c9
core, modes, aes-lowmemory: in-place block cipher API with compile-ti…
dghgit Sep 6, 2026
d1bee58
modes: add AES CFB128 mode with AES_CFB_* aliases, aes*-cfb CLI subco…
dghgit Sep 6, 2026
ca53601
core: ElectronicCodeBook (was BlockPermutation), slice block hooks, b…
dghgit Sep 6, 2026
891669b
modes: add Ecb (SP 800-38A Sec 6.1) with AES_ECB_* aliases and aes*-e…
dghgit Sep 6, 2026
45941d1
padding: add NoPadding (errors when asked to pad) with Padding::ALWAY…
dghgit Sep 6, 2026
74e0100
skills: add commit-range-report, a Markdown report of a commit range …
dghgit Sep 6, 2026
37d0b3b
core: replace StreamCipher with the split StreamCipherEncryptor / Str…
dghgit Sep 6, 2026
c158860
modes: Cfb becomes a stream cipher taking any length with no padding,…
dghgit Sep 6, 2026
8c7ec71
release notes: CFB becomes a stream cipher with a short final segment…
dghgit Sep 6, 2026
9c1b9b7
modes: pin the single-call vs chunked equivalence of Cfb and Cfb8 aga…
dghgit Sep 6, 2026
5cec55d
modes: add Ctr (SP 800-38A Sec 6.5), a stream cipher whose nonce leng…
dghgit Sep 6, 2026
4adbebb
modes: cross-check Ctr against BC Java's SICBlockCipher, which shares…
dghgit Sep 6, 2026
4bac3b3
Reverting the SKILL.md changes about producing a report since this se…
ounsworth Sep 9, 2026
fc2fcb7
Merge branch 'feature/stream-cipher' of git.bouncycastle.org:bc-rust …
ounsworth Sep 9, 2026
d66cd0b
Restructured the mem_usage_benchmarks sub-crate
ounsworth Sep 9, 2026
dc248f9
Reverting the SKILL.md changes about producing a report since this se…
ounsworth Sep 9, 2026
7539532
Moves sha512t_h0 tests out of unit tests and into integration tests. …
ounsworth Sep 9, 2026
b0bd491
rename sha2/tests/cavc_tests.rs to bc-test-data.rs to match other crates
ounsworth Sep 9, 2026
097c09b
Renaming / readability of some of the SHA2 internal traits.
ounsworth Sep 9, 2026
55f53f7
mem_usage_benches: fence the valgrind and ms_print snippets as text, …
dghgit Sep 9, 2026
0156990
sha2: the partial-byte example's three bits are message bits, not pad…
dghgit Sep 9, 2026
213473c
sha2: quote the SHA-512/t IV Generation Function as FIPS 180-4 s. 5.3…
dghgit Sep 9, 2026
5fc1370
CLAUDE.md: the build and test gates need --workspace, the mem_usage_b…
dghgit Sep 9, 2026
b11f8f6
sha2: sha512t_h0 asserts three-digit t and formats it as three digits…
dghgit Sep 9, 2026
5825050
Removing summary.md files
ounsworth Sep 12, 2026
0cc2799
minor tweaks to sha2
ounsworth Sep 12, 2026
b282942
Gave a massive hair-cut to Claude's massive release note
ounsworth Sep 12, 2026
8b48d93
Rename sha3/tasts/cavp_tests.rs to bc-test-data.rs for consistency wi…
ounsworth Sep 12, 2026
6cc74c2
small tweaks to sm3
ounsworth Sep 12, 2026
0558f26
doc change to the mem bench scripts
ounsworth Sep 12, 2026
b77f8a4
sha2: the SHA-224/256 message limit is 2^61 bytes rather than the 2^6…
dghgit Sep 13, 2026
cb0429c
sm3: the message limit is 2^61 bytes, GB/T 32905-2016 s. 5.1 allowing…
dghgit Sep 13, 2026
9c9861c
CLAUDE.md: add a scope-of-changes section, since an unrequested refac…
dghgit Sep 13, 2026
21b375c
Merge release/0.1.3alpha: HMAC and HKDF become generic utility crates…
dghgit Sep 13, 2026
91b3054
sm3: reinstate HMAC-SM3 under the per-crate layout the merge introduc…
dghgit Sep 13, 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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ mutants.out*/

.idea/
.vscode/

# Claude Code: ignore personal/local state, but share team tooling
# (skills, slash commands, subagents, and project settings.json).
.claude/*
!.claude/settings.json
!.claude/skills/
!.claude/commands/
!.claude/agents/
.claude/settings.local.json
.claude 2/
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ cargo run --release -p mem_usage_benches --bin bench_mldsa_mem_usage

The workspace has three top-level kinds of member:

1. `crypto/*` — one sub-crate per primitive (`sha2`, `sha3`, `hmac`, `hkdf`, `mlkem`, `mlkem_lowmemory`, `mldsa`, `mldsa_lowmemory`, `rng`, `hex`, `base64`, `utils`) plus the spine crates `core`, `core-test-framework`, and `factory`. Each crate is published as `bouncycastle-<name>` and depended on internally via the `workspace.dependencies` table in the root `Cargo.toml`.
2. `src/` — the umbrella `bouncycastle` crate, which is just `pub use` re-exports of every sub-crate (e.g. `bouncycastle::sha3`, `bouncycastle::mlkem`). It exists so downstream users can pull the whole library with one dependency; it has no code of its own.
1. `crypto/*` — one sub-crate per primitive (`sha2`, `sha3`, `sm3`, `hmac`, `hkdf`, `mlkem`, `mlkem_lowmemory`, `mldsa`, `mldsa_lowmemory`, `rng`, `hex`, `base64`, `utils`) plus the spine crates `core`, `core-test-framework`, and `factory`. Each crate is published as `bouncycastle-<name>` and depended on internally via the `workspace.dependencies` table in the root `Cargo.toml`.
2. `src/` — the umbrella `bouncycastle` crate, which is just `pub use` re-exports of every sub-crate (e.g. `bouncycastle::sha3`, `bouncycastle::sm3`, `bouncycastle::mlkem`). It exists so downstream users can pull the whole library with one dependency; it has no code of its own.
3. `cli/` — the `bc-rust` binary built on top of `bouncycastle`, exposing every primitive as a streaming stdin→stdout subcommand using `clap`.
4. `mem_usage_benches/` — stand-alone binary crates that measure peak stack usage of algorithms (cannot be done via criterion).

Expand Down
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ version = "0.1.3"

# *** Internal Dependencies ***
bouncycastle = { path = "./" }
bouncycastle-aes-lowmemory = { path = "./crypto/aes-lowmemory" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion point: weird to have an aes_lowmemory without an aes. Should we rename this, or are we planning a fast-but-big AES implementation? I know that FIPS 197 has the EqInvCipher(), but I suspect we can just tuck that into the same crate.

@dghgit dghgit Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This crate does not exist - see PR #115 - you already asked me to rename it, and I already have.

bouncycastle-base64 = { path = "./crypto/base64" }
bouncycastle-modes = { path = "./crypto/modes" }
bouncycastle-core = { path = "crypto/core" }
bouncycastle-core-test-framework = { path = "./crypto/core-test-framework" }
bouncycastle-factory = { path = "./crypto/factory" }
Expand All @@ -20,9 +22,11 @@ bouncycastle-mlkem = { path = "./crypto/mlkem" }
bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory" }
bouncycastle-mldsa = { path = "./crypto/mldsa" }
bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory" }
bouncycastle-padding = { path = "./crypto/padding" }
bouncycastle-rng = { path = "./crypto/rng" }
bouncycastle-sha2 = { path = "./crypto/sha2" }
bouncycastle-sha3 = { path = "./crypto/sha3" }
bouncycastle-sm3 = { path = "./crypto/sm3" }
bouncycastle-utils = { path = "./crypto/utils" }


Expand All @@ -41,6 +45,7 @@ version.workspace = true
edition.workspace = true

[dependencies]
bouncycastle-aes-lowmemory.workspace = true
bouncycastle-base64.workspace = true
bouncycastle-core.workspace = true
bouncycastle-factory.workspace = true
Expand All @@ -51,6 +56,9 @@ bouncycastle-mldsa.workspace = true
bouncycastle-mldsa-lowmemory.workspace = true
bouncycastle-mlkem.workspace = true
bouncycastle-mlkem-lowmemory.workspace = true
bouncycastle-modes.workspace = true
bouncycastle-padding.workspace = true
bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
bouncycastle-sm3.workspace = true
567 changes: 567 additions & 0 deletions alpha_0.1.3_release_notes.md

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions cli/src/aes_cbc_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! AES-CBC encryption and decryption, streaming stdin to stdout.
//!
//! Only the mode wiring lives here: the IV convention, key loading, stdin framing and
//! block-alignment enforcement are all in [`crate::block_mode_cmd`], shared with the `aes*-cfb` and
//! `aes*-ecb` commands. See that module for the command-line contract.
//!
//! CBC (NIST SP 800-38A Sec 6.2) provides confidentiality only. It does not detect tampering, and
//! neither the ciphertext nor the IV is authenticated -- 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::block_mode_cmd::{BLOCK_LEN, BlockModeAction, decrypt_stream, encrypt_stream, load_key};
use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256};
use bouncycastle::core::key_material::KeyMaterial;
use bouncycastle::core::traits::ElectronicCodeBook;
use bouncycastle::modes::{Cbc, Decrypting, Encrypting};

/// Names the mode in error messages.
const MODE: &str = "CBC";

pub(crate) fn aes128_cbc_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes128, 16>(action, &load_key::<16>(key, key_file, "AES-128"), output_hex);
}

pub(crate) fn aes192_cbc_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes192, 24>(action, &load_key::<24>(key, key_file, "AES-192"), output_hex);
}

pub(crate) fn aes256_cbc_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes256, 32>(action, &load_key::<32>(key, key_file, "AES-256"), output_hex);
}

/// Dispatches to the shared streaming loops with `Cbc` filled in as the mode.
fn run<P, const KEY_LEN: usize>(
action: &BlockModeAction,
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
{
match action {
BlockModeAction::Encrypt => {
encrypt_stream::<Cbc<P, Encrypting, KEY_LEN, BLOCK_LEN>, KEY_LEN, BLOCK_LEN>(
key, output_hex, MODE,
)
}
BlockModeAction::Decrypt => {
decrypt_stream::<Cbc<P, Decrypting, KEY_LEN, BLOCK_LEN>, KEY_LEN, BLOCK_LEN>(
key, output_hex, MODE,
)
}
}
}
76 changes: 76 additions & 0 deletions cli/src/aes_cfb8_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! AES-CFB8 encryption and decryption, streaming stdin to stdout.
//!
//! Only the mode wiring lives here: the IV convention, key loading and stdin framing are in
//! [`crate::stream_mode_cmd`] (and [`crate::block_mode_cmd`] for the key loader), shared with the
//! `aes*-cfb` commands. See those modules for the command-line contract.
//!
//! # Which CFB
//!
//! These commands are **CFB8**: the segment size is one byte (`s = 8` in NIST SP 800-38A Sec 6.3).
//! That is a different, non-interoperable mode from the CFB128 of `aes*-cfb`, not a variant of it:
//! the two ciphertexts agree on their first byte and differ everywhere after it. It also costs a
//! full AES call per byte of data, sixteen times the work of `aes*-cfb`, so prefer `aes*-cfb`
//! unless a byte-granular self-synchronising stream is required or the format demands CFB8.
//!
//! # Any length
//!
//! CFB8's segment is a single byte, so these commands accept input of any length, pad nothing, and
//! emit a ciphertext exactly as long as the plaintext.
//!
//! # Warning
//!
//! CFB8 provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the IV is authenticated. Appendix D, Table D.2 gives "SBE in the decryption of Cj" plus random
//! errors in the next `b/s` segments: flipping a ciphertext bit flips the *same* bit of the *same*
//! plaintext byte, corrupts the following 16 bytes, and then decryption resynchronises. Do not
//! decrypt data you have not authenticated separately.

use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key};
use crate::stream_mode_cmd::run_stream_mode;
use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256};
use bouncycastle::core::key_material::KeyMaterial;
use bouncycastle::core::traits::ElectronicCodeBook;
use bouncycastle::modes::{Cfb8, Decrypting, Encrypting};

pub(crate) fn aes128_cfb8_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes128, 16>(action, &load_key::<16>(key, key_file, "AES-128"), output_hex);
}

pub(crate) fn aes192_cfb8_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes192, 24>(action, &load_key::<24>(key, key_file, "AES-192"), output_hex);
}

pub(crate) fn aes256_cfb8_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes256, 32>(action, &load_key::<32>(key, key_file, "AES-256"), output_hex);
}

/// Dispatches to the shared streaming loops with `Cfb8` filled in as the mode.
fn run<P, const KEY_LEN: usize>(
action: &BlockModeAction,
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
{
run_stream_mode::<
Cfb8<P, Encrypting, KEY_LEN, BLOCK_LEN>,
Cfb8<P, Decrypting, KEY_LEN, BLOCK_LEN>,
KEY_LEN,
BLOCK_LEN,
>(action, key, output_hex)
}
77 changes: 77 additions & 0 deletions cli/src/aes_cfb_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! AES-CFB128 encryption and decryption, streaming stdin to stdout.
//!
//! Only the mode wiring lives here: the IV convention, key loading and stdin framing are in
//! [`crate::stream_mode_cmd`] (and [`crate::block_mode_cmd`] for the key loader), shared with the
//! `aes*-cfb8` commands. See those modules for the command-line contract.
//!
//! # Which CFB
//!
//! These commands are **CFB128**: the segment size is the full 16-byte block (`s = b` in NIST
//! SP 800-38A Sec 6.3). SP 800-38A also defines `s = 8`, which is a different, non-interoperable
//! mode -- if you need CFB8, the `aes*-cfb8` commands are it -- and `s = 1`, which this library does
//! not provide.
//!
//! # Any length
//!
//! CFB is a stream cipher, so unlike `aes*-cbc` and `aes*-ecb` these commands accept input of any
//! length and pad nothing; the ciphertext is exactly as long as the plaintext. For a message that
//! is not a whole number of blocks the last partial block is a short final segment, which is what
//! every streaming CFB128 implementation does; see the `bouncycastle_modes::Cfb` docs.
//!
//! # Warning
//!
//! CFB provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the IV is authenticated. CFB's malleability is more directly exploitable than CBC's: Appendix D,
//! Table D.2 gives "SBE in the decryption of Cj" -- flipping a ciphertext bit flips the *same* bit
//! of the plaintext in the *same* block, so an attacker edits the block they aimed at, at the cost
//! of randomising the next one. Do not decrypt data you have not authenticated separately.

use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key};
use crate::stream_mode_cmd::run_stream_mode;
use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256};
use bouncycastle::core::key_material::KeyMaterial;
use bouncycastle::core::traits::ElectronicCodeBook;
use bouncycastle::modes::{Cfb, Decrypting, Encrypting};

pub(crate) fn aes128_cfb_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes128, 16>(action, &load_key::<16>(key, key_file, "AES-128"), output_hex);
}

pub(crate) fn aes192_cfb_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes192, 24>(action, &load_key::<24>(key, key_file, "AES-192"), output_hex);
}

pub(crate) fn aes256_cfb_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes256, 32>(action, &load_key::<32>(key, key_file, "AES-256"), output_hex);
}

/// Dispatches to the shared streaming loops with `Cfb` filled in as the mode.
fn run<P, const KEY_LEN: usize>(
action: &BlockModeAction,
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
{
run_stream_mode::<
Cfb<P, Encrypting, KEY_LEN, BLOCK_LEN>,
Cfb<P, Decrypting, KEY_LEN, BLOCK_LEN>,
KEY_LEN,
BLOCK_LEN,
>(action, key, output_hex)
}
84 changes: 84 additions & 0 deletions cli/src/aes_ctr_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! AES-CTR encryption and decryption, streaming stdin to stdout.
//!
//! Only the mode wiring lives here: the nonce convention, key loading and stdin framing are in
//! [`crate::stream_mode_cmd`] (and [`crate::block_mode_cmd`] for the key loader), shared with the
//! `aes*-cfb` and `aes*-cfb8` commands. See those modules for the command-line contract.
//!
//! # The nonce is 12 bytes and the counter is 4
//!
//! NIST SP 800-38A Sec 6.5 builds CTR on a sequence of counter blocks, and Appendix B.2's second
//! approach makes each one a message nonce followed by a counter. These commands use the
//! `AES_CTR_*` aliases, so the nonce is **12 bytes** and the counter is the remaining 4, giving
//! 2^32 blocks -- 64 GiB -- in a single message.
//!
//! `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.
//!
//! # Any length
//!
//! CTR is a stream cipher: input of any length is accepted, nothing is padded, and the output is
//! exactly as long as the input.
//!
//! # Warning
//!
//! CTR provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the nonce is authenticated. It is the most malleable of the modes here: flipping any ciphertext
//! bit flips exactly the corresponding plaintext bit and affects nothing else (SP 800-38A
//! Appendix D, Table D.2, "SBE in the decryption of Cj"), so an attacker can edit the plaintext at
//! will, wherever they like, without any garbling to give it away. Do not decrypt data you have not
//! authenticated separately.
//!
//! A repeated nonce is fatal here rather than merely unwise: the same nonce under the same key
//! gives the same keystream, and two messages XORed with the same keystream leak their XOR. The
//! nonce is drawn from the OS-backed DRBG for exactly that reason, and there is no way to supply
//! one.

use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key};
use crate::stream_mode_cmd::run_stream_mode;
use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256, CTR_NONCE_LEN};
use bouncycastle::core::key_material::KeyMaterial;
use bouncycastle::core::traits::ElectronicCodeBook;
use bouncycastle::modes::{Ctr, Decrypting, Encrypting};

pub(crate) fn aes128_ctr_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes128, 16>(action, &load_key::<16>(key, key_file, "AES-128"), output_hex);
}

pub(crate) fn aes192_ctr_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes192, 24>(action, &load_key::<24>(key, key_file, "AES-192"), output_hex);
}

pub(crate) fn aes256_ctr_cmd(
action: &BlockModeAction,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<Aes256, 32>(action, &load_key::<32>(key, key_file, "AES-256"), output_hex);
}

/// Dispatches to the shared streaming loops with `Ctr` filled in as the mode.
fn run<P, const KEY_LEN: usize>(
action: &BlockModeAction,
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
{
run_stream_mode::<
Ctr<P, Encrypting, KEY_LEN, BLOCK_LEN, CTR_NONCE_LEN>,
Ctr<P, Decrypting, KEY_LEN, BLOCK_LEN, CTR_NONCE_LEN>,
KEY_LEN,
CTR_NONCE_LEN,
>(action, key, output_hex)
}
Loading
Loading