Skip to main content

Crate vitaminc_aead

Crate vitaminc_aead 

Source
Expand description

§Vitamin C AEAD

Crates.io Workflow Status

Authenticated Encryption with Associated Data (AEAD) primitives for building secure encryption systems.

This crate is part of the Vitamin C framework to make cryptography code healthy.

§What is AEAD?

AEAD (Authenticated Encryption with Associated Data) is a form of encryption that provides both confidentiality and authenticity. It ensures that:

  • Confidentiality: The plaintext is encrypted and cannot be read without the key
  • Authenticity: The ciphertext cannot be modified without detection
  • Associated Data: Additional data can be authenticated (but not encrypted) alongside the ciphertext

This crate provides traits and types for implementing AEAD operations in a safe and ergonomic way.

§Key Features

  • Composable encryption shape: The Cipher trait is paired with SeqCipher and MapCipher sub-traits so a single cipher can drive byte, sequence, and map encryption with a consistent API
  • Visitor-pattern decryption: The Decrypt / Decipher / DecipherVisitor trio mirrors serde’s Deserialize / Deserializer / Visitor, letting types describe how they decrypt themselves independently of any specific cipher
  • Flexible AAD handling: The IntoAad trait lets strings, byte slices, integers, tuples, and () all be used as additional authenticated data
  • Protected types integration: Works with vitaminc-protected so sensitive plaintext stays wrapped through encrypt and decrypt
  • Side-channel-aware errors: The Unspecified error type reveals no information about the cause of a failure

§Usage

§Implementing the Cipher trait

A Cipher is consumed by the operation it drives — typically you implement it for a reference to your cipher state (&MyCipher) so the same cipher can be reused across many calls. The trait declares the output (Ok) and error types, plus associated types for sequence and map encryption:

use std::any::Any;
use vitaminc_aead::{Cipher, IntoAad, MapCipher, SeqCipher, Unspecified};
use vitaminc_protected::Protected;

pub struct MyCipher { /* key material, nonce generator, ... */ }

pub struct MyCipherText(/* ... */);
pub struct MySeqCipher<'c>(&'c MyCipher /* + state */);
pub struct MyMapCipher<'c>(&'c MyCipher /* + state */);

impl<'c> Cipher for &'c MyCipher {
    type Ok = MyCipherText;
    type Error = Unspecified;
    type SeqCipher = MySeqCipher<'c>;
    type MapCipher = MyMapCipher<'c>;

    fn encrypt_bytes_vec<'a, A>(
        self,
        data: Protected<Vec<u8>>,
        aad: A,
    ) -> Result<Self::Ok, Self::Error>
    where
        A: IntoAad<'a>,
    {
        unimplemented!("seal `data` with AAD and return a ciphertext")
    }

    fn encrypt_seq(self, size_hint: Option<usize>) -> Self::SeqCipher {
        unimplemented!("return a SeqCipher initialised with `size_hint` capacity")
    }

    fn encrypt_map(self) -> Self::MapCipher {
        unimplemented!("return a MapCipher")
    }

    fn encrypt_none<'a, A>(self, _aad: A) -> Result<Self::Ok, Self::Error>
    where
        A: IntoAad<'a>,
    {
        unimplemented!("produce an authenticated 'absent' marker bound to `aad`")
    }

    fn passthrough<T>(self, _value: T) -> Result<Self::Ok, Self::Error>
    where
        T: Any + Send + 'static,
    {
        unimplemented!("store `value` unencrypted inside the cipher's output container")
    }
}

impl<'c> SeqCipher for MySeqCipher<'c> {
    type Ok = MyCipherText;
    type Error = Unspecified;

    fn encrypt_next<'a, T, A>(self, _data: T, _aad: A) -> Result<Self, Self::Error>
    where
        T: vitaminc_aead::Encrypt,
        A: IntoAad<'a>,
    { unimplemented!() }

    fn passthrough_next<T>(self, _value: T) -> Result<Self, Self::Error>
    where
        T: Any + Send + 'static,
    { unimplemented!() }

