Skip to content

Refactor of HMACParams - #121

Closed
ounsworth wants to merge 233 commits into
bcgit:release/0.1.3alphafrom
ounsworth:docs/hash_clone
Closed

ounsworth wants to merge 233 commits into
bcgit:release/0.1.3alphafrom
ounsworth:docs/hash_clone

Conversation

@ounsworth

@ounsworth ounsworth commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Original issue:

Adds docs for cloning a hash function mid-stream so that you now have two hash objects that are identical up to this point, but you can feed different suffix input into before finalizing.

I put this next to the existing sections for [Suspendable], since it's closely-related functionality. (and re-arranged order to put these before Memory Usage and Security Consideration sections).

Extension

During development of that, I realized that the pub interface flip of HMAC and HKDF introduced in #122 meant that the way HMACParams was defined was now really clunky. A refactor of that lead to realizing that actually, SP 800-107r1 specifies the security strength of an HMAC higher than the collision strength of the underlying hash function, so I re-worked that as well.

dghgit and others added 25 commits September 6, 2026 12:02
…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 (bcgit#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
@ounsworth
ounsworth requested a review from dghgit September 8, 2026 16:58
dghgit and others added 5 commits September 28, 2026 14:48
…-- ecb.rs trailing whitespace (the rustfmt failure) and the batching sentence's lost object; lib.rs restores the required "Usage Examples" section name, moves the init-data and CFB/CFB8 paragraphs out from under "Notes on GCM Mode", repairs the AEAD and GCM opening sentences, says it is Gcm and not SP 800-38D that fixes the nonce at 12 bytes (Sec 5.2.1.1 only recommends 96 bits), fixes CBF8 and a leftover CCM comment fragment, drops the alias doctest's unused imports and wraps at 100 columns; and the last prose mentions of PaddedEncryptor/PaddedDecryptor take 013d806's new names

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ypt/encrypt_detached/decrypt/decrypt_detached become encrypt_out/encrypt_out_detached/decrypt_out/decrypt_out_detached, since each writes into a caller-supplied buffer and the bare names are the allocating Vec forms elsewhere (review comment on PR bcgit#133, ccm.rs:679); callers in aes, the CLI docs, the benches, mem_usage_benches and the CCM test suites follow, and the CcmEncryptor/CcmDecryptor trait methods are unchanged

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g example imports AES256Internal but named AES128Internal with a 32-byte key and used KeyMaterial256 without importing it, and cfb.rs's one-shot example used KeyMaterial128 while importing KeyMaterial

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… local Claude Code permission allowlist cannot ride along in a PR as one did on the GCM branch (removed in 3cee19b), and ignore custom_mutants_output/, the output directory .cargo/mutants.toml actually configures, which mutants.out*/ did not cover

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hubot
hubot deleted the branch bcgit:release/0.1.3alpha September 28, 2026 15:21
ounsworth and others added 16 commits September 28, 2026 15:26
…e tests crash with a stack overflow at a 5 mb payload.

Assisted-by: Claude Fable 5.1
…e tests crash with a stack overflow at a 5 mb payload.

Assisted-by: Claude Fable 5.1
…t/encrypt_rng and BlockCipherDecryptor::decrypt to encrypt_in_place/encrypt_in_place_rng/decrypt_in_place, so the in-place one-shots no longer share a name with SymmetricCipherEncryptor/Decryptor's allocating std encrypt/decrypt; mechanical, ahead of the stream traits extending the symmetric-cipher traits, where the same clash becomes an E0034 ambiguity; the pre-existing test_large_payload_symmetric_cipher stack overflow from baf16b9 is unchanged here

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CipherEncryptor/Decryptor now extend SymmetricCipherEncryptor/Decryptor<.., 0> instead of receiving them from blanket impls, so the constructors are the supertrait's and the in-place one-shots become encrypt_in_place/encrypt_in_place_rng/decrypt_in_place; a new core KeyStream trait is the raw keystream primitive (as ElectronicCodeBook is the raw permutation), with 🚨 Security 🚨 docs, and core::stream_cipher's StreamCipher<KS, Dir, R, ..> adapter supplies nonce generation, partial-block buffering in a Secret and the up-front exhaustion refusal for any keystream, with Encrypting/Decrypting moving to core and re-exported from modes; Ctr becomes a type alias over StreamCipher<CtrKeyStream, ..> (CtrKeyStream kept off the modes root), GCM wraps CtrKeyStream::start_at, CFB/CFB8 implement both traits over the new stream_update_out/stream_do_final helpers, and CCM's CTR half becomes a crate-private CcmKeyStream sharing apply_counter_blocks with CTR; TestFrameworkKeyStream is added and run against both keystreams; the CCM adapters take AAD_LEN and DATA_LEN, with FINAL_LEN checked at compile time to be DATA_LEN + TAG_LEN (it stays a parameter until generic_const_exprs) and both capped at CCM_MAX_BUFFER_LEN = 512 KiB, their finals share seal/open helpers that move only the key schedule, do_final_out writes straight into the caller's buffer, and the constructors are inline(always) in optimized builds, taking bench_ccm_mem_usage's streaming peaks from 134 152/150 280 B to 68 632/67 864 B; the large-payload streaming test runs at the cap on a 16 MiB thread instead of overflowing at 5 MiB; and Ccm gains new_with_lengths/do_update_aad, so the AAD can be supplied in pieces once its length is declared (SP 800-38C A.2.2), checked against Appendix C.3 and C.4 in several chunkings

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…coded, taking precedence over --aad as for aes*-gcm; a regular file is declared by its size to Ccm::new_with_lengths and streamed through do_update_aad in 1 KiB chunks rather than loaded, a pipe or device with no size to declare is read whole, and a file that grows or shrinks while being read is an error rather than a tag over the wrong length; tested end to end against SP 800-38C Appendix C.4's 65536-byte AAD

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…remotely -- the CCM streaming adapters hold the whole message on the stack, and at CCM_MAX_BUFFER_LEN the round trip needs more stack than a CI runner's test thread gives it; the compile-time cap stays pinned by the no_run and compile_fail doctests on CcmEncryptor, and test_large_payload_inherent keeps the 5 MiB payload through the non-buffering Ccm API; also drops the imports and the LARGE_FINAL_LEN constant only that test used, and the ccm.rs comment that cited it

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…remaining_blocks (65535 for q = 2, u64::MAX for q = 8), killing the five mutants the bouncycastle-modes run left in code added by bd6b8c1 -- the cap's doctests hold for any value, and the counter limit can never bind through the public API; re-checked with -F on ccm.rs:851/913/936, 14 mutants: 11 caught, 3 unviable. The full run (886 mutants, 33 min, --jobs 3) was 605 caught, 23 missed, 257 unviable, 1 timeout; of the other 18 missed, 13 are equivalent (the stream do_final's zero-length array, and the |/^ substitutions ccm.rs and ghash.rs already document), and 5 are in gcm.rs/ghash.rs code this work did not touch

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1f4a84e)

