Repository navigation
Conversation
mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default; the valgrind/ms_print shell one-liners here weren't fenced, so `cargo test --workspace` failed to compile them as Rust (CI on PR #139). Fenced as ```text like the other bench binaries, per CLAUDE.md's note on this exact pitfall. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
We had agreed not to add more ciphers until we get through reviewing and locking in the overall architecture via the AES work. Closing this PR for now. |
|
Why on earth would anybody want 3DES? NIST deprecated 3DES for new use via SP 800-131A Rev. 2 (disallowed after December 31, 2023) and withdrew SP 800-67 Rev. 2 effective January 1, 2024. |
|
Fair question, even though, as much as I hate to say it, I could point you at people still using DES, some of who should definitely know better... We still support it in BC-FIPS as well, as far as I can tell it's mainly used for dealing with legacy encrypted data these days. In this case this one is simply here as it's on the OpenSSL provider list, so it's one of the algorithms they'll want to invoke (at least for a while). |
d1994a9 to
d7e98cf
Compare
Implements the KeyWrapper / KeyUnwrapper traits from the previous commit for the two 128-bit-block key-wrap algorithms of NIST SP 800-38F, KW (Sec 6.2; RFC 3394) and KWP (Sec 6.3; RFC 5649), generically over any ElectronicCodeBook permutation, and publishes the AES instantiations. modes: - kw.rs: Kw<P, KEK_LEN>. The wrapping function W and its inverse (Algorithms 1 and 2) run in place on the caller's buffer, visiting the registers in the order the spec's shift register would present them, which RFC 3394 Sec 2.2.1 gives as the equivalent index-based form; the one block of scratch is a Secret. KW-AE/AD are Algorithms 3 and 4. - kwp.rs: Kwp<P, KEK_LEN>. KWP-AE/AD are Algorithms 5 and 6, including the single-block path for plaintexts of at most one semiblock. The ICV, length-field and padding checks of Algorithm 6 steps 4, 7 and 8 are all made, combined bitwise, and reported as the one DecryptionFailed. - The fixed-length API rejects invalid (KEY_LEN, CT_LEN) pairs at compile time via const assertions on the Table 1 limits; the run-time API returns InvalidInputLength outside them. Output buffers are zeroized on every failure. TKW (TDEA) is not included: it needs a 64-bit-block engine (PR bcgit#139). aes: - kw.rs: AES_KW_128/192/256 and AES_KWP_128/192/256 aliases, with the RFC 3394 Sec 4.1 and RFC 5649 7-octet vectors as doctests. No OIDs yet, in line with the other AES mode aliases. tests: - modes/tests/kw_tests.rs: all six RFC 3394 Sec 4 vectors and both RFC 5649 Sec 6 examples via TestFrameworkKeyWrap::test_kat, the shared framework for every AES key length at 26 payload lengths (every padding length of the single-block case included), and pins for the properties that distinguish KW, KWP and a raw block encryption. - modes/tests/acvp_kw_tests.rs: the NIST ACVP-AES-KW and ACVP-AES-KWP sets from bc-test-data, 1800 cases each, through both APIs, including the 171 + 178 forged ciphertexts that must be rejected. - aes/tests/kw_alias_tests.rs: the aliases name the right permutation and pass the framework. benches: KW/KWP wrap and unwrap of a 256-bit key, and KWP over 4 KiB. cli: aes{128,192,256}-kw and -kwp with wrap|unwrap, reading all of stdin (key wrap is one-shot), sharing key loading with the other AES commands; cli/tests/aes_kw_cli_tests.rs replays the RFC vectors in both directions and pins the length rules, the fail-closed unwrap and the help text. Assisted-by: Claude:claude-fable-5-1
mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default; the valgrind/ms_print shell one-liners here weren't fenced, so `cargo test --workspace` failed to compile them as Rust (CI on PR #139). Fenced as ```text like the other bench binaries, per CLAUDE.md's note on this exact pitfall. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…```text Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…```text Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default. Fenced as ```text like the other bench binaries. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements the KeyWrapper / KeyUnwrapper traits from the previous commit for the two 128-bit-block key-wrap algorithms of NIST SP 800-38F, KW (Sec 6.2; RFC 3394) and KWP (Sec 6.3; RFC 5649), generically over any ElectronicCodeBook permutation, and publishes the AES instantiations. modes: - kw.rs: Kw<P, KEK_LEN>. The wrapping function W and its inverse (Algorithms 1 and 2) run in place on the caller's buffer, visiting the registers in the order the spec's shift register would present them, which RFC 3394 Sec 2.2.1 gives as the equivalent index-based form; the one block of scratch is a Secret. KW-AE/AD are Algorithms 3 and 4. - kwp.rs: Kwp<P, KEK_LEN>. KWP-AE/AD are Algorithms 5 and 6, including the single-block path for plaintexts of at most one semiblock. The ICV, length-field and padding checks of Algorithm 6 steps 4, 7 and 8 are all made, combined bitwise, and reported as the one DecryptionFailed. - The fixed-length API rejects invalid (KEY_LEN, CT_LEN) pairs at compile time via const assertions on the Table 1 limits; the run-time API returns InvalidInputLength outside them. Output buffers are zeroized on every failure. TKW (TDEA) is not included: it needs a 64-bit-block engine (PR bcgit#139). aes: - kw.rs: AES_KW_128/192/256 and AES_KWP_128/192/256 aliases, with the RFC 3394 Sec 4.1 and RFC 5649 7-octet vectors as doctests. No OIDs yet, in line with the other AES mode aliases. tests: - modes/tests/kw_tests.rs: all six RFC 3394 Sec 4 vectors and both RFC 5649 Sec 6 examples via TestFrameworkKeyWrap::test_kat, the shared framework for every AES key length at 26 payload lengths (every padding length of the single-block case included), and pins for the properties that distinguish KW, KWP and a raw block encryption. - modes/tests/acvp_kw_tests.rs: the NIST ACVP-AES-KW and ACVP-AES-KWP sets from bc-test-data, 1800 cases each, through both APIs, including the 171 + 178 forged ciphertexts that must be rejected. - aes/tests/kw_alias_tests.rs: the aliases name the right permutation and pass the framework. benches: KW/KWP wrap and unwrap of a 256-bit key, and KWP over 4 KiB. cli: aes{128,192,256}-kw and -kwp with wrap|unwrap, reading all of stdin (key wrap is one-shot), sharing key loading with the other AES commands; cli/tests/aes_kw_cli_tests.rs replays the RFC vectors in both directions and pins the length rules, the fail-closed unwrap and the help text. Assisted-by: Claude:claude-fable-5-1
|
The review round of PR #140 applied here, in
|
|
Roy's two follow-ups from PR #140 applied here too, head
|
3c170c9 to
81cef74
Compare
mem_usage_benches/src/lib.rs makes every bench source a module of a lib target, so its //! header is rustdoc'd and an indented block compiles as a Rust doctest by default; the valgrind/ms_print shell one-liners here weren't fenced, so `cargo test --workspace` failed to compile them as Rust (CI on PR #139). Fenced as ```text like the other bench binaries, per CLAUDE.md's note on this exact pitfall. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@dghgit Fourth-round review of |
…ncryptor_nonce and the _nonce one-shots on SymmetricEncryptionKey and BlockEncryptionKey, encrypt_init_nonce on the modes, CLI --iv through them with --nonce as its alias) into feature/tdes: conflicts in cli/src/cbc_cmd.rs and ecb_cmd.rs (the three-argument start_encryptor with this branch's BLOCK_LEN const generics); TDESKey gains create_encryptor_nonce forwarding to the modes, tdes_cbc_cmd and tdes_ecb_cmd take the new start_encryptor, the eight TDES --iv / --iv-file args take the --nonce / --nonce-file aliases, and ChaCha20Key's create_encryptor_nonce goes through this branch's stream_cipher_over
…t split into MACGenerator / MACVerifier with the tag length in the type, the direction markers in bouncycastle_core::direction) into feature/tdes: conflict in cipher/src/lib.rs, resolved by taking core's markers through cipher's re-export, keeping a crate-local sealed trait for KeyStreamPolicy, and carrying this branch's Direction::IS_ENCRYPTING (the DECRYPT_ONLY check in StreamCipher) onto core's Direction
PR #139 round four, TDES4-001: the generic BlockCipherKey over TDES2Internal could generate two-key TDEA CMAC tags, which SP 800-131A Rev 2 Table 9 lists as "Disallowed" (verification "Legacy use"), because CMAC carried no DECRYPT_ONLY check. With the MAC worker split from PR #194 the generator and the verifier have separate constructors, so MacMode::generate_init for CMAC gets the same inline const assert the encrypting cipher constructors and the key wrappers carry, and verify_init does not: a two-key key under CMAC verifies and cannot produce a tag, and three-key is unchanged. Assisted-by: Claude:claude-mythos-5-1
PR #139 round four: the doc says a MAC generator is among the constructors the gate covers, a verifier is not, and cites SP 800-131A Rev 2 Table 9 beside Tables 1 and 6. Assisted-by: Claude:claude-mythos-5-1
…mode PR #139 round four. The two-key section says two-key TDEA verifies CMAC tags and cannot generate them, that TDESKey carries no MAC role, and that CMAC over either bundle is reached through bouncycastle_cipher::BlockCipherKey; the policy paragraph keeps three-key CMAC generation on the same terms as three-key encryption and wrapping (Table 9: "Deprecated through 2023, Disallowed after 2023"). TDES2Internal gains a compile_fail doctest for create_generator on BlockCipherKey<TDES2Internal, CMAC<8>>, and cmac_generic_key_tests checks all four NIST CMAC-TDES samples (csrc.nist.gov TDES_CMAC.pdf): three-key generated and verified, two-key verified streamed and one-shot with a changed tag refused. The data-limit section gives the figure per mode as the blocks TDEA processes: 8 MiB for ECB, CBC, CFB64 and CTR, 1 MiB for CFB8, 699,048 bytes of TKW plaintext. Assisted-by: Claude:claude-mythos-5-1
…a and SM4 key-wrap aliases gain Dir PR #139 round four nits. Assisted-by: Claude:claude-mythos-5-1
|
Round four is in at 9249570, after the branch took release/0.1.4alpha 6144224 (the PR #194 MAC rework squash) in 7cb618d. TDES4-001 is fixed on the generating constructor: the MAC split gives |
|
@dghgit With #194's split constructors, the gate is on Two other additional items (neither merge-blocking, but worth mentioning):
|
…e too Review round five of PR #139 (issue 1): the const assertion on BlockPermutation::DECRYPT_ONLY sat in the modes' encrypt_init bodies alone, and the encrypt_init_nonce twins that the fix/supplied-nonce merge brought in had none, so a two-key key's create_encryptor_nonce, encrypt_nonce, encrypt_nonce_out and encrypt_inplace_nonce compiled and encrypted under CBC (both paddings), CFB, CFB8 and padded ECB; CTR was already refused through the stream cipher's keystream policy. The five bodies now carry the same assertion, so every encrypting constructor over the two-key engine is a compile error, pinned by a compile_fail doctest on create_encryptor_nonce beside the create_encryptor one; the three-key twins build as before. Assisted-by: Claude:claude-mythos-5-1
Review round five of PR #139 (issue 2): cmac_generic_key_tests.rs, which 0bf4d98's message cites, was never added to the tree, so nothing exercised CMAC over a 64-bit block (the cipher crate's suite runs a 16-byte toy). It is committed now: NIST's TDES_CMAC samples, the three-key bundle generating and verifying and the two-key one verifying and refusing a changed tag. cmac_bc-test-data.rs runs the ACVP sets already in bc-test-data: CMAC-TDES.4014545 (three-key: 72 gen and 180 ver, truncated tags compared on their prefix, full tags through verify) and CMAC-TDES.4014546 (two-key: the 60 full-tag ver cases through verify; the 120 truncated-tag cases are counted and skipped, since two-key TDEA cannot compute a tag here). Assisted-by: Claude:claude-mythos-5-1
Review round five of PR #139 (issue 3): CMAC_ALG_NAME was empty on both engines, under a comment saying no MAC mode is offered, while the crate documents CMAC through the generic BlockCipherKey, and ModeNames reserves the empty name for a mode the permutation does not implement. The names are TDES_CMAC and TDES2_CMAC, as TKW's are, with alg_name_tests checking both directions of the former and the verifying direction of the latter. Assisted-by: Claude:claude-mythos-5-1
|
Thanks, Francis. Round five is on the branch at 3c7fa45, three commits over 9249570: 3c8c1f1 (cipher, tdes) puts the |
…ment and ECC CDH with the X9.63 and concatenation KDFs; PR #180 squash: BLAKE2b-512, BLAKE2s-256 and BLAKE3; the post-#193 cleanups) into feature/tdes Two keep-both conflicts: the mem_usage_benches manifest (the tdes stack bench beside the BLAKE2 and BLAKE3 ones) and the umbrella crate's re-exports (tdes beside x963kdf). One adaptation: the CLI's main thread stack goes from 8 MiB to 32 MiB. In a debug build the startup frame for the full subcommand set already sat close to 8 MiB on the release, and the eleven tdes-* / tdes2-* commands take it past, so the debug binary aborted on every invocation, --help included (the release-profile binary was unaffected). That bound is the release's to set once; it is raised here so the branch's shell suites run. The tdes, cipher, factory and cli suites, the tdes, blake, eccdh, concatkdf, x963kdf and hkdf CLI shell suites and the all-targets build pass.
…generator per curve in bouncycastle-ec's hazmat, which the ecdsa and eccdh keygens and derive_pks now wrap) into feature/tdes
…trait, fix/eccdh-generic, fix/ecdsa-generic and fix/kdf-description-followups squashes: curve markers and the Curve trait in bouncycastle-ec, ECC CDH and ECDSA written once over Curve and the hash, KdfParams over an HSM-readable KdfDescription, and the KDF description's follow-ups) into feature/tdes No conflicts and no adaptation: the five squashes touch ec, ecdsa, eccdh and the KDF crates, which the TDES crate, its CLI commands and its stack harness do not reach; cli/src/main.rs and src/lib.rs auto-merged on neighbouring lines. Assisted-by: Claude:claude-mythos-5-1
|
@dghgit Sixth-round review of
I also suggest the following edits to the description:
|
|
Thanks for the sixth round. No code change this time; the description is updated on all four points:
Assisted-by: Claude:claude-mythos-5-1 |
officialfrancismendoza
left a comment
There was a problem hiding this comment.
@dghgit looks good. LGTM!
…ant-time table-free Triple DES (TDEA) engine per SP 800-67r2 on the block cipher key objects: TDES3Key<M> (three-key) and the decryption-only two-key TDES2Key<M> over the engines TDESInternal and TDES2Internal, with the modes, TKW and CMAC over them. The S-box layer is a compile-time-derived ANF Boolean circuit with no lookup tables and no secret-indexed memory access; the round function's assembly on Cortex-M0 and RISC-V 32I holds no multiply (spread goes through ct::value_barrier off x86-64 and aarch64, as AES's round-key widening does), and dev_scripts/check_aes_ct_asm.sh and the aes-ct-asm CI job include it with a plain-shift spread as the positive control. TDESKey<P, M> mirrors AESKey: the key holds the mode's state, its workers borrow it, and which role pair a key carries is decided by its mode (CBC<Blocks>, CBC<PKCS7>, ECB<..>, CFB, CFB8, CTR<6, 8>, TKW), with the aliases TDES_CBC<'k, Dir, Pad>, TDES_CFB, TDES_CFB8, TDES_CTR, TDES_ECB, TDES_TKW<'k, Dir> and the TDES2_* decryptors and unwrapper naming what the keys hand out; keys and engines implement PartialEq and Eq in constant time over the expanded schedule. TDESInternal::new rejects non-distinct component keys and exactly the 64 weak, semi-weak and possibly-weak DES keys of SP 800-67r2 Sec 3.3.2 by a structural check as KeyMaterialError::WeakKey, and TDES2Internal::new rejects Key1 = Key2 and either component being weak. Two-key TDEA is decryption-only at compile time: TDES2Internal sets BlockPermutation::DECRYPT_ONLY (this library's policy for two-key alone; three-key encryption stays for the OpenSSL-provider interop, said so in the crate's Security Considerations), and every route that would make an encrypting worker over a decrypt-only permutation asserts it in an inline const (the modes' encrypt_init and encrypt_init_nonce, StreamCipher::from_keystream, crate-private through the sealed KeyStreamPolicy, Tkw's wrapping constructor and CMAC::generate_init), so a two-key key's create_encryptor, create_encryptor_nonce, create_wrapper and create_generator do not compile while its decryptors, unwrapper and verifier work, each route pinned by a compile_fail doctest on TDES2Internal; the two-key docs say what the gate does not stop, CFB and CTR decryption being the forward cipher. TKW (SP 800-38F Sec 7) is cipher::modes::TKW / Tkw<'k, P, Dir>, Kw at half the width (4-byte semiblocks, 32-bit counter, ICV3) generic over any 64-bit block permutation, TDES3Key<TKW> wrapping and unwrapping through the KeyWrappingKey and KeyUnwrappingKey roles and TDES2Key<TKW> unwrapping only; alongside it Kw and Kwp carry their direction in the type (Kw<'k, P, Dir>), so the AES_KW_*, AES_KWP_*, ARIA_KW_*, ARIA_KWP_*, Camellia_KW_*, SM4_KW and SM4_KWP aliases gain the Dir parameter, an API change to those crates listed in the 0.1.4 release notes. CMAC over TDEA (SP 800-38B Sec 5.3) is reached through the generic BlockCipherKey<TDESInternal, CMAC<8>> (TDESKey carries no MAC role), the two-key form verifying only (SP 800-131A Rev 2 Table 9), named TDES_CMAC and TDES2_CMAC. The data limit of SP 800-67r2 Sec 3.4 (2^20 blocks per key bundle) is stated per mode in the crate docs as the blocks TDEA processes (8 MiB for ECB, CBC, CFB64 and CTR, 1 MiB for CFB8, 699,048 bytes for TKW); the library cannot count across the uses of a key, so the limit is the key manager's, and the tdes-* CLI encrypt commands refuse input past it in one invocation through an InputLimit on the shared block- and stream-mode loops. The CLI gains tdes-* and tdes2-* subcommands for every mode and tdes-tkw and tdes2-tkw through the key objects with the aes*-* contract (tdes-cbc and tdes2-cbc are CBC<PKCS7> at any length, --iv/--iv-file with --nonce as its alias, -d/--direction everywhere, the tdes2-* commands accepting only decrypt), its block-mode plumbing taking the block length as a const parameter so the shared loops serve 64-bit blocks, and its main-thread stack raised from 8 MiB to 32 MiB because the debug binary overflowed the 8 MiB on --help once this branch's subcommands joined the release's. Outside the new crate: core gains BlockPermutation::DECRYPT_ONLY, Direction::IS_ENCRYPTING, KeyMaterialError::WeakKey and the DECRYPT_ONLY contract naming MAC generators; cipher the const asserts at every encrypting construction, StreamCipher::from_keystream crate-private behind the sealed KeyStreamPolicy, the TKW mode and Kw / Kwp with Dir in the type; aes, aria, camellia and sm4 their key-wrap aliases taking Dir; cli the generic block length, DecryptOnlyAction, InputLimit and aes_kw_cmd::run_with; dev_scripts the TDES round function in the constant-time assembly check; mem_usage_benches the TDES stack harness; and the 0.1.4 release notes carry all of it. Tests: the NIST CAVP T{E,C}{CB,BC,FB64,FB8}MMT{2,3} vectors at both key sizes in both directions (CBC and CFB through the keys, ECB through the engines), the CAVP TKW_AE / TKW_AD sets (five plaintext lengths, 100 trials each, 20 forgeries per section) through TDES3Key<TKW> and TDES2Key<TKW> plus RFC 3394-style reference tests over the raw permutation, NIST's TDES_CMAC samples (three-key generate and verify, two-key verify and refuse) and the ACVP CMAC-TDES.4014545 / .4014546 sets (three-key 72 gen and 180 ver, two-key the 60 full-tag ver cases, the 120 truncated-tag ones counted and skipped), all through the test framework's bc_test_data loader, which skips only when the bc-test-data directory is absent and panics on a missing file (the TKW files are a 2026-11-06 addition to bc-test-data, the CMAC sets there since August); the weak-key predicate asked about all 256 rotation-invariant (C0, D0) pairs and refusing exactly the spec's 64; the shared frameworks over every mode key (TestFrameworkFromKeyMaterial, TestFrameworkBlockCipher, TestFrameworkSymmetricCipher, TestFrameworkStreamCipher, TestFrameworkElectronicCodeBook, the key-wrap and MAC-key suites); cli/tests/tdes_cli_tests.rs and tdes_tkw_cli_tests.rs pinning COUNT = 0 of the MMT3 files and TKW through the binary in both directions and the two-key [DECRYPT] vectors through tdes2-*; and cli/tests/test_tdes.sh checking the per-mode data limit end to end. Reviewed by Francis in six rounds (2026-11-04 to 2026-11-13: is_weak matching 256 classes rather than 64, the gate covering create_encryptor only with the keystream and resume routes open, spread folding to a multiply on Cortex-M0, the CAVP suites passing vacuously on a missing file, the SuspendableKeyed resume route removed with #173, TKW and the key-wrap modes taking their direction in the type so a two-key unwrapper cannot wrap, the limits counting the blocks TDEA processes, CMAC's generating constructor refusing a decrypt-only permutation after #194, the five encrypt_init_nonce bodies from the supplied-nonce merge carrying no DECRYPT_ONLY assert so eight supplied-IV routes encrypted under two-key TDEA, the CMAC test that was never added, and the empty CMAC name; all fixed, approved 2026-11-13). Mutants on apollo: bouncycastle-tdes 500 (422 caught, 41 unviable, 33 missed, 31 of them bit-permutation XOR/OR equivalences in spread, permute_p, lookup, rotl28, pc1 and expand and two loop-bound mutants in the compile-time anf_constants, 4 timeouts in the compile-time ANF derivation), a scoped re-run over schedule.rs and des.rs after the round-one fixes 333 (303 caught, 30 missed, every miss a |-to-^ on disjoint bits), and the key-wrap modes kw/kwp/tkw with the cipher, aes and tdes tests 217 (159 caught, 58 unviable, 0 missed).
Assisted-by: Claude:claude-mythos-5-1
Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5-5
Summary
Adds
bouncycastle-tdes: a constant-time, table-free Triple DES (TDEA) engine per SP 800-67r2, on the block cipher key objects:TDES3Key<M>(three-key) and the decryption-only two-keyTDES2Key<M>, over the enginesTDESInternalandTDES2Internal, with the modes, TKW and CMAC over them. Base:release/0.1.4alpha, currently merged up to f213c9e (the ECCurvetrait, generic ECDSA and ECC CDH, and the KDF description).Description
spreadgoes throughct::value_barrieroff x86-64 and aarch64, as AES's round-key widening does);dev_scripts/check_aes_ct_asm.shand theaes-ct-asmCI job include it, with a plain-shiftspreadas the positive control.TDESKey<P, M>mirrorsAESKey: the key holds the mode's state, its workers borrow it, and which role pair a key carries is decided by its mode (CBC<Blocks>,CBC<PKCS7>,ECB<..>,CFB,CFB8,CTR<6, 8>,TKW). The aliasesTDES_CBC<'k, Dir, Pad>,TDES_CFB,TDES_CFB8,TDES_CTR,TDES_ECB,TDES_TKW<'k, Dir>and theTDES2_*decryptors and unwrapper name what the keys hand out. Keys and engines implementPartialEq/Eqin constant time over the expanded schedule.TDESInternal::newrejects non-distinct component keys and exactly the 64 weak, semi-weak and possibly-weak DES keys of SP 800-67r2 Sec 3.3.2 (a structural check, not a table) asKeyMaterialError::WeakKey;TDES2Internal::newrejectsKey1 = Key2and either component being weak.TDES2InternalsetsBlockPermutation::DECRYPT_ONLY(this library's policy for two-key alone; three-key encryption stays for the OpenSSL-provider interop, said so in the crate's Security Considerations), and every route that would make an encrypting worker over a decrypt-only permutation asserts it in an inlineconst: the modes'encrypt_initandencrypt_init_nonce,StreamCipher::from_keystream(crate-private, through the sealedKeyStreamPolicy),Tkw's wrapping constructor andCMAC::generate_init. A two-key key'screate_encryptor,create_encryptor_nonce,create_wrapperandcreate_generatordo not compile; its decryptors, unwrapper and verifier work.compile_faildoctests onTDES2Internalpin each route. The two-key docs say what the gate does not stop: CFB and CTR decryption are the forward cipher, so a deployment that must rule two-key encryption out altogether exposes only the CBC and ECB decryptors.cipher::modes::TKW/Tkw<'k, P, Dir>isKwat half the width (4-byte semiblocks, 32-bit counter, ICV3), generic over any 64-bit block permutation;TDES3Key<TKW>wraps and unwraps through theKeyWrappingKey/KeyUnwrappingKeyroles,TDES2Key<TKW>unwraps only. Alongside,KwandKwpcarry their direction in the type (Kw<'k, P, Dir>), so theAES_KW_*,AES_KWP_*,ARIA_KW_*,ARIA_KWP_*,Camellia_KW_*,SM4_KWandSM4_KWPaliases gain theDirparameter: an API change to those crates, not an addition, listed in the 0.1.4 release notes.BlockCipherKey<TDESInternal, CMAC<8>>(TDESKeycarries no MAC role); the two-key form verifies only (SP 800-131A Rev 2 Table 9). The engines name itTDES_CMAC/TDES2_CMAC.tdes-*CLIencryptcommands refuse input past it in one invocation, through anInputLimiton the shared block- and stream-mode loops.tdes-*/tdes2-*for every mode andtdes-tkw/tdes2-tkw, through the key objects, with theaes*-*contract:tdes-cbcandtdes2-cbcareCBC<PKCS7>at any length, every command with an IV or nonce takes--iv/--iv-file(--nonceas its alias), every command takes-d/--direction, and thetdes2-*commands accept onlydecryptthere. The CLI's block-mode plumbing takes the block length as a const parameter so the shared loops serve 64-bit blocks.Changes outside the new crate
core:BlockPermutation::DECRYPT_ONLY,Direction::IS_ENCRYPTING,KeyMaterialError::WeakKey, and theDECRYPT_ONLYcontract naming MAC generators.cipher: theconstasserts at every encrypting construction (modes, keystream wrapper,Tkw,CMAC),StreamCipher::from_keystreamcrate-private behind the sealedKeyStreamPolicy, theTKWmode, andKw/KwpwithDirin the type (the alias change above).aes,aria,camellia,sm4: their key-wrap aliases takeDir.cli: generic block length,DecryptOnlyAction,InputLimit,aes_kw_cmd::run_with.dev_scripts: the TDES round function in the constant-time assembly check.clialso raises the main-thread stack from 8 MiB to 32 MiB (67ae72d), because the debug binary overflowed the 8 MiB on--helponce this branch's subcommands joined the release's. The 0.1.4 release notes carry all of it.Validation
T{E,C}{CB,BC,FB64,FB8}MMT{2,3}vectors, both key sizes, both directions: CBC and CFB through the keys, ECB through the engines. NIST CAVPTKW_AE/TKW_AD(five plaintext lengths, 100 trials each, 20 forgeries per section) throughTDES3Key<TKW>andTDES2Key<TKW>, plus RFC 3394-style reference tests over the raw permutation. NIST'sTDES_CMACsamples (three-key generate and verify, two-key verify and refuse) and the ACVPCMAC-TDES.4014545/.4014546sets (three-key: 72 gen and 180 ver, truncated tags on their prefix; two-key: the 60 full-tag ver cases, the 120 truncated-tag ones counted and skipped since two-key cannot compute a tag here). All read through the test framework'sbc_test_dataloader. The TKW files are a recent addition tobc-test-data(crypto/aes_tdes_vectors/TDES/TKW_A{E,D}.txt, 2026-11-06; thecrypto/aes_tdes_vectors/CMAC/CMAC-TDES.*.jsonsets have been there since August). The loader skips only when thebc-test-datadirectory is absent; when it is present but a file is missing it panics, so a checkout older than the TKW files failstkw_ae,tkw_adand the TKW CLI tests rather than passing vacuously. Update it alongside this branch.(C0, D0)pair, all 256, and must refuse exactly the 64 the spec lists; random keys withC0 = D0and no period are accepted, which an OR-to-XOR mutant in the predicate would not do.TestFrameworkFromKeyMaterial,TestFrameworkBlockCipher,TestFrameworkSymmetricCipher,TestFrameworkStreamCipher,TestFrameworkElectronicCodeBook, and the key-wrap and MAC-key suites.cli/tests/tdes_cli_tests.rsandtdes_tkw_cli_tests.rspinCOUNT = 0of the MMT3 files and TKW through the binary in both directions, and the two-key[DECRYPT]vectors throughtdes2-*;cli/tests/test_tdes.shchecks the per-mode data limit end to end.cargo test --workspace,cargo test --workspace --all-targets --no-run, rustdoc,cargo +nightly fmt --check, clippy adds nothing in the new code.cargo mutants -p bouncycastle-tdesafter the review fixes: 500 mutants, 422 caught, 41 unviable, 33 missed (31 bit-permutation XOR/OR equivalences inspread,permute_p,lookup,rotl28,pc1andexpand; a scoped re-run overschedule.rsanddes.rsat fd55fc5 is 333 mutants, 303 caught, 30 missed, every miss a|-to-^on disjoint bits, and two loop-bound mutants in the compile-timeanf_constants), 4 timeouts (the compile-time ANF constant derivation).Review rounds
is_weakmatched 256 key classes, not the spec's 64; the gate coveredcreate_encryptoronly, and a two-key decryptor's keystream could be wrapped as an encryptor;spreadfolded back into amulson Cortex-M0; the CAVP suites passed vacuously when a file was missing; the NIST withdrawal notice quoted verbatim; doc and help nits. All fixed.SuspendableKeyedremoved from every cipher worker (Security: Prevent keystream reuse when cloning or resuming stateful cipher workers #173, by merge of release 1bee29d): the resume route and its doctest go with it.tdes-tkwandtdes-cfb8limits count the blocks TDEA processes; the wrap docs cite Table 6; nits across cipher, core, cli and dev_scripts.generate_initalone, the verifier works); theDECRYPT_ONLYcontract names MAC generators and Table 9; the data limit stated per mode; release-notes nits.encrypt_init_noncebodies from the fix/supplied-nonce merge carried noDECRYPT_ONLYassert, so eight supplied-IV routes encrypted under two-key TDEA (confirmed against OpenSSL): the five bodies now carry it and a doctest pinscreate_encryptor_nonce. (2) The CMAC test round 4 cited was never added to the tree: committed, with the ACVP sets beside it. (3) CMAC's empty name:TDES_CMAC/TDES2_CMAC. Plus this description rewritten and thebc-test-datanote above. The round-5 changes areconstasserts, tests and name constants; mutants were not re-run for them.AI Usage Statement
Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-mythos-5-1
🤖 Generated with Claude Code