    fn end(self) -> Result<Self::Ok, Self::Error> { unimplemented!() }
}

impl<'c> MapCipher for MyMapCipher<'c> {
    type Ok = MyCipherText;
    type Error = Unspecified;

    fn encrypt_key(self, _key: &'static str) -> Result<Self, Self::Error> { unimplemented!() }

    fn encrypt_value<'a, T, A>(self, _value: T, _aad: A) -> Result<Self, Self::Error>
    where
        T: vitaminc_aead::Encrypt,
        A: IntoAad<'a>,
    { unimplemented!() }

    fn passthrough_entry<T>(self, _key: &'static str, _value: T) -> Result<Self, Self::Error>
    where
        T: Any + Send + 'static,
    { unimplemented!() }

    fn end(self) -> Result<Self::Ok, Self::Error> { unimplemented!() }
}

For a complete reference implementation see vitaminc_encrypt::Aes256Cipher.

§Encrypting data

Once you have a Cipher implementation (typically for &MyCipher), use the Encrypt trait. Many built-in types already implement Encrypt:

use vitaminc_aead::Encrypt;

// `cipher: MyCipher` where `Cipher` is implemented for `&MyCipher`.

// Encrypt a string
let encrypted_string = "secret message".encrypt(&cipher)?;

// Encrypt with additional authenticated data
let encrypted_with_aad = "secret".encrypt_with_aad(&cipher, "context data")?;

// Encrypt a byte array
let encrypted_bytes = [1u8, 2, 3, 4, 5].encrypt(&cipher)?;

Note that Encrypt::encrypt consumes the cipher value. Implementing Cipher for &MyCipher (rather than MyCipher) means you can pass &cipher for each call and reuse the underlying state.

§Decrypting data

Decryption uses a visitor pattern modelled on serde::Deserialize:

  • A type that knows how to decrypt itself implements Decrypt.
  • A cipher provides a Decipher (typically wrapping a ciphertext + cipher state) that drives the decryption.
  • Concrete cipher implementations expose ergonomic decrypt entry points — for example, Aes256Cipher provides cipher.decrypt::<T>(ciphertext) and cipher.decrypt_with_aad::<T, _>(ciphertext, aad).
// Using a concrete cipher (see `vitaminc_encrypt::Aes256Cipher`):
let plaintext: String = cipher.decrypt(ciphertext)?;
let plaintext: String = cipher.decrypt_with_aad(ciphertext, "context data")?;

String, Vec<u8>, [u8; N], u32, Vec<T: Decrypt>, HashMap<String, T: Decrypt>, and Protected<T: Decrypt> all implement Decrypt out of the box.

Note on maps: HashMap decryption yields HashMap<String, T>, but map encryption requires statically known keys — only HashMap<&'static str, T> implements Encrypt. Map keys are passed to MapCipher::encrypt_key, which takes a &'static str. A HashMap<String, T> with runtime-derived keys can therefore be decrypted but not encrypted directly.

§Additional Authenticated Data (AAD)

Many types can be used as AAD through the IntoAad trait:

use vitaminc_aead::Encrypt;

// String AAD
"my-secret".encrypt_with_aad(&cipher, "user_id:123")?;

// Byte slice AAD
"my-secret".encrypt_with_aad(&cipher, &b"metadata"[..])?;

// Integer AAD
"my-secret".encrypt_with_aad(&cipher, 42u64)?;

// Tuple AAD (PAE-encoded to prevent canonicalisation attacks)
"my-secret".encrypt_with_aad(&cipher, ("user_id", "session_token"))?;

// No AAD
"my-secret".encrypt_with_aad(&cipher, ())?;

§Working with Protected Types

The crate integrates with vitaminc-protected so sensitive plaintext stays wrapped:

use vitaminc_aead::Encrypt;
use vitaminc_protected::Protected;

let sensitive_data = Protected::new([1u8, 2, 3, 4, 5]);
let encrypted = sensitive_data.encrypt(&cipher)?;

The corresponding Decrypt impl for Protected<T> re-wraps the decrypted plaintext, so the value stays inside Protected end-to-end.

§Custom Types

Implementing Encrypt and Decrypt for your own types lets you choose which fields are encrypted and how the ciphertext is structured.

use vitaminc_aead::{
    Cipher, Decipher, DecipherVisitor, Decrypt, Encrypt, IntoAad, MapAccess, MapCipher,
    Unspecified,
};

struct User {
    id: u64,
    email: String,
    password_hash: String,  // Will be encrypted
}

impl Encrypt for User {
    fn encrypt_with_aad<'a, C, A>(self, cipher: C, aad: A) -> Result<C::Ok, C::Error>
    where
        C: Cipher,
        A: IntoAad<'a>,
    {
        // Encrypt the user as a map of named fields, encrypting only the
        // password hash. id and email are not stored here for brevity —
        // a real implementation would also encrypt or pass them through.
        cipher
            .encrypt_map()
            .encrypt_entry("password_hash", self.password_hash, aad)?
            .end()
    }
}

impl<'c> Decrypt<'c> for User {
    fn decrypt<D: Decipher<'c>>(decipher: D) -> D::Ok<Self> {
        // A visitor describes what to do with each shape the cipher might
        // produce. Here we only accept maps.
        struct UserVisitor;
        impl<'c> DecipherVisitor<'c> for UserVisitor {
            type Value = User;

            fn visit_map<A: MapAccess<'c>>(self, mut map: A) -> Result<Self::Value, Unspecified> {
                let mut password_hash = None;
                while let Some((key, value)) =
                    map.next_entry::<String>().map_err(|_| Unspecified)?
                {
                    if key == "password_hash" {
                        password_hash = Some(value);
                    }
                }
                Ok(User {
                    id: 0,
                    email: String::new(),
                    password_hash: password_hash.ok_or(Unspecified)?,
                })
            }
        }
        decipher.decrypt_map(UserVisitor)
    }
}

