Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4a5da4c
aria: add bouncycastle-aria, a constant-time, table-free ARIA ported …
dghgit Sep 5, 2026
3e5a83a
aria: every CBC and ECB alias takes <Dir, Pad> over the shared Padded…
dghgit Sep 8, 2026
53befbb
aria: add Wycheproof CBC-PKCS5 vectors, 216 cases of which 144 are in…
dghgit Sep 8, 2026
ffda885
aria: the ElectronicCodeBook trait is the only public route to the pe…
dghgit Sep 7, 2026
8f83b11
aria: consolidate the bc-java attribution
dghgit Sep 19, 2026
b6c17fb
mem_usage_benches: fence bench_aria_mem_usage's shell recipes as ```text
dghgit Sep 19, 2026
bfe7dc2
aria: add the ARIA_CFB_*, ARIA_CFB8_* and ARIA_CTR_* aliases and thei…
dghgit Sep 20, 2026
ff7b21a
aria: make the new CLI stream-mode test harnesses tolerate a broken p…
dghgit Sep 20, 2026
fe79f2d
aria: give aria_cbc_cli_tests.rs the same broken-pipe tolerance the s…
dghgit Sep 20, 2026
30231e5
aria: drop references to ElectronicCodeBook's batch-method defaults, …
dghgit Sep 25, 2026
80ddc87
aria: adapt to feature/simple-ciphers -- the API changes this branch …
dghgit Sep 28, 2026
a2539b2
aria: adapt to feature/simple-ciphers 0fb4e79 -- the stream-mode alia…
dghgit Sep 29, 2026
cec8cfa
aria: adopt the hazmat layout (#156 step 5)
dghgit Oct 1, 2026
7abbb4d
aria: project the ARIA_CBC_* aliases through core's sealed Direction:…
dghgit Oct 1, 2026
4e0ff6a
aria: what the linear replay dropped -- the merge-carried adaptations…
dghgit Oct 24, 2026
f778457
aria: onto the key objects -- ARIAKey<P, M> hands out the mode worker…
dghgit Oct 24, 2026
1f0e3c7
aria: the keys carry only the roles the aliases use -- the AEAD, ECB …
dghgit Oct 25, 2026
5d3a0e6
aria: the engine and the keys compare in constant time
dghgit Oct 25, 2026
f02135f
Merge branch 'feature/key-rework-memory' into feature/aria
dghgit Oct 26, 2026
a756041
Merge branch 'feature/key-rework-memory' into feature/aria
dghgit Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ version = "0.1.3"
# *** Internal Dependencies ***
bouncycastle = { path = "./" }
bouncycastle-aes = { path = "./crypto/aes" }
bouncycastle-aria = { path = "./crypto/aria" }
bouncycastle-ascon = { path = "./crypto/ascon" }
bouncycastle-base64 = { path = "./crypto/base64" }
bouncycastle-cipher = { path = "./crypto/cipher" }
Expand Down Expand Up @@ -48,6 +49,7 @@ edition.workspace = true

[dependencies]
bouncycastle-aes.workspace = true
bouncycastle-aria.workspace = true
bouncycastle-ascon.workspace = true
bouncycastle-base64.workspace = true
bouncycastle-cipher.workspace = true
Expand Down
83 changes: 83 additions & 0 deletions cli/src/aria_cbc_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! ARIA-CBC encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the IV convention, key loading, stdin framing and
//! block-alignment enforcement are all in [`crate::helpers::block_mode_helpers`], shared with the `aes*-cbc`,
//! `aes*-cfb` and `aes*-ecb` commands. See that module for the
//! command-line contract.
//!
//! `aria128-cbc` / `aria192-cbc` / `aria256-cbc` are the same command as `aes*-cbc` over the ARIA
//! permutation (RFC 5794; 16-, 24- or 32-byte key, 16-byte block -- the `id-aria*-cbc` algorithms of
//! RFC 5794 Appendix B), so every remark there applies unchanged. CBC (NIST SP 800-38A Sec 6.2)
//! provides confidentiality only: neither the ciphertext nor the IV is authenticated. Do not decrypt
//! data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{
CipherDirection, decrypt_stream, encrypt_stream, load_key_object, start_encryptor,
};
use bouncycastle::aria::{ARIA128Key, ARIA192Key, ARIA256Key, BLOCK_LEN};
use bouncycastle::cipher::modes::CBC;
use bouncycastle::cipher::padding::NoPadding;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};

/// Names the mode in error messages.
const MODE: &str = "CBC";

pub(crate) fn aria128_cbc_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 16>(
action,
&load_key_object::<ARIA128Key<CBC<NoPadding>>, 16>(key, key_file, "ARIA-128"),
output_hex,
);
}

pub(crate) fn aria192_cbc_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 24>(
action,
&load_key_object::<ARIA192Key<CBC<NoPadding>>, 24>(key, key_file, "ARIA-192"),
output_hex,
);
}

