Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requesting an example #576

Open
David-OConnor opened this issue Feb 13, 2024 · 3 comments
Open

Requesting an example #576

David-OConnor opened this issue Feb 13, 2024 · 3 comments

Comments

@David-OConnor
Copy link

David-OConnor commented Feb 13, 2024

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();
}
@JustAnotherCodemonkey
Copy link

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.

@David-OConnor
Copy link
Author

David-OConnor commented Feb 15, 2024

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(())
}

@JustAnotherCodemonkey
Copy link

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.

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

No branches or pull requests

3 participants
@David-OConnor @JustAnotherCodemonkey and others