AEADs icon indicating copy to clipboard operation
AEADs copied to clipboard

Requesting an example

Open David-OConnor opened this issue 2 years ago • 3 comments

Hi! Does anyone have an example of basic operation, that includes a function or struct field containing each relevant type (key, poly, nonce)? Thank you!

Here is my non-compiling attempt:

Cargo.toml:

crypto = { package = "chacha20poly1305", version = "^0.10.1", default-features = false, features = ["rand_core"] }
rand_core = "^0.6.4"
generic-array = "^1.0.0"

Program:

use chacha20poly1305::{
    aead::{AeadCore, KeyInit},
    ChaCha20Poly1305 as Poly, Nonce
};
use generic_array::GenericArray;

type Key = GenericArray<u8, 12>;
type Nonce2 = GenericArray<u8, 12>;

pub fn make_cipher(rng: &mut Rng) -> (Key, Poly, Nonce) {
    let key = Poly::generate_key(rng);
    let cipher = Poly::new(&key);

    // The nonce is unique per message. 96-bits.
    let nonce = Poly::generate_nonce(rng);

    (key, cipher, nonce)
}

pub fn encrypt(data: &mut [u8], cipher: &Poly, nonce: &Nonce) {
    let ciphertext = cipher.encrypt(&nonce, data).unwrap();
}

pub fn decrypt(data: &mut [u8], cipher: &Poly, nonce: &Nonce) {
    let ciphertext = cipher.decrypt(&nonce, data).unwrap();
}

David-OConnor avatar Feb 13 '24 22:02 David-OConnor

Haven't cracked into this as I'm currently working on another PR rn but it would really help if you could post a dump of your specific compiler errors and warnings. I do think that more/better examples would definitely be a good thing though.

JustAnotherCodemonkey avatar Feb 15 '24 23:02 JustAnotherCodemonkey

Hey! I got it working on my end. Important parts of the code. I think my biggest confusion point was on what a Generic Array is, and how to construct it.

use crypto::{
    aead::{heapless::Vec, AeadCore, AeadInPlace, KeyInit},
    ChaCha20Poly1305 as Poly, Key, Nonce,
};

pub struct CipherError {}

/// Generate a key; provide this as an option for the user to run, eg in the PC config.
pub fn genkey() -> Key {
    let mut rng = Rng {};
    Poly::generate_key(&mut rng)
}

pub struct Cipher {
    /// A 256-bit key. We share this between the radios.
    pub key: Key,
    pub cipher: Poly,
    /// The nonce is unique per message. 96-bits.
    pub nonce: Nonce,
}

impl Cipher {
    pub fn new(key: &Key) -> Self {
        let mut rng = Rng {};

        Self {
            key: key.clone(),
            cipher: Poly::new(&key),
            nonce: Poly::generate_nonce(&mut rng),
        }
    }

    /// Update the nonce; run this prior to sending each message. Send the nonce with the message.
    pub fn update_nonce(&mut self) {
        let mut rng = Rng {};
        self.nonce = Poly::generate_nonce(&mut rng)
    }
}

/// Data must include space for the auth. It will extend the effective message length.
pub fn encrypt(data: &mut [u8], cipher: &Cipher) -> Result<(), CipherError> {
    // We must use heapless here, or else implement a trait like it for an array.
    // Note: buffer needs 16-bytes overhead for auth tag
    let len = data.len();

    let mut buffer: Vec<u8, { RADIO_BUF_SIZE as usize }> = Vec::new();
    buffer.extend_from_slice(&data[..len - AUTH_SIZE]).ok();

    // "associated data is the AD in AEAD. basically its an arbitrary value what you can "mix" a MAC of
    // into the AEAD's cipherext + authentication tag output. this lets you transmit the AEAD in
    // plaintext but still validate it was the right one upon later decryption of the ciphertext.
    // they are also used to add more context into an encryption operation for cotext binding etc
    // since the wrong AD when decrypting will fail the whole operation"
    let associated_data = [];

    // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
    if cipher
        .cipher
        .encrypt_in_place(&cipher.nonce, &associated_data, &mut buffer)
        .is_err()
    {
        return Err(CipherError {});
    }

    data.clone_from_slice(&buffer[..len]);
    Ok(())
}

/// The decrypted data will be of the unencrypted (shorter) length. The input data must
/// not include the nonce.
pub fn decrypt(data: &mut [u8], cipher: &Cipher) -> Result<(), CipherError> {
    let len = data.len(); // includes payload and auth; no nonce.

    let mut buffer: Vec<u8, { RADIO_BUF_SIZE as usize }> = Vec::new();
    buffer.extend_from_slice(data).ok();

    let associated_data = [];

    // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
    if cipher
        .cipher
        .decrypt_in_place(&cipher.nonce, &associated_data, &mut buffer)
        .is_err()
    {
        return Err(CipherError {});
    }

    // Write the decrypted message to the relevant (first) part of the data.
    data[..len - AUTH_SIZE].clone_from_slice(&buffer[..len - AUTH_SIZE]);

    // Write 0s to the part of the buffer taken up by the auth.
    data[len - AUTH_SIZE..len].clone_from_slice(&[0; AUTH_SIZE]);

    Ok(())
}

David-OConnor avatar Feb 15 '24 23:02 David-OConnor

Yeah that's actually a major criticism of mine lol. See the issue below yours in the issues list. I'm working on a PR that adds documentation that spells out this connection more explicitly.

JustAnotherCodemonkey avatar Feb 15 '24 23:02 JustAnotherCodemonkey