Skip to content

aria: add bouncycastle-aria, a constant-time ARIA block cipher - #142

Open
dghgit wants to merge 31 commits into
release/0.1.3alphafrom
feature/aria
Open

dghgit wants to merge 31 commits into
release/0.1.3alphafrom
feature/aria

Conversation

@dghgit

@dghgit dghgit commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds bouncycastle-aria: a constant-time, table-free, low-memory ARIA block cipher (RFC 5794), ported from Bouncy Castle Java's ARIAEngine, on the key objects of #172: ARIA128Key<M>, ARIA192Key<M> and ARIA256Key<M> over the engines ARIA128Internal and friends.

Rebased onto feature/key-rework-memory (#172) on 2026-10-24 and ported to its key-object design; the base is now release/0.1.3alpha, which took #172 as 615065a.

Description

  • All four S-boxes (SB1/SB2 and their inverses SB3/SB4) are Boolean circuits, reusing the Boyar-Peralta AES non-linear section via field isomorphisms.
  • Bit-sliced over 16-bit planes, four blocks at a time.
  • ARIAKey<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. The crate aliases CBC, CFB, CFB8 and CTR (ARIA_CBC_128<'k, Dir, Pad>, ARIA_CFB_128<'k, Dir>, ...); the engines implement ModeNames for all nine modes, since the trait asks for every mode (RFC 5794 Appendix B assigns ARIA an identifier in ECB, CBC, CFB, OFB, CTR, CMAC, OCB2, GCM, CCM and key wrap; GMAC has none), so the mode types over the engines are named; the keys carry every role pair AESKey does: the symmetric and block pairs (the four aliased modes, ECB<PKCS7>, ECB<NoPadding>, CTR<N> at any nonce length), the AEAD pairs (GCM, CCM, CCM_Packet), the hazmat ECB pair (ECB<Blocks>) and the MAC pair (CMAC), with GMAC's nonce-bound pair inherent on the key; ARIA_GCM_*, ARIA_CCM_*, ARIA_CMAC_* and ARIA_GMAC_* name those workers. Keys and engines implement PartialEq / Eq in 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.
  • CLI subcommands aria{128,192,256}-{cbc,cfb,cfb8,ctr,gcm,ccm}, through the key objects, with the aes* commands' contract: CBC<PKCS7> at any length, the IV or nonce in band unless --iv/--iv-file supplies it, -d/--direction.

Scope and Risk

New crate only; the shared changes it carries (the bouncycastle-aria workspace entry, the umbrella re-export, the memory bench) are additive. No behavioural change to AES or other existing ciphers.

Validation

  • RFC 5794 Appendix A vectors (ciphertexts plus every intermediate round key/value for the 128-bit key) and BC Java's own ARIATest vectors, through the engines; OpenSSL's evpciph_aria.txt (KISA's published vectors) for ECB through the engines and CBC, CFB128, CFB8 and CTR through the keys; Wycheproof ARIA-CBC-PKCS5 (216 cases, 144 invalid, each refused with the error its flags predict and the buffer zeroized) through ARIA*Key<CBC<PKCS7>>, and Wycheproof ARIA-CCM (486 of 552 cases dispatchable) and ARIA-GCM (194 of 311) through the CCM and GCM mode traits over the engines, all through the shared test_data_loaders.
  • Shared frameworks over the keys: TestFrameworkFromKeyMaterial for every mode, and the block, symmetric and stream suites; the CTR counter limit pinned through a key.
  • cargo test --workspace: all passing; cargo test --workspace --all-targets --no-run builds the benches; cargo +nightly fmt --check and RUSTDOCFLAGS='-D warnings' cargo doc clean. The CLI integration tests replay the KISA vectors through the binary in both directions, under --iv.
  • cargo mutants -p bouncycastle-aria on the current tree (2026-11-03, after the review round): 1222 mutants, 1174 caught, 29 unviable, 19 missed, every miss a |/^ equivalence on disjoint byte positions (transpose, diffuse, substitute, ortho::swap, the four S-box circuits); key.rs has no missed mutant.
  • The Memory Usage table is measured (deepest frame chain per entry point from the release build's frame-layout remarks, rustc 1.97 nightly, x86-64), not estimated.

Second review round (2026-11-04, three commits): the diffusion layer's byte-parity broadcast is three rotations, since LLVM folded the shift-and-OR form back into the multiply it was meant to avoid (44 imul in the crate's release assembly before, none after; the check is on the assembly and the crate docs say how to repeat it); the decryption-order unit test transcribes Sec 2.2 and Sec 2.3.2 round by round on the Appendix A.1 ciphertext and checks decrypt_4blocks against it, where before it asserted on a copy of the engine's index map; the aria*-cbc help no longer claims RFC 5794 Appendix B's id-aria*-cbc, whose default padding is ISO/IEC 9797-1 method 2; the key comment and Security Considerations say that ECB<PKCS7> and ECB<NoPadding> keys reach ECB through the safe API and that the keys have no authenticated mode yet (the AEAD roles are a follow-up, with Encrypt-then-MAC suggested meanwhile); the S-box prose, the stale SM4/Camellia citations, two semicolons, a private-item link and the unused serde_json dev-dependency. A scoped cargo mutants run over round.rs and hazmat/aria.rs is queued; its numbers follow here.

Third round (2026-11-04, e64d01e and b5f786e): the AEAD, ECB and MAC role pairs are back on the key, as discussed on the Security Considerations thread. 4f39822 had dropped them for want of a published vector; the ARIA-GCM, ARIA-CCM and ARIA-CMAC Wycheproof suites now run through ARIA128Key<GCM>, <CCM<N, T>> and <CMAC> (194, 486 and 306 cases) rather than over the permutation, the constructor takes MAC material for a MAC mode as AESKey does, the ARIA_GCM_*, ARIA_CCM_*, ARIA_CMAC_* and ARIA_GMAC_* aliases name the workers, the mode-key suite covers the restored keys, and the CLI gains aria*-gcm and aria*-ccm with Wycheproof known answers.

AI Usage Statement

  • Yes, non-trivial code changes were generated by AI

Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-mythos-5-1
Assisted-by: Claude:claude-fable-5-1

🤖 Generated with Claude Code

@hubot
hubot deleted the feature/aria branch September 19, 2026 01:02
@dghgit
dghgit restored the feature/aria branch September 19, 2026 04:26
@ounsworth

ounsworth commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

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.

@ounsworth ounsworth closed this Sep 19, 2026
@dghgit dghgit reopened this Sep 19, 2026
@hubot
hubot deleted the feature/aria branch September 28, 2026 15:21
@hubot
hubot deleted the branch release/0.1.3alpha September 28, 2026 15:21
@hubot
hubot restored the feature/aria branch September 28, 2026 15:45
@dghgit
dghgit changed the base branch from feature/xof-cshake to feature/simple-ciphers September 28, 2026 15:49
@dghgit
dghgit changed the base branch from feature/simple-ciphers to feature/key-rework-memory October 24, 2026 12:47
@hubot
hubot force-pushed the feature/aria branch 2 times, most recently from 1c2b7da to f778457 Compare October 24, 2026 17:46
@dghgit dghgit added this to the 0.1.4alpha milestone Oct 31, 2026
Comment thread cli/src/main.rs Resolved
Comment thread crypto/aria/src/key.rs Resolved
Comment thread crypto/aria/src/key.rs Resolved
Comment thread crypto/aria/src/hazmat/aria.rs Resolved
Comment thread crypto/aria/src/lib.rs Resolved
Comment thread crypto/aria/src/key.rs Resolved
Comment thread crypto/aria/src/schedule.rs Resolved
Comment thread crypto/aria/src/round.rs Resolved
Comment thread crypto/aria/src/lib.rs Resolved
dghgit added a commit that referenced this pull request Nov 3, 2026
…cs sit on the items, the Wycheproof CBC suite uses the shared loaders, and the CTR limit is pinned through a key

Three of the aria review's findings hold for this crate too. The cbc, cfb,
cfb8, ctr and key modules are private, so the docs each carried never
rendered; they now sit on `Camellia_CBC_128`, `Camellia_CFB_128`,
`Camellia_CFB8_128`, `Camellia_CTR_128` and `CamelliaKey`, the one path each
has, and the module headers say so. The CAMELLIA-CBC-PKCS5 suite found its
file through its own two candidate paths and skipped silently when neither
resolved, accepted any error for an invalid case, and could not tell a
zeroized output buffer from one never written; it is now the AES suite over
the Camellia keys -- the shared `wycheproof_json` loader, the expected error
read off the case's flags, a pre-filled output buffer that a failed decrypt
must leave all zeros -- as `cbc_wycheproof.rs`, named as the suites it
copies. And no test reached `Ctr`'s counter limit through a Camellia key:
stream_mode_tests now does, under `CTR<15>`, where the 256th block is the
last and the byte after it is refused.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Nov 3, 2026
…it on the items, and the CTR limit is pinned through a key

Two of the aria review's findings hold for this crate too. The cbc, cfb,
cfb8, ctr and key modules are private, so the docs each carried never
rendered; they now sit on `TDES_CBC`, `TDES_CFB`, `TDES_CFB8`, `TDES_CTR`
and `TDESKey`, the one path each has, and the module headers say so. And no
test reached `Ctr`'s counter limit through a TDES key: mode_key_tests now
does, under `CTR<7, 8>`, where the 256th block is the last and the byte
after it is refused.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Nov 3, 2026
…t on the items, and the CTR limit is pinned through a key

Two of the aria review's findings hold for this crate too. The cbc, cfb,
cfb8, ctr, gcm and key modules are private, so the docs each carried -- the
construction, the examples, the CTR nonce note, the key's roles and Memory
Usage -- never rendered; they now sit on `SM4_CBC`, `SM4_CFB`, `SM4_CFB8`,
`SM4_CTR`, `SM4_GCM` and `SM4Key`, the one path each has, and the module
headers say so. And no test reached `Ctr`'s counter limit through an SM4
key: stream_mode_tests now does, under `CTR<15>`, where the 256th block is
the last and the byte after it is refused.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Nov 3, 2026
`expand` copied `W0 .. W3` out of their `Secret` into plain locals and built
the seventeen candidate round keys in a plain array before copying the first
`ROUNDS + 1` into the schedule's `Secret`, while its docs said the
intermediates were scrubbed (PR #142 review). The `W`s are now read in place
and each candidate is computed straight into the schedule, from a table of
`(a, b, rotation)` triples that spells out Sec 2.2's `ek_i = W_a ^ rot(W_b)`;
no copy of a `W` or a candidate is made outside a `Secret`. The docs and the
crate's constant-time caveat say what is still not scrubbed: the by-value
temporaries of `FO`/`FE` and the word conversions.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Nov 3, 2026
…t on the items, and the CTR limit is pinned through a key

Two of the aria review's findings hold for this crate too. The cbc, cfb,
cfb8, ctr, gcm and key modules are private, so the docs each carried -- the
construction, the examples, the CTR nonce note, the key's roles and Memory
Usage -- never rendered; they now sit on `SM4_CBC`, `SM4_CFB`, `SM4_CFB8`,
`SM4_CTR`, `SM4_GCM` and `SM4Key`, the one path each has, and the module
headers say so. And no test reached `Ctr`'s counter limit through an SM4
key: stream_mode_tests now does, under `CTR<15>`, where the 256th block is
the last and the byte after it is refused.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Nov 3, 2026
…cs sit on the items, the Wycheproof CBC suite uses the shared loaders, and the CTR limit is pinned through a key

Three of the aria review's findings hold for this crate too. The cbc, cfb,
cfb8, ctr and key modules are private, so the docs each carried never
rendered; they now sit on `Camellia_CBC_128`, `Camellia_CFB_128`,
`Camellia_CFB8_128`, `Camellia_CTR_128` and `CamelliaKey`, the one path each
has, and the module headers say so. The CAMELLIA-CBC-PKCS5 suite found its
file through its own two candidate paths and skipped silently when neither
resolved, accepted any error for an invalid case, and could not tell a
zeroized output buffer from one never written; it is now the AES suite over
the Camellia keys -- the shared `wycheproof_json` loader, the expected error
read off the case's flags, a pre-filled output buffer that a failed decrypt
must leave all zeros -- as `cbc_wycheproof.rs`, named as the suites it
copies. And no test reached `Ctr`'s counter limit through a Camellia key:
stream_mode_tests now does, under `CTR<15>`, where the 256th block is the
last and the byte after it is refused.

Assisted-by: Claude:claude-mythos-5-1
@dghgit
dghgit changed the base branch from feature/key-rework-memory to release/0.1.3alpha November 3, 2026 12:59
@hubot
hubot deleted the feature/aria branch November 3, 2026 13:16
@hubot
hubot deleted the branch release/0.1.3alpha November 3, 2026 13:17
@hubot
hubot restored the feature/aria branch November 3, 2026 14:53
Comment thread crypto/aria/src/lib.rs Resolved
Comment thread crypto/aria/src/lib.rs Resolved
Comment thread cli/src/aria_cbc_cmd.rs Resolved
Comment thread crypto/aria/src/hazmat/aria.rs Resolved
Comment thread crypto/aria/src/round.rs Resolved
Comment thread crypto/aria/Cargo.toml Resolved
Comment thread crypto/aria/src/lib.rs
//! [`ARIA128Internal`] and its siblings transform exactly 16 bytes. Using one directly on data
//! means ECB, which is not confidential: identical plaintext blocks produce identical ciphertext
//! blocks, so structure in the plaintext survives encryption. **Do not do it.** Use a mode of
//! operation, and prefer an authenticated one so that ciphertext tampering is detected. RFC 5794

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.

With the ARIA-GCM and ARIA-CCM Wycheproof suites passing over the engines, the "no published vector" reason in 4f39822 for dropping the AEAD roles no longer holds. Is restoring them planned as a follow-up? Meanwhile, this paragraph tells users to "prefer an authenticated" mode that the ARIA keys don't offer. One line pointing to Encrypt-then-MAC with this library's HMAC, or to the follow-up, would help.

@dghgit dghgit Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roles come back now that the key is the source of the operator. Done in e64d01e: the AEAD, ECB and MAC pairs are on ARIAKey as on AESKey, the three Wycheproof suites run through the keys (ARIA-GCM 194 cases, ARIA-CCM 486, ARIA-CMAC 306), ARIA_GCM_*/ARIA_CCM_*/ARIA_CMAC_*/ARIA_GMAC_* name the workers, and the Security Considerations paragraph now points at ARIA128Key<GCM> and <CCM<N, T>> as the authenticated modes. b5f786e adds aria*-gcm and aria*-ccm to the CLI. These are based off the published OIDs/vectors for the Cipher.

… not a shift-and-OR of the parity byte, which LLVM folded back into the multiply by 0x01010101 it was meant to avoid (44 imul in the crate's release assembly; now none); the check is on the assembly, and the crate docs say how to repeat it

Assisted-by: Claude:claude-fable-5-1
….2's dk_i and Sec 2.3.2 one round at a time on the Appendix A.1 ciphertext, checks the plaintext comes back, and checks decrypt_4blocks agrees, where before it asserted on a copy of the engine's own index map (the BlockPermutation batch is named as such in the doc)

Assisted-by: Claude:claude-fable-5-1
…ommands are not RFC 5794 Appendix B's id-aria*-cbc, whose default padding is ISO/IEC 9797-1 method 2, not PKCS#7; the key comment and Security Considerations say that ECB<PKCS7> and ECB<NoPadding> keys hand out ECB through the safe API, and that the keys have no authenticated mode yet (the AEAD roles are a follow-up; Encrypt-then-MAC meanwhile); the S-box prose says its input constants are applied before M; the SM4 and Camellia citations, two stray semicolons and a stale private-item link go; serde_json is no longer a dev-dependency

Assisted-by: Claude:claude-fable-5-1
…arries them -- 4f39822 dropped them for want of a published vector, and the ARIA-GCM, ARIA-CCM and ARIA-CMAC Wycheproof suites now run through ARIA128Key<GCM>, <CCM<N, T>> and <CMAC> (194, 486 and 306 cases); the constructor takes MAC material for a MAC mode, retagged for the permutation as AESKey does; ARIA_GCM_*, ARIA_CCM_*, ARIA_CMAC_* and ARIA_GMAC_* name the workers, with GMAC's nonce-bound generator and verifier inherent on the key; the mode-key suite adds the ECB<Blocks>, GCM and CCM_Packet keys and the MAC keys' round trips, and the docs say which modes a key now covers

Assisted-by: Claude:claude-fable-5-1
…d aes*-ccm commands over the ARIA keys (the CCM runner and its key table are shared with the AES file), with Wycheproof known answers in both directions, tag-check and round-trip tests

Assisted-by: Claude:claude-fable-5-1
…feature/aria: the CLI dispatch keeps this branch's ARIA arms beside the release's rewritten SP 800-185 arms, the shared CCM runner stays pub(crate) under the release's clippy allowance, the memory-bench manifest lists both new binaries, and ARIAParams::Schedule gains the NoPadding bound that Secret's equality now requires, as AESParams::Schedule has

Assisted-by: Claude:claude-fable-5-1
@dghgit

dghgit commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Merged release/0.1.3alpha at 029a17b (the squash of PR #190) into this branch as f16cb1f: the CLI dispatch, the shared CCM runner's visibility and the memory-bench manifest were the conflicts, and ARIAParams::Schedule takes the NoPadding bound that Secret's equality now requires; workspace suite passes.

@officialfrancismendoza

officialfrancismendoza commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

@dghgit no blockers or major issues in the code. Primarily nits, documentation, and test gaps, so feel free to merge after!

Comment thread cli/src/main.rs
#[arg(long)]
key: Option<String>,

/// A file containing the 16-byte ARIA key, in binary or hex.

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.

Since #190 the shared loaders read every file raw unless the global --hex-input is given: load_secret for key files, read_input_file for IV, nonce and AAD files. #190 updated the AES help to "(raw bytes; hex with --hex-input)". The 18 ARIA variants still say the key file may be "in binary or hex" and that IV and nonce files get "no hex decoding", so both statements are now wrong.

Concretely, a hex key file is rejected without --hex-input ("ARIA-128 needs a 16-byte key, got 32 bytes"). And aria256-cbc --key-file k.hex, with k.hex holding the hex of a 16-byte key, is accepted as 32 ASCII bytes.

Could the 18 --key-file lines, the 18 --iv-file/--nonce-file lines and the IV/AAD paragraphs take the AES wording?

P: ElectronicCodeBook<KEY_LEN, 16> + ModeNames + Clone,
{
type Mode<const N: usize, const T: usize> = CCM<N, T>;
let perm = P::new(&cipher_key::<KEY_LEN>(key_bytes)).expect("a wycheproof key");

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 suite builds the permutation and calls CCM::<N, T>::encrypt_detached_in_place_nonce(&perm, …), so it never goes through ARIA*Key<CCM<N, T>>, despite the description's "now run through … <CCM<N, T>>". It shows in cargo mutants -p bouncycastle-aria: key.rs:396, encrypt_detached_in_place_nonce replaced with Ok([0; TAG_LEN]) or Ok([1; TAG_LEN]), survives, because only the CLI tests exercise that forwarder.

The forwarder itself is right; I drove it through the CLI against an SP 800-38C reference at every nonce and tag length.

Could run_case build ARIA*Key<CCM<N, T>>::from_key_material and use the key's two in-place methods, as gcm_wycheproof.rs does? Porting the relevant part of AES's ccm_key_tests.rs would also work.

/// A MAC-mode key's `KEY_TYPE` is the `MACKey` the mode asks for: MAC material builds it and
/// cipher material is refused by type, at both key lengths and for both MAC modes.
#[test]
fn a_mac_mode_key_takes_mac_material_and_refuses_cipher_material() {

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.

The MAC keys rely on as_cipher_key carrying the MAC key's strength onto the retagged copy, but nothing here tests a weak MAC key. With that line replaced by Ok(()), all 97 ARIA tests still pass; the same edit fails AES's suite twice. AES's a_key_below_its_own_strength_is_refused_at_construction (in cmac_key_tests.rs and gmac_key_tests.rs) ports directly.

GMAC also has no known answer. The ARIA-GCM Wycheproof file's empty-message cases (tcIds 2, 67 and 171) are GMAC known answers; or there is AES's GMAC-vs-GCM cross-check in gmac_key_tests.rs.


// RFC 5794 Appendix B assigns object identifiers to ARIA in ECB, CBC, CFB (whose parameters
// carry the segment size, so CFB8 is `id-aria*-cfb` with `k = 8`), CTR, CMAC, GCM and CCM, among
// others; GMAC has none, and is named here only because the trait asks for every mode. The keys

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.

The last sentence here ("The keys hand out workers for the four aliased modes … only") is still in place. Since e64d01e8 the keys also serve ECB, GCM, CCM, CMAC and GMAC, so I'd just drop it.

//!
//! The known answer is Project Wycheproof's `testvectors_v1/aria_gcm_test.json` tcId 9 (ARIA-128,
//! a 96-bit IV, a 128-bit tag, 1 byte of AAD, a 20-byte message), which the crate's
//! `gcm_wycheproof.rs` runs with the rest of the file.

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.

These module docs are AES's verbatim. They point at gcm_bc-test-data.rs and "the ACVP one" (and, in ccm_wycheproof.rs, ccm_bc-test-data.rs), none of which exists in this crate.

Comment thread cli/src/main.rs

/// ARIA-128 in GCM (NIST SP 800-38D), streaming stdin to stdout.
///
/// AUTHENTICATED, unlike the other AES modes here: tampering with the ciphertext, the AAD or

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.

Two nits:

Comment thread cli/src/aria_gcm_cmd.rs
use bouncycastle::cipher::modes::GCM;
use bouncycastle::core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey};

pub(crate) fn aria128_gcm_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.

Two nits:

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