The visitor pattern keeps the cipher and the type independent: the cipher decides how the ciphertext is laid out and how AAD is enforced, while the type decides how its fields are reassembled.

§Nonce Generation

The crate provides nonce generation utilities for AEAD operations:

use vitaminc_aead::{NonceGenerator, RandomNonceGenerator};

// Create a random nonce generator for 12-byte nonces
let generator = RandomNonceGenerator::<12>::init()?;
let nonce = generator.generate()?;

§Security Considerations

  • Always use unique nonces for each encryption operation with the same key
  • Never reuse nonces with the same key, as this can compromise security
  • The Unspecified error type is used to prevent side-channel attacks by not revealing information about failures
  • When decrypting, always verify authentication before processing the plaintext

§CipherStash

Vitamin C is brought to you by the team at CipherStash.

License: MIT

Structs§

Aad
Associated Authenticated Data passed to an AEAD cipher.
CipherTextBuilder
LocalCipherText
Nonce
Represents a nonce used in AEAD encryption of N bytes length.
RandomNonceGenerator
Unspecified
An error that provides no information about the failure. It is crucial when returning an error from a cipher operation that does not reveal any details about the failure as this can lead to side channel attacks.

Traits§

Cipher
A driver of an encryption operation, analogous to serde’s Serializer.
Decipher
A trait for types that can decrypt data, driving a DecipherVisitor to produce values.
DecipherVisitor
A visitor over the structural shape of a ciphertext, analogous to serde’s Visitor.
Decrypt
The counterpart to Encrypt — a type that knows how to decrypt itself using a Decipher. Analogous to serde’s Deserialize.
Encrypt
A type that knows how to encrypt itself by driving a Cipher.
IntoAad
Types that can be canonically converted into an Aad.
MapAccess
Pull-style access to entries of a decrypted map.
MapCipher
Sub-cipher driving the encryption of a map of key/value pairs.
NonceGenerator
SeqAccess
Pull-style access to elements of a decrypted sequence.
SeqCipher
Sub-cipher driving the encryption of a sequence of values.