Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .github/workflows/aead.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ jobs:
- run: cargo check --all-features
- run: cargo build --target ${{ matrix.target }} --release --no-default-features
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features bytes
# TODO: re-enable in v0.6.1
# - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features heapless
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rand_core

minimal-versions:
Expand Down
26 changes: 6 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 2 additions & 8 deletions aead/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,19 @@ repository = "https://github.com/RustCrypto/traits"
license = "MIT OR Apache-2.0"
keywords = ["crypto", "encryption"]
categories = ["cryptography", "no-std"]
description = """
Traits for Authenticated Encryption with Associated Data (AEAD) algorithms,
such as AES-GCM as ChaCha20Poly1305, which provide a high-level API
"""
description = "Traits for Authenticated Encryption with Associated Data (AEAD) algorithms"

[dependencies]
common = { version = "0.2", package = "crypto-common" }
inout = "0.2.2"

# optional dependencies
arrayvec = { version = "0.7", optional = true, default-features = false }
blobby = { version = "0.4", optional = true }
bytes = { version = "1.11.1", optional = true, default-features = false }

[features]
default = ["rand_core"]
alloc = []
dev = ["blobby", "alloc"]
getrandom = ["common/getrandom"]
getrandom = ["common/getrandom", "rand_core"]
rand_core = ["common/rand_core"]

[lints]
Expand Down
81 changes: 81 additions & 0 deletions aead/src/aead.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::{Result, VariableAead};
use alloc::vec::Vec;

/// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms.
pub trait Aead {
/// Encrypt the given plaintext payload, and return the resulting
/// ciphertext as a vector of bytes.
///
/// # Errors
/// AEAD algorithm implementations may return an error if the plaintext or AAD are too long.
fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>>;
Copy link
Copy Markdown
Member Author

@newpavlov newpavlov Jun 1, 2026

Choose a reason for hiding this comment

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

I removed the Payload type for now. I think we either need a more general solution which would help with other methods as well (e.g. Nonce, Aad, Plaintext, etc. wrappers), or we could just tolerate the potential incorrect reordering of arguments, which hopefully is somewhat unlikely in modern conditions (IDEs and stuff).


/// Decrypt the given ciphertext slice, and return the resulting plaintext
/// as a vector of bytes.
///
/// # Errors
/// - if the `ciphertext` is inauthentic (i.e. tag verification failure)
/// - if the `ciphertext` is too long
/// - if the `aad` is too long
fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>>;

/// Encrypt plaintext in `buf` extending it as necessary.
///
/// On success, `buf` will contain the resulting ciphertext,
/// while on error it will be left intact.
///
/// # Errors
/// AEAD algorithm implementations may return an error if the plaintext or AAD are too long.
#[inline]
fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec<u8>) -> Result<()> {
let res = self.encrypt_into_vec(nonce, aad, buf)?;
*buf = res;
Ok(())
}

/// Decrypt ciphertext in `buf` truncating it as necessary.
///
/// On success, `buf` will contain the resulting plaintext,
/// while on error it will be zeroized.
///
/// # Errors
/// - if the `ciphertext` is inauthentic (i.e. tag verification failure)
/// - if the `ciphertext` is too long
/// - if the `aad` is too long
#[inline]
fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec<u8>) -> Result<()> {
let res = self.decrypt_into_vec(nonce, aad, buf);
match res {
Ok(pt) => {
*buf = pt;
Ok(())
}
Err(err) => {
buf.fill(0);
Err(err)
}
}
}
}

impl<T: VariableAead> Aead for T {
#[inline]
fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
self.variable_encrypt_into(nonce, aad, plaintext, |n| alloc::vec![0u8; n])
}

#[inline]
fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
self.variable_decrypt_into(nonce, aad, ciphertext, |n| alloc::vec![0u8; n])
}

#[inline]
fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec<u8>) -> Result<()> {
self.variable_encrypt_within(nonce, aad, buf, |buf, len| buf.resize(len, 0))
}

#[inline]
fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec<u8>) -> Result<()> {
self.variable_decrypt_within(nonce, aad, buf, Vec::truncate)
}
}
94 changes: 23 additions & 71 deletions aead/src/dev.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
//! Development-related functionality

#![allow(clippy::missing_errors_doc, reason = "dev module")]
#![allow(clippy::missing_panics_doc, reason = "dev module")]
#![allow(clippy::unwrap_in_result, reason = "dev module")]

use crate::{
Aead, AeadInOut, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf,
};
pub use blobby;

use crate::Aead;
use common::KeyInit;

/// AEAD test vector
Expand All @@ -26,8 +21,11 @@ pub struct TestVector {
}

