Skip to content

Simple ciphers: stream + symmetric cipher unification off the release branch, plus SHA-512/t for any t - #133

Open
dghgit wants to merge 254 commits into
release/0.1.3alphafrom
feature/simple-ciphers
Open

dghgit wants to merge 254 commits into
release/0.1.3alphafrom
feature/simple-ciphers

Conversation

@dghgit

@dghgit dghgit commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Consolidates the stacked pair #113 (feature/stream-cipher → release/0.1.3alpha) and #115
(feature/symmetric-cipher → feature/stream-cipher) into a single branch off the release
branch, and adds the SHA-512/t work on top.

feature/simple-ciphers was built by branching from release/0.1.3alpha and merging
feature/stream-cipher then feature/symmetric-cipher; both merges fast-forwarded, so the
content below the two new commits is exactly what #113 and #115 already carried — this is a
re-packaging, not new review surface. #113 (+21,938) and #115 (+2,310) sum to roughly the
+23,499 here.

New in this PR, on top of those two

core: SecurityStrength::from_bits / from_bytes become const fn (3912434) — two
keywords, no behaviour change. Needed so an associated const can derive a strength from a const
generic; split out ahead of the feature per CLAUDE.md's scope-of-changes rule.

sha2: SHA512t<T> is usable for every t FIPS 180-4 s. 5.3.6 defines a hash for (785dbef).
Previously only T = 224 and T = 256 had parameter impls, so nothing else could be named.

  • The t-value checks are back, and they are the spec's. b11f8f6 had narrowed sha512t_h0 to
    100 <= t < 512 and emitted a fixed three digits, since only 224 and 256 were reachable. With
    arbitrary t reachable that would produce the "0256" spelling s. 5.3.6 explicitly forbids —
    and hence the wrong IV — for every t below 100. The one- and two-digit branches are restored
    and check_t asserts the section's own rule: positive, < 512, not 384.
  • One deviation, documented: t must be a multiple of 8, because Hash is byte-oriented and
    a 100-bit digest has no representation here. BC Java's SHA512tDigest imposes the identical
    restriction, so the two libraries accept the same set of truncations.
  • Unapproved truncations are gated the way ElectronicCodeBook::ENCRYPTION_APPROVED gates
    two-key TDEA: SHA512tParams::<T>::FIPS_APPROVED is public and SHA512Internal::new asserts it
    in an inline const, so an unapproved t is a compile error at the call site and
    new_allow_unapproved_t() is the deliberate way in. That also blocks Default, which keeps an
    unapproved truncation out of generic code by accident.
  • ALG_NAME, OUTPUT_LEN and MAX_SECURITY_STRENGTH are derived from t, with const
    assertions pinning them to the values 224 and 256 previously had by hand.

Verification

  • cargo test --workspace: 943 passed, 0 failed (was 923). cargo fmt --check clean; no new
    clippy warnings. Both new commits build independently.
  • Spec text read from a freshly downloaded FIPS 180-4, not from recall.
  • Test vectors cross-checked against BC Java's SHA512tDigest, an independent implementation:
    eight truncations (8, 16, 24, 88, 96, 104, 264, 504) spanning all three decimal branches, over
    the FIPS 180-4 Appendix C messages plus the one-million-'a' case. The 224/256 rows in the same
    table match the NIST-published values.
  • cargo mutants on the changed files: 259 mutants — 180 caught, 5 timeout-kills, 69 unviable,
    5 missed. All five missed are the pre-existing XOR/OR equivalences already annotated at their
    sites in ch, maj and do_final_internal; no new missed mutants.

Note for reviewers

Adding const to a public core function is a forward compatibility commitment, and
SHA512tParams is currently its only caller. The alternative was a private copy of the rounding
ladder inside sha2, free to drift from the real one — happy to switch if the API-surface cost is
the greater worry.

🤖 Generated with Claude Code

dghgit and others added 30 commits September 6, 2026 12:30
…ptor with multi-block and one-shot methods (PR #107)
…me lengths, AES_CBC_* aliases, simpler CLI (PR #109)
…locks8, SymmetricCipherEncryptor/Decryptor (from feature/sm4); CFB follows suit
…cb CLI subcommands; block-mode CLI generic over INIT_DATA_LEN
…S_PADS; SymmetricCipherEncryptor::do_final reports its output length
…rams/HashMLDSAParams/MLKEMParams traits, one impl per parameter set (#117)
…eamCipherDecryptor pair, shaped like the block cipher pair (in place, any length, generated init data); TestFrameworkStreamCipher implemented in place of its todo!()
… and Cfb8 (SP 800-38A Sec 6.3, s = 8) is added, with AES_CFB8_* aliases, aes*-cfb8 CLI subcommands and a shared stream-mode CLI
…, CFB8 is added, and the StreamCipher trait is replaced by the split encryptor/decryptor pair; re-measured throughput and mutation figures
…inst real AES at all three key lengths, not only the toy permutation
…th picks the counter width (max 4 bytes) and which errors rather than repeat a counter, with AES_CTR_* aliases and aes*-ctr CLI subcommands
… the nonce-plus-counter construction, pinning the 1, 2 and 3-byte counter widths that the ACVP and OpenSSL vectors cannot reach
…ptor with multi-block and one-shot methods (PR #107)
Comment thread crypto/cipher/src/stream.rs
@@ -1,76 +1,267 @@
//! Generic behaviour tests for the symmetric cipher traits.

@ounsworth ounsworth Sep 30, 2026 •

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.

This, and key_stream.rs should move to the bouncycastle-cipher crate, once that refactor has been done to create it.

Comment thread crypto/core/src/lib.rs Outdated
@@ -8,5 +8,7 @@

pub mod errors;
pub mod key_material;
pub mod security_strength;
pub mod stream_cipher;

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.

Remove, after refactor.

ounsworth and others added 8 commits September 30, 2026 14:47
…symmetric_ciphers.rs into aead.rs and block_cipher.rs, turn core's aead_tagged_tests into a runner driven by AES-GCM, and move the buffering-toy test of core's default one-shots back to core/tests

Assisted-by: Claude:claude-fable-5-1
Note: crypto/aes/src/padded_mode.rs's PaddedMode is the same unsealed
projection pattern this replaces in ascon; it can be de-duplicated onto
core's Direction::Select in a follow-up, which is deliberately not bundled
here.

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and the byte-slice helpers) into feature/simple-ciphers

Replaces core::hint::black_box with a volatile store/load barrier used by Condition::select/negate/swap/is_in_list,
ct_eq_bytes, ct_eq_zero_bytes and conditional_copy_bytes. Adds ct_eq_bytes_mask and has conditional_copy_bytes take a
Condition<u32> so ML-KEM implicit rejection never passes the secret through a bool. Sets rust-version = "1.88". Closes #128.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a backslash -- a backslash that is not the of an escaped byte is reported as InvalidHexCharacter at its own index, with regression tests for the trailing, lone and mid-input cases.

Assisted-by: Claude:claude-fable-5-1
… > file` is now exactly N bytes. Newline preserved for -x

Assisted-by: Claude:claude-fable-5-1
Comment thread crypto/utils/Cargo.toml
@@ -2,6 +2,7 @@
name = "bouncycastle-utils"
version.workspace = true
edition.workspace = true
rust-version.workspace = true

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.

Note-to-self: huh?

Comment thread crypto/utils/src/ct.rs
// msp430 and avr; another backend gets no such promise. `core::hint::black_box`, used
// previously, is weaker still: its documentation calls it "best-effort" and says it "does not
// offer any guarantees for cryptographic or security purposes".
// ---------------------------------------------------------------------------------------------

@ounsworth ounsworth Oct 1, 2026 •

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.

Note-to-self: read and make sure it makes sense.

Comment thread cli/src/main.rs
@@ -1,3 +1,11 @@
mod aes_cbc_cmd;

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.

Note-to-self: leaving un-viewed until I check the ascon bits

ounsworth and others added 11 commits September 30, 2026 22:43
…ith the CLAUDE.md pointer updated to match

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bouncycastle_core::hazmat defines the term; each crate with hazmat items declares pub mod hazmat and
never re-exports out of it. Moved, paths only: ElectronicCodeBook, KeyStream and
do_hazardous_operations in core; AESInternal and AES_ECB_* in aes; CtrKeyStream and Ecb in modes.
ML-KEM's encaps_internal becomes hazmat::EncapsWithRandomness and HashDRBG80090A::new_unititialized
becomes hazmat::NewUninitialized (typo fixed), as extension traits so the call needs the hazmat
import. No logic change, no mutation run owed; test count 1040 before and after.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
AES_CBC_* and AES_ECB_* were written through aes's own PaddedMode trait, the same unsealed direction
projection that 64f3923 replaced in ascon; they now use Direction::Select and the trait and its
module go. Type aliases only, no behaviour change, no mutation run owed; test count 1040 before and
after.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ng to be sub-modules of a new crate bouncycastle-cipher. Assisted-by: claude-fable-5.1
StreamCipher loses its R: RNG type parameter (which it had to have because bouncycastle-core couldn't depend on bouncycastle-rng): do_encrypt_init now
draws from HashDRBG_SHA512::new_from_os(), as Cbc, Gcm and Ccm do.

Assisted-by: claude-opus-5-5
…al contract tests move to tests/

No behaviour change.

Assisted-by: Claude:claude-fable-5-1
…-utils, with the component layer for composing suspended states

Assised-by:claude-fable-5-1
Comment thread crypto/core/src/traits.rs
/// that has to see the whole message before it can process any of it (see
/// [`AEADCipherEncryptor`]), may also return [`SymmetricCipherError::GenericError`] if the
/// input would exceed it.
fn do_encrypt_out(

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.

We have provided a do_encrypt_out, but not a do_encrypt() -> Vec<u8>. Create one, wrapped in a #[cargo(alloc)]` that we can clean up later after Jason's pattern lands.

@ounsworth ounsworth Oct 2, 2026 •

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.

Ditto for the AEAD trait -- most of the things there needs to be twinned.

…nstead of buffering the message (PR #164)

CcmEncryptor / CcmDecryptor no longer hold a copy of the payload, which gave them a stack
footprint that grew with the message. DATA_LEN is now the exact payload length, committed to
B0 at init, so the streaming methods release every byte as it arrives, FINAL_LEN is the tag,
and the decryptor holds back only the bytes past the frame as the possible inline tag. More
than DATA_LEN is refused at the update and less at the final, on both sides. The AES aliases
are renamed AES_CCM_*_Packet, CCM_MAX_BUFFER_LEN is gone, and a value is now the Ccm state
plus the AAD capacity at any DATA_LEN. The inherent Ccm API, which takes the lengths per
message, is unchanged.

The AEAD one-shots and finals follow the library's trailing `_out` convention
(encrypt_detached_out, decrypt_with_aad_out, do_final_detached_out and so on) across core,
cipher, aes and ascon, and the AEADCipherEncryptor trait docs are rewritten for a calling
application. QUALITY_AND_STYLE gains the rule that public API docs carry no implementation
detail. Suspend-and-resume round-trip tests cover every mode, adapter and AES alias, and the
shared test framework takes a fixed message length so it can drive the fixed-frame pair.

Review fixes folded in: CcmDecryptor::decrypt_out_max_len is `ciphertext_len.min(DATA_LEN)`,
so a short inline C through the one-shots reaches the final and is DecryptionFailed rather
than OutputBufferTooSmall, with a test at DATA_LEN = 32; the adapters' update docs say a
refused non-empty call still ends the AAD phase; and three wording errors in the rewritten
trait docs are fixed.

cargo mutants over crypto/cipher/src/modes/ccm.rs with the cipher and aes tests: 313 mutants,
231 caught, 78 unviable, 2 timeouts that are real kills, 2 missed (the OR/XOR equivalence in
format_b0's flags octet).

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
//! root: this crate holds the traits, and `bouncycastle-aes` and `bouncycastle_cipher::modes` hold their
//! implementors. The safe adapters that wrap them -- `bouncycastle_cipher::stream::StreamCipher`
//! over a [`KeyStream`], the modes over an [`ElectronicCodeBook`] -- are not hazmat and stay where
//! they are.

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.

Adjust / shorten this.

Comment thread crypto/core/src/errors.rs
/// Errors from [`Suspendable`](crate::traits::Suspendable) and
/// [`SuspendableKeyed`](crate::traits::SuspendableKeyed). Defined in `bouncycastle-utils` next to
/// the version-header helpers that raise it, and re-exported here with the other error types.
pub use bouncycastle_utils::suspendable_state::SuspendableError;

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.

This should be moved here, not re-imported.

This branch has not been deployed

No deployments
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.

3 participants