Repository navigation
Conversation
…S_PADS; SymmetricCipherEncryptor::do_final reports its output length
…with API changes and per-commit summaries
…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
…eme as well as a direction, via a PaddedMode projection, so the two block modes name their padding in the type
…ricCipherDecryptor with FINAL_LEN = 0, by blanket impls over the in-place methods, so any of the five modes can be held through one trait
…to AEADCipher, its only remaining user; the framework suite follows, and both AEAD security-strength loops gain the key-length guard the other suites already had
…_2blocks (were *_blocks2), the reading Mike Ounsworth gave them in 736b0ac; modes, aes, the framework suite, benches and notes follow
… a debug self-check that sub_word's eight broadcast planes agree, in-place benches with decrypt paths for every key length and key-expansion throughput, summary.md removed, and acvp_tests.rs becomes bc-test-data.rs; the rest of Mike Ounsworth's 736b0ac review that still applied
…decrypt_8blocks (were *_blocks8), so they read like the pair methods; modes, the framework suite, benches and notes follow
…pt_4blocks (was eight): AES fills a pair and the u16/u32-plane engines fill four, so eight was two passes for every engine and left a four-lane engine half-empty on a 4-to-7-block tail; modes chunk fours, then pairs, then singles, the framework suite and the rotated-four toy pin the four path, benches and notes follow
…thods by their current name
…ipherEncryptor / SimpleCipherDecryptor; the framework suite becomes TestFrameworkSimpleCipher and the modes API test file is renamed to match; aes, padding, modes and the notes follow
…p Vim swap file that d98f703 swept in
…ems like a personal workflow rather than a general thing.
…into feature/stream-cipher
…ems like a personal workflow rather than a general thing.
…Added a note about this to QUALITY_AND_STYLE.md.
…so the lib target the src/ move introduced does not compile them as failing Rust doctests
…0-185 types to the Memory Usage table -- the squeezer is already what the KMACXOF, TupleHashXOF and ParallelHashXOF into_squeezer calls hand back, and leaving it unnamed produced five rustdoc warnings for links to a private item (zero now). bench_sha3_mem_usage prints size_of for the fourteen SP 800-185 types and both squeezers; the table takes its figures from that run and notes ParallelHash's partial-block heap buffer. Assisted-by: Claude:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… -- the state is the inner sponge after K xor ipad, and Keccak-f is a permutation, so the key is not kept out of the state by omitting it; the suspend section and the Security Considerations bullet now say to store it as securely as the key. Assisted-by: Claude:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he bytes arrive, instead of buffering the block in a Vec and hashing it whole -- the per-block digest is SHAKE(block, 2c) either way, so output is unchanged (NIST sample values and the chunking test pass), but the state is now a fixed size whatever B is, which suspend/resume needs, and the crate loses a heap allocation. ParallelHash grows from 496 to 920 bytes and ParallelHashXOF from 488 to 912 (bench_sha3_mem_usage); the Memory Usage table follows and drops its heap note. Assisted-by: Claude:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…OF, TupleHash, TupleHashXOF, ParallelHash, ParallelHashXOF and the LengthBoundSqueezer the three XOF forms hand back, each as a SuspendableComponent over the SHA3-family state (as the cipher modes are over their permutations) with a Suspendable shell per type. Every type writes its own variant tag, eight per width from a base on SHAKEParams (7..=14 for the 128-bit set, 15..=22 for 256), so a KMAC state cannot resume as KMACXOF and finish with the other length encoding; the squeezer's two phases share one tag and are told apart by the sponge's own squeezing flag, as SHAKE and SHAKESqueezer already are. cSHAKE's state is the family state plus its customized byte (416 bytes); the functions built on it write that under their tag and their own fields after it: the output length for KMAC and TupleHash (424), and for ParallelHash the inner sponge, block size, fill and count (852, 860 with the output length). Resume refuses a squeezing sponge in an absorbing type, a customized byte other than 0/1, an empty function name on a derived function, a zero block size, a fill not inside the block, and an inner sponge that is squeezing. KMAC is Suspendable rather than SuspendableKeyed: the key is absorbed at construction and never used again, so a re-supplied key could neither rebuild anything nor be checked, and the docs say the state inverts to the key and must be stored as one. KMACInternal gains the Clone derive KMACXOFInternal already had. Tests: round trips in every phase for all fourteen types (ParallelHash mid-block, on a boundary and before its first block), cross-type and cross-width rejections, each field check, 22 distinct tags, and the pinned lengths. quality_stats on crypto/sha3: unwraps in core code 32 -> 32, Err() 27 -> 33, code lines 2178 -> 2553, test lines 2610 -> 2842. cargo mutants over the five changed files: 608 mutants, 410 caught, 191 unviable, 6 timeouts, 1 missed -- the | -> ^ in into_squeezer_partial_bits_with_suffix, the OR/XOR equivalence noted in the code. The timeouts are loops that no longer terminate (the bytepad zero-fill, the encoding width count, and ParallelHash's block fill with its digest step or boundary test removed), so they are kills. Assisted-by: Claude:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ome other docs fixes
…istent style. Heavily writted by Claude Fable 5.1
…hkdf, hmac. - Addad a new HKDF-SHA384 alias (wycheproof had it, so might as well expose and test it), along with factory and CLI wirings for it; - the CLI HKDF fixes -- we exposed cli params for all the HKDF componints (ikm, additional info, hkdf_expand length, etc, but these were not wired up correctly); - new CLI test scripts test_hkdf.sh and test_hmac.sh.
…-data and wycheproof) are now loaded from core-test-framework. This means that now only bouncycastle-core-test-framework, and no other crate, has a dependency on serde_json. Written-by: Claude Fable 5.1.
| impl Algorithm for AES128Internal { | ||
| const ALG_NAME: &'static str = AES128Params::ALG_NAME; | ||
| const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; | ||
| } |
There was a problem hiding this comment.
This is cute to impl Algorithm once for AES128, but that's not right. The ALG_NAME on the AES_GCM_128 object needs to be "AES_GCM_128", not "AES_128".
If we're going to say that "AES_CBC_128" and "AES_GCM_128" are different algorithms with different key types (which we really should), then those need separate Algorithm impls.
Looking at your complicated tower of definitions, we'd need to impl Algorithm on this:
pub type AES_CBC_128<Dir, Pad> = <Dir as Direction>::Select<
PaddedBlockCipherEncryptor<
Cbc<AES128Internal, Encrypting, 16, AES_BLOCK_LEN>,
Pad,
16,
AES_BLOCK_LEN,
AES_BLOCK_LEN,
>,
PaddedBlockCipherDecryptor<
Cbc<AES128Internal, Decrypting, 16, AES_BLOCK_LEN>,
Pad,
16,
AES_BLOCK_LEN,
AES_BLOCK_LEN,
>,
>;Looking at the current user docs in aes/src/cbc.rs, we have:
type Enc = AES_CBC_128<Encrypting, NoPadding>;
let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap();
// A whole block is fine, and comes out the same length.
let mut out = [0u8; 16];
let (_iv, written) = Enc::encrypt_out(&key, &[0u8; 16], &mut out).expect("aligned");Then, that type Enc = AES_CBC_128<Encrypting, NoPadding>; should impl Algorithm; ie you should be able to do Enc::ALG_NAME which should be "AES_CBC_128" and Enc::MAX_SECURITY_STRENGTH which should be "_128bit", because after all, AES_CBC_128 is an algorithm.
There was a problem hiding this comment.
@ounsworth Agreed. I changed the mode types so each user-facing AES alias now has its own Algorithm::ALG_NAME (AES_CBC_128, AES_GCM_128, etc.) while preserving the appropriate security strength.
I added a ModeNames trait in the cipher crate that the AES internals implement, and each mode's Algorithm impl picks up its corresponding name from there.
The diff is unfortunately a bit larger than the conceptual change because adding that bound required updating the generic mode types/tests. I also added a cipher-local test block cipher rather than generating future technical debt for core. The alternative would've put mode-specific knowledge into core::ElectronicCodeBook (or otherwise making core depend on the set of modes defined by the cipher crate), which would require rewrites for new updates.
All public AES mode aliases now have mode-specific algorithm names and the workspace tests pass aside from the unrelated local Ascon vector issue.
Handled in commit 79c859d
There was a problem hiding this comment.
@ounsworth Also fixed the specific key issue you raised; valid given there was no strict counter to cross-algorithm reuse.
Each public AES mode now reports its own ALG_NAME (AES_CBC_128, AES_GCM_128, etc.) while preserving the appropriate security strength.
I also added opt-in algorithm binding to KeyMaterial: an unbound SymmetricCipherKey preserves existing behavior, but once bound to (say) AES_CBC_128, GCM/other modes will reject it. Changing/removing a binding requires do_hazardous_operations. Chose an opt-in solution to preserve reverse-compatibility to the existing API design without invasive changes, but also to enable certain operations, including but not limited to:
- Key generation/storage APIs that produce or persist “a 128-bit symmetric key” before a caller chooses CBC vs GCM.
- Generic test/framework code that only cares about key length/type and passes the key into some P: Algorithm.
- Low-level primitives like AES128Internal, where the key is for raw AES rather than specifically CBC/GCM.
- APIs that derive/import a key and then decide later which construction uses it.
- Key-management code that needs to move/store keys without depending on every cipher mode type.
With strict binding, all of those must know the final algorithm name up front (and would blow up the existing SymmetricCipherKey).
To avoid coupling core to the cipher crate's modes, ModeNames remains cipher-local and I moved the toy block cipher into the cipher tests rather than adding mode-specific metadata to ElectronicCodeBook.
Workspace tests pass except the existing local Ascon Wycheproof failure from the missing vector file.
Handled in commit 0de70ae
…for NIST SP 800-232
…c-rust into feature/simple-ciphers
…is is a large diff because adding that required updating the generic mode types/tests, but this prevents technical debt via maintenance for core
…lgorithm, but enabled reverse-compatible generic to not blow up the existing codebase + allow generate first, bind-later, generic use for raw AES, etc.
…zmat/aes_internal.rs, and hazmat/ecb.rs
- CcmEncryptor/CcmDecryptor override every one-shot (detached and inline, with and without AAD, with and without a caller's RNG) to call Ccm's own, since with the whole message in hand there is nothing to declare or hold back: no AAD buffer, and no DATA_LEN or AAD_LEN bound. Those two parameters now govern the streaming methods only, and decrypt_out_len is C less its tag as for any AEAD. The core-test-framework's fixed-length checks move accordingly: one-shots must round trip every length, a stream of the wrong length must be refused. - AEADCipherEncryptor gains do_encrypt_init_nonce and the encrypt_detached_nonce[_out] / encrypt_with_aad_nonce[_out] one-shots, for protocols that manage their own nonces, with the uniqueness obligation in the trait docs; implemented for GCM, Ascon-AEAD128 and CCM, and exercised by the framework against the rng forms under the same nonce. - CCM's 12-byte nonce floor moves from the adapters' shape check to the forms that draw a nonce, so the supplied-nonce forms and the decryptor take every A.1 length from 7 through 13 (802.11 CCMP's 13/8 covered in sp800_38c_tests). - CCM memory table re-measured with massif from one build. Assisted-by: Claude:claude-mythos-5-1 Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
…mplementing pair The lengths-declaring type is the one for SP 800-38C Sec 3's "packet environment, i.e., when all of the data is available in storage before CCM is applied", so it takes that name: CcmPacket in bouncycastle-cipher, AES_CCM_128_Packet and friends in bouncycastle-aes. The CcmEncryptor/CcmDecryptor pair, which implements the AEAD traits and now covers any length, a supplied or generated nonce and every A.1 nonce length through its one-shots, takes the plain AES_CCM_128 name, as AES_GCM_128 and the other modes' aliases name their trait implementors. No behaviour change. Assisted-by: Claude:claude-mythos-5-1 Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
…o do_{en,de}crypt_final_detached[_out]
The one-shots, their _len helpers and the rng and nonce forms already say "detached", and the tag is the only thing an AEAD can detach, so the four streaming finals follow. Mechanical rename across core, cipher, aes, ascon, the test framework, the CLI helpers and the mem benches; no behaviour change.
Assisted-by: Claude:claude-mythos-5-1
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
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 releasebranch, and adds the SHA-512/t work on top.
feature/simple-cipherswas built by branching fromrelease/0.1.3alphaand mergingfeature/stream-cipherthenfeature/symmetric-cipher; both merges fast-forwarded, so thecontent 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_bytesbecomeconst fn(3912434) — twokeywords, 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 everytFIPS 180-4 s. 5.3.6 defines a hash for (785dbef).Previously only
T = 224andT = 256had parameter impls, so nothing else could be named.sha512t_h0to100 <= t < 512and emitted a fixed three digits, since only 224 and 256 were reachable. Witharbitrary
treachable that would produce the"0256"spelling s. 5.3.6 explicitly forbids —and hence the wrong IV — for every
tbelow 100. The one- and two-digit branches are restoredand
check_tasserts the section's own rule: positive,< 512, not 384.tmust be a multiple of 8, becauseHashis byte-oriented anda 100-bit digest has no representation here. BC Java's
SHA512tDigestimposes the identicalrestriction, so the two libraries accept the same set of truncations.
ElectronicCodeBook::ENCRYPTION_APPROVEDgatestwo-key TDEA:
SHA512tParams::<T>::FIPS_APPROVEDis public andSHA512Internal::newasserts itin an inline
const, so an unapprovedtis a compile error at the call site andnew_allow_unapproved_t()is the deliberate way in. That also blocksDefault, which keeps anunapproved truncation out of generic code by accident.
ALG_NAME,OUTPUT_LENandMAX_SECURITY_STRENGTHare derived fromt, withconstassertions pinning them to the values 224 and 256 previously had by hand.
Verification
cargo test --workspace: 943 passed, 0 failed (was 923).cargo fmt --checkclean; no newclippy warnings. Both new commits build independently.
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 mutantson 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,majanddo_final_internal; no new missed mutants.Note for reviewers
Adding
constto a publiccorefunction is a forward compatibility commitment, andSHA512tParamsis currently its only caller. The alternative was a private copy of the roundingladder inside
sha2, free to drift from the real one — happy to switch if the API-surface cost isthe greater worry.
🤖 Generated with Claude Code