/// Run AEAD test for the provided passing test vector
#[allow(clippy::cast_possible_truncation)]
pub fn pass_test<C: AeadInOut + KeyInit>(
///
/// # Errors
/// - If the cipher has failed initialization with the provided key.
/// - If the AEAD mode has failed to pass the test vector.
pub fn pass_test<C: Aead + KeyInit>(
&TestVector {
key,
nonce,
Expand All @@ -36,76 +34,31 @@ pub fn pass_test<C: AeadInOut + KeyInit>(
ciphertext,
}: &TestVector,
) -> Result<(), &'static str> {
let nonce = nonce.try_into().expect("wrong nonce size");
let cipher = <C as KeyInit>::new_from_slice(key).expect("failed to initialize the cipher");
let cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?;

let res = cipher
.encrypt(
nonce,
Payload {
aad,
msg: plaintext,
},
)
.encrypt_into_vec(nonce, aad, plaintext)
.map_err(|_| "encryption failure")?;
if res != ciphertext {
return Err("encrypted data is different from target ciphertext");
}

let res = cipher
.decrypt(
nonce,
Payload {
aad,
msg: ciphertext,
},
)
.decrypt_into_vec(nonce, aad, ciphertext)
.map_err(|_| "decryption failure")?;
if res != plaintext {
return Err("decrypted data is different from target plaintext");
}

let (ct, tag) = match C::TAG_POSITION {
TagPosition::Prefix => {
let (tag, ct) = ciphertext.split_at(C::TagSize::USIZE);
(ct, tag)
}
TagPosition::Postfix => ciphertext.split_at(plaintext.len()),
};
let tag: &Tag<C> = tag.try_into().expect("tag has correct length");

// Fill output buffer with "garbage" to test that its data does not get read during encryption
let mut buf: alloc::vec::Vec<u8> = (0..plaintext.len()).map(|i| i as u8).collect();
let inout_buf = InOutBuf::new(plaintext, &mut buf).expect("pt and buf have the same length");

let calc_tag = cipher
.encrypt_inout_detached(nonce, aad, inout_buf)
.map_err(|_| "encrypt_inout_detached: encryption failure")?;
if tag != &calc_tag {
return Err("encrypt_inout_detached: tag mismatch");
}
if ct != buf {
return Err("encrypt_inout_detached: ciphertext mismatch");
}

// Fill output buffer with "garbage"
buf.iter_mut()
.enumerate()
.for_each(|(i, v): (usize, &mut u8)| *v = i as u8);

let inout_buf = InOutBuf::new(ct, &mut buf).expect("ct and buf have the same length");
cipher
.decrypt_inout_detached(nonce, aad, inout_buf, tag)
.map_err(|_| "decrypt_inout_detached: decryption failure")?;
if plaintext != buf {
return Err("decrypt_inout_detached: plaintext mismatch");
}

Ok(())
}

/// Run AEAD test for the provided failing test vector
pub fn fail_test<C: AeadInOut + KeyInit>(
///
/// # Errors
/// - If the cipher has failed initialization with the provided key.
/// - If the cipher has passed the test vector.
pub fn fail_test<C: Aead + KeyInit>(
&TestVector {
key,
nonce,
Expand All @@ -114,16 +67,9 @@ pub fn fail_test<C: AeadInOut + KeyInit>(
..
}: &TestVector,
) -> Result<(), &'static str> {
let nonce = nonce.try_into().expect("wrong nonce size");
let cipher = <C as KeyInit>::new_from_slice(key).expect("failed to initialize the cipher");
let cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?;

let res = cipher.decrypt(
nonce,
Payload {
aad,
msg: ciphertext,
},
);
let res = cipher.decrypt_into_vec(nonce, aad, ciphertext);
if res.is_ok() {
Err("decryption must return error")
} else {
Expand All @@ -134,6 +80,9 @@ pub fn fail_test<C: AeadInOut + KeyInit>(
/// Define AEAD test for passing test vectors
#[macro_export]
macro_rules! new_pass_test {
($name:ident, $cipher:ty $(,)?) => {
$crate::new_pass_test!($name, stringify!($name), $cipher);
};
($name:ident, $test_name:expr, $cipher:ty $(,)?) => {
#[test]
fn $name() {
Expand Down Expand Up @@ -165,6 +114,9 @@ macro_rules! new_pass_test {
/// Define AEAD test for failing test vectors
#[macro_export]
macro_rules! new_fail_test {
($name:ident, $cipher:ty $(,)?) => {
$crate::new_fail_test!($name, stringify!($name), $cipher);
};
($name:ident, $test_name:expr, $cipher:ty $(,)?) => {
#[test]
fn $name() {
Expand Down
Loading
Loading