pub(crate) fn aria256_cbc_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 32>(
action,
&load_key_object::<ARIA256Key<CBC<NoPadding>>, 32>(key, key_file, "ARIA-256"),
output_hex,
);
}

/// Dispatches to the shared streaming loops under the key's CBC mode. `NoPadding` keeps the
/// command's contract: whole blocks only, with the IV in band.
fn run<K, const KEY_LEN: usize>(action: &CipherDirection, key: &K, output_hex: bool)
where
K: SymmetricEncryptionKey<KEY_LEN, BLOCK_LEN, BLOCK_LEN>
+ SymmetricDecryptionKey<KEY_LEN, BLOCK_LEN, BLOCK_LEN>,
{
match action {
CipherDirection::Encrypt => {
let (enc, generated_iv) = start_encryptor(None, |rng| key.create_encryptor(rng));
encrypt_stream::<_, BLOCK_LEN>(enc, generated_iv.as_ref(), output_hex, Some(MODE))
}
CipherDirection::Decrypt => decrypt_stream::<_, BLOCK_LEN>(
None,
|iv| key.create_decryptor(iv),
output_hex,
Some(MODE),
),
}
}
75 changes: 75 additions & 0 deletions cli/src/aria_cfb8_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! ARIA-CFB8 encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the IV convention, key loading and stdin framing are in
//! [`crate::helpers::stream_mode_helpers`] (and [`crate::helpers::block_mode_helpers`] for the key loader), shared with the
//! `aes*-cfb8` commands. See those modules for the command-line contract.
//!
//! `aria128-cfb8` / `aria192-cfb8` / `aria256-cfb8` are the same command as `aes*-cfb8`
//! over the ARIA permutation (RFC 5794; 16-, 24- or 32-byte key, 16-byte block), so every
//! remark there applies unchanged. The segment size is one byte (NIST SP 800-38A Sec 6.3 with
//! `s = 8`): a DIFFERENT, NON-INTEROPERABLE mode from the CFB128 of `aria*-cfb`, costing a full
//! ARIA call per byte, sixteen times the work. Prefer `aria*-cfb` unless a byte-granular
//! self-synchronising stream is required or the format demands CFB8.
//!
//! # Warning
//!
//! CFB8 provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the IV is authenticated. Do not decrypt data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{CipherDirection, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aria::{ARIA128Key, ARIA192Key, ARIA256Key, BLOCK_LEN};
use bouncycastle::cipher::modes::CFB8;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor};

pub(crate) fn aria128_cfb8_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 16>(
action,
&load_key_object::<ARIA128Key<CFB8>, 16>(key, key_file, "ARIA-128"),
output_hex,
);
}

pub(crate) fn aria192_cfb8_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 24>(
action,
&load_key_object::<ARIA192Key<CFB8>, 24>(key, key_file, "ARIA-192"),
output_hex,
);
}

pub(crate) fn aria256_cfb8_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 32>(
action,
&load_key_object::<ARIA256Key<CFB8>, 32>(key, key_file, "ARIA-256"),
output_hex,
);
}

/// Dispatches to the shared streaming loops with `Cfb8` filled in as the mode; the init data
/// travels in band, as the command's contract says.
fn run<'k, K, const KEY_LEN: usize>(action: &CipherDirection, key: &'k K, output_hex: bool)
where
K: SymmetricEncryptionKey<KEY_LEN, BLOCK_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, BLOCK_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<BLOCK_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<BLOCK_LEN>,
{
run_stream_mode::<K, KEY_LEN, BLOCK_LEN>(action, key, None, output_hex)
}
80 changes: 80 additions & 0 deletions cli/src/aria_cfb_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! ARIA-CFB128 encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the IV convention, key loading and stdin framing are in
//! [`crate::helpers::stream_mode_helpers`] (and [`crate::helpers::block_mode_helpers`] for the key loader), shared with the
//! `aes*-cfb` commands. See those modules for the command-line contract.
//!
//! `aria128-cfb` / `aria192-cfb` / `aria256-cfb` are the same command as `aes*-cfb`
//! over the ARIA permutation (RFC 5794; 16-, 24- or 32-byte key, 16-byte block), so every
//! remark there applies unchanged. The segment size is the full block, i.e. **CFB128**
//! (NIST SP 800-38A Sec 6.3 with `s = b`); the `s = 8` variant is a different, non-interoperable
//! mode and has its own commands, `aria*-cfb8`.
//!
//! CFB is a stream cipher, so unlike `aria*-cbc` these commands accept input of any length and
//! pad nothing; the ciphertext is exactly as long as the plaintext.
//!
//! # Warning
//!
//! CFB provides confidentiality only. It does not detect tampering, and neither the ciphertext nor
//! the IV is authenticated. Flipping a ciphertext bit flips the *same* bit of the plaintext in the
//! *same* block (SP 800-38A Appendix D, Table D.2), at the cost of randomising the next one, so an
//! attacker edits the block they aimed at. Do not decrypt data you have not authenticated
//! separately.

