Skip to content

Commit

Permalink
[WIP] ChaCha20Poly1305 AEAD
Browse files Browse the repository at this point in the history
Implements the ChaCha20Poly1305 AEAD described in RFC 8439 using the
`chacha20` and `poly1305` crates.
  • Loading branch information
tarcieri committed Aug 19, 2019
1 parent 13f4031 commit 98ec721
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
members = [
"chacha20poly1305",
]

[patch.crates-io]
aead = { git = "https://github.com/mobilecoinofficial/traits" }
9 changes: 8 additions & 1 deletion chacha20poly1305/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ version = "0.0.0"
authors = ["RustCrypto Developers"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "CTR block mode of operation"
description = """
ChaCha20Poly1305 Authenticated Encryption with Additional Data Algorithm (RFC 8439)
"""
documentation = "chacha20poly1305"
repository = "https://github.com/RustCrypto/AEADs"
keywords = ["crypto", "cipher", "aead"]
categories = ["cryptography", "no-std"]

[dependencies]
aead = "0"
chacha20 = "0.2" # TODO: add: features = ["zeroize"]
generic-array = { version = "0.12", default-features = false }
poly1305 = "0.2"
zeroize = { version = "0.9", default-features = false }
174 changes: 174 additions & 0 deletions chacha20poly1305/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//! ChaCha20Poly1305 Authenticated Encryption with Additional Data Algorithm
//! (RFC 8439)

#![no_std]

extern crate alloc;

pub use aead;
// TODO(tarcieri): re-export this from the AEAD crate
pub use generic_array;

use aead::{Aead, Error, NewAead};
use alloc::vec::Vec;
use chacha20::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
use chacha20::ChaCha20;
use core::convert::TryInto;
use generic_array::{
typenum::{U0, U12, U16, U32},
GenericArray,
};
use poly1305::{Poly1305, Tag};
use zeroize::{Zeroize, Zeroizing};

/// Maximum number of blocks that can be encrypted with ChaCha20 before the
/// counter overflows.
// TODO(tarcieri): move this to the `chacha20` crate?
const CHACHA20_MAX_BLOCKS: usize = core::u32::MAX as usize;

/// Size of a ChaCha20 block in bytes
// TODO(tarcieri): move this to the `chacha20` crate?
const CHACHA20_BLOCK_SIZE: usize = 64;

/// ChaCha20Poly1305 AEAD
#[derive(Clone)]
pub struct ChaCha20Poly1305 {
/// Secret key
key: GenericArray<u8, U32>,
}

impl NewAead for ChaCha20Poly1305 {
type KeySize = U32;

fn new(key: GenericArray<u8, U32>) -> Self {
ChaCha20Poly1305 { key }
}
}

impl Aead for ChaCha20Poly1305 {
type NonceSize = U12;
type TagSize = U16;
type CiphertextOverhead = U0;

fn encrypt(
&mut self,
associated_data: &[u8],
nonce: &GenericArray<u8, Self::NonceSize>,
plaintext: &[u8],
) -> Result<Vec<u8>, Error> {
CipherInstance::new(&self.key, nonce).encrypt(associated_data, plaintext)
}

fn decrypt(
&mut self,
associated_data: &[u8],
nonce: &GenericArray<u8, Self::NonceSize>,
ciphertext: &[u8],
) -> Result<Vec<u8>, Error> {
CipherInstance::new(&self.key, nonce).decrypt(associated_data, ciphertext)
}
}

impl Drop for ChaCha20Poly1305 {
fn drop(&mut self) {
self.key.as_mut_slice().zeroize();
}
}

/// ChaCha20Poly1305 instantiated with a particular nonce
struct CipherInstance {
chacha20: ChaCha20,
poly1305: Poly1305,
}

impl CipherInstance {
/// Instantiate the underlying cipher with a particular nonce
fn new(key: &GenericArray<u8, U32>, nonce: &GenericArray<u8, U12>) -> Self {
let mut chacha20 = ChaCha20::new(key, nonce);

// Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream
let mut auth_key = Zeroizing::new([0u8; poly1305::KEY_SIZE]);
chacha20.apply_keystream(&mut *auth_key);

// Set ChaCha20 counter to 1
chacha20.seek(CHACHA20_BLOCK_SIZE as u64);

let poly1305 = Poly1305::new(&auth_key);
Self { chacha20, poly1305 }
}

/// Encrypt the given message, allocating a vector for the resulting ciphertext
fn encrypt(self, associated_data: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let mut buffer = Vec::with_capacity(plaintext.len() + poly1305::BLOCK_SIZE);
buffer.extend_from_slice(plaintext);

let tag = self.encrypt_in_place(associated_data, &mut buffer)?;
buffer.extend_from_slice(tag.code().as_slice());
Ok(buffer)
}

/// Encrypt the given message in-place, returning the authentication tag
fn encrypt_in_place(
mut self,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
if buffer.len() / CHACHA20_BLOCK_SIZE >= CHACHA20_MAX_BLOCKS {
return Err(Error);
}

self.poly1305.input_padded(associated_data);
self.chacha20.apply_keystream(buffer);
self.poly1305.input_padded(buffer);
self.authenticate_lengths(associated_data, buffer)?;
Ok(self.poly1305.result())
}

/// Decrypt the given message, allocating a vector for the resulting plaintext
fn decrypt(self, associated_data: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
if ciphertext.len() < poly1305::BLOCK_SIZE {
return Err(Error);
}

let tag_start = ciphertext.len() - poly1305::BLOCK_SIZE;
let mut buffer = Vec::from(&ciphertext[..tag_start]);
let tag: [u8; poly1305::BLOCK_SIZE] = ciphertext[tag_start..].try_into().unwrap();

self.decrypt_in_place(associated_data, &mut buffer, &tag)?;
Ok(buffer)
}

/// Decrypt the given message, first authenticating ciphertext integrity
/// and returning an error if it's been tampered with.
fn decrypt_in_place(
mut self,
associated_data: &[u8],
buffer: &mut [u8],
tag: &[u8; poly1305::BLOCK_SIZE],
) -> Result<(), Error> {
if buffer.len() / CHACHA20_BLOCK_SIZE >= CHACHA20_MAX_BLOCKS {
return Err(Error);
}

self.poly1305.input_padded(associated_data);
self.poly1305.input_padded(buffer);
self.authenticate_lengths(associated_data, buffer)?;

if self.poly1305.result() == Tag::new(*GenericArray::from_slice(tag)) {
self.chacha20.apply_keystream(buffer);
Ok(())
} else {
Err(Error)
}
}

/// Authenticate the lengths of the associated data and message
fn authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error> {
let associated_data_len: u64 = associated_data.len().try_into().map_err(|_| Error)?;
let buffer_len: u64 = buffer.len().try_into().map_err(|_| Error)?;

self.poly1305.input(&associated_data_len.to_le_bytes());
self.poly1305.input(&buffer_len.to_le_bytes());
Ok(())
}
}
81 changes: 81 additions & 0 deletions chacha20poly1305/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! ChaCha20Poly1305 test vectors.
//!
//! From RFC 8439 Section 2.8.2:
//! <https://tools.ietf.org/html/rfc8439#section-2.8.2>

use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::generic_array::GenericArray;
use chacha20poly1305::ChaCha20Poly1305;

const KEY: &[u8; 32] = &[
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
];

const NONCE: &[u8; 12] = &[
0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
];

const AAD: &[u8; 12] = &[
0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
];

const PLAINTEXT: &[u8] = b"Ladies and Gentlemen of the class of '99: \
If I could offer you only one tip for the future, sunscreen would be it.";

const CIPHERTEXT: &[u8] = &[
0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58,
0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
0x61, 0x16,
];

const TAG: &[u8] = &[
0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91,
];

#[test]
fn encrypt() {
let key = GenericArray::from_slice(KEY);
let nonce = GenericArray::from_slice(NONCE);

let mut cipher = ChaCha20Poly1305::new(*key);
let ciphertext = cipher.encrypt(AAD, nonce, PLAINTEXT).unwrap();

let tag_begins = ciphertext.len() - 16;
assert_eq!(CIPHERTEXT, &ciphertext[..tag_begins]);
assert_eq!(TAG, &ciphertext[tag_begins..]);
}

#[test]
fn decrypt() {
let key = GenericArray::from_slice(KEY);
let nonce = GenericArray::from_slice(NONCE);

let mut ciphertext = Vec::from(CIPHERTEXT);
ciphertext.extend_from_slice(TAG);

let mut cipher = ChaCha20Poly1305::new(*key);
let plaintext = cipher.decrypt(AAD, nonce, &ciphertext).unwrap();

assert_eq!(PLAINTEXT, plaintext.as_slice());
}

#[test]
fn decrypt_modified() {
let key = GenericArray::from_slice(KEY);
let nonce = GenericArray::from_slice(NONCE);

let mut ciphertext = Vec::from(CIPHERTEXT);
ciphertext.extend_from_slice(TAG);

// Tweak the first byte
ciphertext[0] ^= 0xaa;

let mut cipher = ChaCha20Poly1305::new(*key);
assert!(cipher.decrypt(AAD, nonce, &ciphertext).is_err());
}

0 comments on commit 98ec721

Please sign in to comment.