-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements the ChaCha20Poly1305 AEAD described in RFC 8439 using the `chacha20` and `poly1305` crates.
- Loading branch information
Showing
4 changed files
with
266 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |