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
…on says to avoid -- a weak or semi-weak key, or a bundle whose component keys are not distinct
…and CHUNK_LEN is a fixed 1 KiB, so the shared encrypt/decrypt loops serve block ciphers other than AES
…permutation kept for decrypting legacy data only, so a mode can refuse to encrypt with it at compile time
…s P::ENCRYPTION_APPROVED in an inline const, so building an encryptor over a decryption-only permutation is a compile error
…SP 800-67r2) whose S-box layer is an ANF circuit derived from the spec tables at compile time -- three-key TDES with TDES_* mode aliases, decryption-only two-key TDES2Key with TDES2_* decryptors, tdes-*/tdes2-* CLI subcommands, CAVP MMT vectors from bc-test-data
summary.md duplicated the crate docs' content and added a "Where the plan changed" section citing decisions from an internal design session that isn't part of this repo, plus several bc-java class-name references not needed once the doc itself is gone. The crate docs in src/lib.rs already cover the design, verification and the decisions a reviewer would want to revisit. No behavioural change; cargo fmt --check is clean. Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
…ptor::encrypt/encrypt_rng now returning (usize, [u8; INIT_DATA_LEN]) instead of just [u8; INIT_DATA_LEN] -- TDES_CFB, TDES_CFB8 (twice) and TDES_CTR each destructure the tuple instead of binding the old array-only return; no other TDES code is affected since these files are only type aliases over the generic Cfb/Cfb8/Ctr in crypto/modes, not their own trait impls Assisted-by: Claude:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…absorbed through its merges (SymmetricCipher/BlockCipherPadding renames, the ElectronicCodeBook batch methods, AES*Internal, core::security_strength, PaddedBlockCipher* adapters and the rest), restored in one commit after the linear rebase dropped those merges; the tree is identical to merging d1994a9 with 2d4d038 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R and TDES2_* examples take the renamed in-place one-shots (encrypt_in_place/decrypt_in_place) and import the SymmetricCipher* supertraits their do_*_init calls now come from; and a compile_fail example pins that Ctr<TDES2Key, Encrypting, ..> is still refused, now through KeyStream::ENCRYPTION_APPROVED (which CtrKeyStream takes from its permutation, carried in the merge) since CTR's encrypting constructor moved into core's StreamCipher adapter Assisted-by: Claude:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TDES, the decryption-only TDES2Key and the TDES_ECB / TDES2_ECB aliases move to bouncycastle_tdes::hazmat, with BLOCK_LEN, KEY_LEN and KEY_LEN_2KEY defined at the crate root; the merge ahead of this ported ENCRYPTION_APPROVED onto the moved trait and CtrKeyStream files. No logic change, no mutation run owed. Assisted-by: Claude Code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Select Follows aes 160ac18; the crate's own PaddedMode copy goes with it. Type aliases only, no behaviour change, no mutation run owed. Assisted-by: Claude Code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…er paths for the modes and padding, core::hazmat::ElectronicCodeBook The linear replay of this branch onto feature/key-rework-memory drops its merges from feature/simple-ciphers, and the move of bouncycastle-modes and bouncycastle-padding into bouncycastle-cipher, with Direction and ElectronicCodeBook's new homes, was resolved only in those merges. This is the tdes crate, its CLI commands and its memory bench exactly as the branch tip 5197229 had them. Assisted-by: Claude:claude-mythos-5-1
…s, the engines become TDESInternal / TDES2Internal The engines are renamed for what they are under the key objects of feature/key-rework-memory: TDES -> TDESInternal and TDES2Key -> TDES2Internal, each Clone, implementing ElectronicCodeBook (new, ENCRYPTION_APPROVED) and BlockPermutation (the block methods) separately, and ModeNames for the five modes offered (the AEAD and MAC names are empty). TDESKey<P: TDESEngine, M: Mode<P>> mirrors AESKey: the key holds the mode's key state, FromKeyMaterial<24> / <16> build it per engine, and the six role impls (symmetric, block and ECB, each direction) forward to the mode traits. TDES3Key<M> and TDES2Key<M> name the engines. A TDES2Key carries the same roles; its create_encryptor fails to compile through the const assert the modes make on ENCRYPTION_APPROVED, which the compile_fail doctests on TDES2Internal now express at the key. The aliases take the key-state lifetime (TDES_CBC<'k, Dir, Pad>, ...), and the docs and examples build a key and call encrypt / decrypt on it. CTR has no usable default for an 8-byte block, so its key is TDES3Key<CTR<6, 8>>. Tests: cbc_alias_tests and ecb_alias_tests become padded_alias_tests and alg_name_tests on the key; mode_key_tests runs the FromKeyMaterial, block, symmetric and stream frameworks over every mode and checks the two-key decryptors against the raw permutation; cavp_mmt_tests drives the CBC and CFB vectors through the keys and the ECB ones through the engines. CLI: the ten tdes commands load a TDES3Key / TDES2Key under NoPadding and keep their contract -- whole blocks, the IV in band, no --iv -- and the tdes2-* commands take DecryptOnlyAction. Checked against the TECBMMT3, TCBCMMT2, TECBMMT2, TCFB64MMT2 and TCFB8MMT2 COUNT = 0 vectors. Assisted-by: Claude:claude-mythos-5-1
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>
Lifted onto this branch from feature/key-rework-memory (5607ba9), where AESKey and AESInternal gained the same: the two engines compares the schedule through Secret's byte-wise equality, which inspects every byte so the time taken does not say where two differ, and a key is equal when its mode state is, bounded on the state rather than on Mode::KeyState. The key objects never hand the material back, so this is the only way two keys can be compared. mode_key_tests: equality follows the material for a clone, the same bytes and a single flipped bit, and a flipped parity bit, which PC-1 drops, leaves a bundle equal. Assisted-by: Claude:claude-mythos-5-1
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
…ack table is measured, and the doc nits The same round as PR #140's review, applied here. TDESKey's two constructors ran check_key_type ahead of the engine's own validation, which is the same check for every mode that has a role; it goes (a scoped `cargo mutants` on key.rs is 0 missed; the crate lists 500 mutants). The constructors' docs say the checks are the engine's. The Security Considerations heading takes the style guide's form and the anchor follows it. The Memory Usage section tabulates the measured stack per entry point and engine: bench_tdes_mem_usage takes the AES bench's measure-closure shape for new and the block entry points of both engines, the two-key engine's decrypting ones only, under the TECBMMT3 COUNT = 0 bundle, and the figures are the closure frames from the release build's frame-layout remarks plus the return address. The unit tests in hazmat/tdes.rs and hazmat/tdes2.rs that tested public behaviour only (the engine sizes, the algorithm names and strengths, the ENCRYPTION_APPROVED gate) move to alg_name_tests; the split/join, parity and schedule pins stay. Nits: the manifest comment, the reference-style link in key.rs that could not fit, and every doc or comment line past 100 columns. Assisted-by: Claude:claude-mythos-5-1
… and -d, with a test suite As for sm4 and camellia: tdes-cbc was CBC<NoPadding> with no --iv while aes*-cbc on this base is PKCS7 at any length with --iv/--iv-file, and the two-key commands took their action positionally. tdes-cbc and tdes2-cbc are now CBC<PKCS7> over the same block-mode helpers; the stream commands, three- and two-key, take --iv/--iv-file through load_iv (8 bytes, or the 6-byte CTR nonce); every command takes the direction as -d/--direction, the two-key ones with `decrypt` as the one value the parser accepts; tdes-ecb and tdes2-ecb keep NoPadding and no IV, as aes*-ecb does. The tdes commands had no CLI tests. tdes_cli_tests.rs pins COUNT = 0 of the CAVP MMT files the crate's suite uses, read from bc-test-data at test time and skipped with a warning without it: tdes-cbc, -cfb, -cfb8 and -ecb encrypt under --iv reproduce TCBCMMT3 (plus the PKCS7 block, computed from the engine), TCFB64MMT3, TCFB8MMT3 and TECBMMT3, and decrypt takes them back; the tdes2-* commands decrypt the two-key [DECRYPT] vectors. The contract tests cover the round trips of every mode, CBC's padding against ECB's alignment rule, a supplied CTR nonce held to the library, -x, the parser's refusal of `tdes2-* -d encrypt`, and the bundle-length, repeated-component and weak-key rejections. Assisted-by: Claude:claude-mythos-5-1
|
The review round of PR #140 applied here, in
|
Summary
Adds
bouncycastle-tdes: a constant-time, table-free Triple DES (TDEA) engine per SP 800-67r2, on the key objects of #172:TDES3Key<M>(three-key) and the decryption-only two-keyTDES2Key<M>, over the enginesTDESInternalandTDES2Internal.Rebased onto
feature/key-rework-memory(#172) on 2026-10-24 and ported to its key-object design; the base is now that branch. The review round of PR #140 is applied here in the last two commits: the keys' redundant key-type check is gone, the CLI matches the AES commands and gains a test suite, the Memory Usage table is measured, and the style and nit items are fixed. Wycheproof carries no TDEA vectors, so there is no suite to add.Description
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>). The aliasesTDES_CBC<'k, Dir, Pad>,TDES_CFB,TDES_CFB8,TDES_CTR,TDES_ECBand theTDES2_*decryptors name what the keys hand out. Keys and engines implementPartialEq/Eqin constant time over the expanded schedule, as on Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172.TDESInternal::new(itsElectronicCodeBookimpl) rejects non-distinct component keys and any of the 64 weak/semi-weak/possibly-weak DES keys (structural check, not a table), asKeyMaterialError::WeakKey(added in this PR).TDES2InternalsetsElectronicCodeBook::ENCRYPTION_APPROVED = false(added in this PR); the encrypting modes assert it in an inlineconst, soTDES2Key::create_encryptorfails to compile under every mode, per SP 800-131A Rev 2's "disallowed for encryption, legacy use for decryption". Twocompile_faildoctests pin it.tdes-*/tdes2-*for every mode, 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(in band otherwise), and every command takes-d/--direction; thetdes2-*commands accept onlydecryptthere, soencryptis refused by the parser. ECB stays unpadded with no IV, asaes*-ecbis.Scope and Risk
New crate only; the
core/cipher/clichanges it depends on (ENCRYPTION_APPROVED, the mode-sideconstassert on it,KeyMaterialError::WeakKey, generic block length in the CLI plumbing,DecryptOnlyAction) are additive and covered by existing suites. No behavioural change to AES or other existing ciphers.Validation
T{E,C}{CB,BC,FB64,FB8}MMT{2,3}vectors (bc-test-data), both key sizes, both directions: CBC and CFB through the keys, ECB through the engines.TestFrameworkFromKeyMaterial,TestFrameworkBlockCipher,TestFrameworkSymmetricCipher,TestFrameworkStreamCipher,TestFrameworkElectronicCodeBook.cargo test --workspace: all passing;cargo test --workspace --all-targets --no-runbuilds the benches;cargo +nightly fmt --checkclean.cli/tests/tdes_cli_tests.rspinsCOUNT = 0of TCBCMMT3, TCFB64MMT3, TCFB8MMT3 and TECBMMT3 through the binary in both directions (encryption under--iv), and the two-key[DECRYPT]vectors through thetdes2-*commands, read from bc-test-data at test time.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,pc1,expandandis_weak, and two loop-bound</<=mutants in the compile-timeanf_constants), 4 timeouts (the compile-time ANF constant derivation).AI Usage Statement
Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-mythos-5-1
🤖 Generated with Claude Code