Skip to content

Part 5: AES tweaks: AES_CBC_* aliases, flat-array one-shots and streaming, simpler CLI - #109

Open
dghgit wants to merge 6 commits into
feature/block-cipher-paddingfrom
feature/aes-tweaks
Open

Part 5: AES tweaks: AES_CBC_* aliases, flat-array one-shots and streaming, simpler CLI#109
dghgit wants to merge 6 commits into
feature/block-cipher-paddingfrom
feature/aes-tweaks

Conversation

@dghgit

@dghgit dghgit commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Follow-ups to the block-cipher stack, stacked on #97 (padding). Three commits:

  1. aes-lowmemory: AES_CBC_128 / AES_CBC_192 / AES_CBC_256 type aliases over bouncycastle_modes::Cbc, generic in the direction marker, so callers never spell out the KEY_LEN / BLOCK_LEN const parameters (AES_CBC_128::<Encrypting>::encrypt(&key, &msg)). They live in the AES crate because the modes crate is deliberately cipher-agnostic; aes-lowmemory gains a dependency on modes (modes keeps its dev-dependency on aes-lowmemory -- a Cargo-permitted dev-dep cycle, verified by the modes test suite). Naming follows HMAC_SHA256 / HKDF_SHA256. Each alias has a two-direction doctest, since Rust only checks an alias's bounds at a use site.

  2. core: the block-cipher one-shots take a flat [u8; LEN]. encrypt / encrypt_rng / encrypt_out / encrypt_out_rng and decrypt / decrypt_out replace the block-shaped encrypt_blocks* / decrypt_blocks* from core: split BlockCipher into block-aligned BlockCipherEncryptor/Decryptor #96. LEN % BLOCK_LEN == 0 is enforced at compile time by an inline const assertion at the instantiating call site -- no runtime length check and no error variant; a 47-byte array is a compile_fail doctest. Internally LEN / BLOCK_LEN is not nameable without generic_const_exprs, so the buffer is walked with as_chunks: pairs first (so a mode's two-block path is used), then the at-most-one remaining block.

  3. core: flat streaming methods replace the by-value block methods; simpler CLI. do_encrypt<LEN> / do_encrypt_out<LEN> and do_decrypt<LEN> / do_decrypt_out<LEN> are provided; the by-value do_*_blocks<N> are removed. The one block-shaped method left is the implementor hook do_*_blocks_out<N> over [[u8; BLOCK_LEN]; N] -- it guarantees an implementation never sees a partial block and that in/out lengths agree at compile time, and serves runtime-count tails. An implementor now writes only init[_rng] and that hook. The CLI's aes*-cbc commands stream stdin into a flat 1 KiB buffer (do_*_out::<1024> for full chunks, do_*::<16> for the whole-block tail); the block staging buffer and partial-block carry logic are gone. Behaviour and error messages unchanged (16 CLI subprocess tests).

None of this has shipped (0.1.3 is unreleased), so the removals are not breaking changes; the #96 release-note bullet is updated to describe the final API. Verified: cargo fmt --check clean, full workspace green (92 suites / 586 tests), benches compile, zero rustdoc warnings.

Merge after #97. Land by pushing to origin (GitHub is a mirror).

dghgit and others added 3 commits September 2, 2026 16:46
One alias per AES key length over bouncycastle_modes::Cbc, generic in the
direction marker, so callers never spell out the KEY_LEN / BLOCK_LEN const
parameters. They live in the AES crate (new cbc.rs module) because the modes
crate is deliberately cipher-agnostic; this adds bouncycastle-modes as a
dependency of aes-lowmemory, leaving modes' dev-dependency on aes-lowmemory
as a Cargo-permitted dev-dep cycle. Each alias carries a doctest exercising
both directions, since Rust only checks an alias's bounds at a use site, and
the crate docs gain a CBC usage example. Naming follows the HMAC_SHA256 /
HKDF_SHA256 convention, with the same non_camel_case_types allowance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…provided

The one-shots on BlockCipherEncryptor / BlockCipherDecryptor -- encrypt,
encrypt_rng, encrypt_out, encrypt_out_rng and decrypt, decrypt_out -- now take
a `[u8; LEN]` instead of an array of blocks. They replace the block-shaped
encrypt_blocks / encrypt_blocks_rng / encrypt_blocks_out / encrypt_blocks_out_rng
and decrypt_blocks / decrypt_blocks_out from #96, which nothing outside tests
and docs used and which the flat form makes redundant (0.1.3 is unreleased, so
nothing shipped changes).