use crate::helpers::block_mode_helpers::{CipherDirection, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aria::{ARIA128Key, ARIA192Key, ARIA256Key, BLOCK_LEN};
use bouncycastle::cipher::modes::CFB;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor};

pub(crate) fn aria128_cfb_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 16>(
action,
&load_key_object::<ARIA128Key<CFB>, 16>(key, key_file, "ARIA-128"),
output_hex,
);
}

pub(crate) fn aria192_cfb_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 24>(
action,
&load_key_object::<ARIA192Key<CFB>, 24>(key, key_file, "ARIA-192"),
output_hex,
);
}

pub(crate) fn aria256_cfb_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 32>(
action,
&load_key_object::<ARIA256Key<CFB>, 32>(key, key_file, "ARIA-256"),
output_hex,
);
}

/// Dispatches to the shared streaming loops with `Cfb` filled in as the mode; the init data
/// travels in band, as the command's contract says.
fn run<'k, K, const KEY_LEN: usize>(action: &CipherDirection, key: &'k K, output_hex: bool)
where
K: SymmetricEncryptionKey<KEY_LEN, BLOCK_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, BLOCK_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<BLOCK_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<BLOCK_LEN>,
{
run_stream_mode::<K, KEY_LEN, BLOCK_LEN>(action, key, None, output_hex)
}
84 changes: 84 additions & 0 deletions cli/src/aria_ctr_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! ARIA-CTR encryption and decryption, streaming stdin to stdout.
//!
//! Only the cipher wiring lives here: the nonce convention, key loading and stdin framing are in
//! [`crate::helpers::stream_mode_helpers`] (and [`crate::helpers::block_mode_helpers`] for the key loader), shared with the
//! `aes*-ctr` commands. See those modules for the command-line contract.
//!
//! `aria128-ctr` / `aria192-ctr` / `aria256-ctr` are the same command as `aes*-ctr` over
//! the ARIA permutation (RFC 5794; 16-, 24- or 32-byte key, 16-byte block), so every remark
//! there applies unchanged, including the split of the counter block: the nonce is **12 bytes** and
//! the counter the remaining 4, giving 2^32 blocks -- 64 GiB -- in a single message. That is
//! NIST SP 800-38A Appendix B.2's construction, and it is the one KISA's published ARIA-CTR
//! vectors use: their counter block is all zeros and increments from there, which is what a
//! twelve-byte zero nonce and a counter starting at zero produce.
//!
//! `encrypt` writes that 12-byte nonce as the first bytes of its output and `decrypt` reads it back,
//! exactly as the other modes do with their IVs; note that it is 12 bytes here, not 16.
//!
//! # Warning
//!
//! CTR provides confidentiality only and is the most malleable mode here: flipping any ciphertext
//! bit flips exactly the corresponding plaintext bit and affects nothing else (SP 800-38A
//! Appendix D, Table D.2), so an attacker can edit the plaintext at will with no garbling to give
//! it away. A repeated nonce under one key is fatal rather than merely unwise -- the same keystream
//! twice leaks the XOR of the two messages -- which is why the nonce is drawn from the OS-backed
//! DRBG and there is no way to supply one. Do not decrypt data you have not authenticated
//! separately.

use crate::helpers::block_mode_helpers::{CipherDirection, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aria::{ARIA128Key, ARIA192Key, ARIA256Key, CTR_NONCE_LEN};
use bouncycastle::cipher::modes::CTR;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor};

pub(crate) fn aria128_ctr_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 16>(
action,
&load_key_object::<ARIA128Key<CTR>, 16>(key, key_file, "ARIA-128"),
output_hex,
);
}

pub(crate) fn aria192_ctr_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 24>(
action,
&load_key_object::<ARIA192Key<CTR>, 24>(key, key_file, "ARIA-192"),
output_hex,
);
}

pub(crate) fn aria256_ctr_cmd(
action: &CipherDirection,
key: &Option<String>,
key_file: &Option<String>,
output_hex: bool,
) {
run::<_, 32>(
action,
&load_key_object::<ARIA256Key<CTR>, 32>(key, key_file, "ARIA-256"),
output_hex,
);
}

/// Dispatches to the shared streaming loops with `Ctr` filled in as the mode; the init data
/// travels in band, as the command's contract says.
fn run<'k, K, const KEY_LEN: usize>(action: &CipherDirection, key: &'k K, output_hex: bool)
where
K: SymmetricEncryptionKey<KEY_LEN, CTR_NONCE_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, CTR_NONCE_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<CTR_NONCE_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<CTR_NONCE_LEN>,
{
run_stream_mode::<K, KEY_LEN, CTR_NONCE_LEN>(action, key, None, output_hex)
}
Loading
Loading