Replayed onto the HMAC/HKDF utility-crate split, which had restructured both
crate doc headers. Adapted while resolving the conflict:

  * link [`SHA256Internal`] / [`SHA512Internal`] and [`SHA3Internal`] rather
    than [`SHA2`] / [`SHA3`]; the latter two are not type names in either
    crate and would have been dead intra-doc links.
  * keep the "## HMAC" pointer that the split added to the sha3 header.
  * move sha2's "Memory Usage" and "Security Considerations" below
    "Suspending and resuming execution", so that both crates' doc headers
    present the same section order. sha3 was already in that order.

Assisted-by: Claude Code:claude-opus-5

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…aliases

The HMAC test suite exercised HMAC over SHA-2 and SHA-3, which forced
bouncycastle-hmac to carry bouncycastle-sha2, -sha3, -rng and -hex as
dev-dependencies purely in order to test itself. That was a deliberate cycle
through dev-dependencies, flagged with a todo, left over from the
utility-crate split.

Split the suite by the hash it exercises and move each half to the crate that
publishes the aliases under test:

  * crypto/sha2/tests/hmac_tests.rs -- HMAC-SHA224/256/384/512: the full
    RFC 4231 suite, suspend/resume, keygen, and weak-RNG rejection.
  * crypto/sha3/tests/hmac_tests.rs -- HMAC-SHA3-224/256/384/512: the
    143-byte long-key regression (SHA3-224 has the largest block at 144
    bytes), truncated-tag rejection, suspend/resume, and keygen.

No test bodies or expected values were changed; the suite was partitioned,
not rewritten. bouncycastle-hmac's [dev-dependencies] are gone entirely and
with them the dependency cycle. The hex dev-dependency follows the tests to
bouncycastle-sha2, which did not previously have one.

This leaves bouncycastle-hmac with no tests of its own. That is inherent to
it being a utility crate rather than a gap in coverage: HMAC<H> cannot be
instantiated without a hash, and this crate must not depend on the hash
crates. Every behaviour it had is still exercised, from the two suites above.

On rebasing onto feature/simple-ciphers, its HMAC-SHA512/224 and -SHA512/256 tests
follow SHA-2 here, and its HMAC-SM3 tests move to a new crypto/sm3/tests/hmac_tests.rs.

Assisted-by: Claude Code:claude-opus-5

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ating it

Assisted-by: Claude Code:claude-opus-5
@dghgit
dghgit changed the base branch from release/0.1.3alpha to feature/simple-ciphers September 29, 2026 07:41
@ounsworth ounsworth added this to the 0.1.3alpha milestone Oct 7, 2026
@ounsworth
ounsworth changed the base branch from feature/simple-ciphers to release/0.1.3alpha October 24, 2026 13:26
@ounsworth ounsworth mentioned this pull request Oct 24, 2026
@ounsworth

Copy link
Copy Markdown
Contributor Author

Closing as this has been replaced by #176

@ounsworth ounsworth closed this Oct 24, 2026
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