LEN must be a whole number of blocks and this is enforced at compile time: an
inline `const { assert!(LEN % BLOCK_LEN == 0) }` fails at the call site that
instantiates a misaligned LEN, so no runtime length check and no error variant.
Inside, LEN / BLOCK_LEN is not nameable without generic_const_exprs, so the
shared helpers walk the buffer with as_chunks -- pairs first, so a mode's
two-block path is used, then the at-most-one remaining block.

The by-value streaming methods do_{en,de}crypt_blocks are now provided in terms
of their _out forms, shrinking the implementor contract to init[_rng] and
do_*_blocks_out.

Tests and docs updated to the flat form: the core-test-framework suite covers
the single-block flat forms (all it can form generically); modes/tests/cbc_tests
checks 3- and 4-block flat arrays agree byte-for-byte with the streaming API in
both directions and via _out; sp800_38a_tests runs the F.2 decrypt vectors
through the flat one-shot; the AES_CBC_* docs gain flat and streaming examples
and a compile_fail doctest for a 47-byte array. Release note updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lify the CLI

BlockCipherEncryptor / BlockCipherDecryptor gain provided flat streaming
methods -- do_encrypt<LEN> / do_encrypt_out<LEN> and do_decrypt<LEN> /
do_decrypt_out<LEN> over a `[u8; LEN]`, with the same compile-time
LEN % BLOCK_LEN == 0 assertion as the one-shots, which now delegate to them.
The by-value do_{en,de}crypt_blocks<N> are removed. The one block-shaped
method left is the implementor hook do_{en,de}crypt_blocks_out<N> over
[[u8; BLOCK_LEN]; N]: it is what guarantees an implementation never sees a
partial block and that in/out lengths agree at compile time, and it serves
runtime-count tails one block at a time. An implementor now writes only
init[_rng] and that hook; Cbc and the padding test toy drop their by-value
wrappers, and the padding adapter's two single-block calls use do_encrypt /
do_decrypt.

CLI: aes*-cbc stream stdin into a flat 1 KiB buffer and hand full chunks to
do_*_out::<1024>, the whole-block tail to do_*::<16>; the block staging
buffer and partial-block carry logic are gone (reads simply accumulate until
the buffer is full). Behaviour and error messages unchanged; the 16 CLI
subprocess tests still pass.

Tests: framework, modes (cbc, sp800-38a, acvp) and benches moved to the
flat methods; block-structured tests use two small helpers over the hook.
Release notes updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@dghgit dghgit changed the title AES tweaks: AES_CBC_* aliases, flat-array one-shots and streaming, simpler CLI Part 5: AES tweaks: AES_CBC_* aliases, flat-array one-shots and streaming, simpler CLI Sep 2, 2026
dghgit and others added 3 commits September 3, 2026 17:12
BlockCipher declared only MAX_SECURITY_STRENGTH, which Algorithm already
has, so every implementor of BlockPermutation (which also implemented
Algorithm) had two copies of the same constant and had to qualify every
use of it. BlockPermutation, BlockCipherEncryptor and BlockCipherDecryptor
are now bounded on Algorithm instead, and Cbc implements Algorithm with
its permutation's ALG_NAME and MAX_SECURITY_STRENGTH.

Raised in review of PR #107.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A block cipher mode never changes the length of its data, so a separate
output buffer was only ever a copy, and a copy of plaintext is one more
thing to scrub. The one-shots, the flat streaming methods and the
implementor hook now all take a single `&mut [u8; LEN]` /
`&mut [[u8; BLOCK_LEN]; N]` and transform it in place; `encrypt` and
`encrypt_rng` return just the init data. The `_out` variants and the
`usize` byte counts (always LEN) are gone. The compile-time
`LEN % BLOCK_LEN == 0` check is unchanged.

The data methods keep a `Result` for modes with a per-initialization
data limit (counter-based modes); CBC never fails them, and the docs
say so.

Cbc, PaddedEncryptor/PaddedDecryptor, the test framework, the modes
tests and benches, the doc examples and the CLI follow. The padding
layer pads and encrypts the final block inside its `Secret`, so only
ciphertext is ever copied out of it.

Raised in review of PR #107.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A pure reordering (verified: the sorted non-blank lines are identical
before and after). Each trait keeps its doc comment and any todo notes
attached to it; SecurityStrength keeps its two impl blocks. Sorted
case-insensitively by item name.

Requested in review of PR #107.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant