Skip to content

Commit bfee786

Browse files
authored
Merge pull request #3 from RustCrypto/chacha20poly1305
ChaCha20Poly1305 AEAD
2 parents 13f4031 + 10abd72 commit bfee786

File tree

4 files changed

+246
-2
lines changed

4 files changed

+246
-2
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# RustCrypto: stream ciphers
1+
# RustCrypto: Authenticated Encryption with Associated Data
22
[![Build Status](https://travis-ci.org/RustCrypto/AEADs.svg?branch=master)](https://travis-ci.org/RustCrypto/AEADs) [![dependency status](https://deps.rs/repo/github/RustCrypto/AEADs/status.svg)](https://deps.rs/repo/github/AEADs/stream-ciphers)
33

44
Collection of [Authenticated Encryption with Associated Data (AEAD)][1]

chacha20poly1305/Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ version = "0.0.0"
44
authors = ["RustCrypto Developers"]
55
edition = "2018"
66
license = "MIT OR Apache-2.0"
7-
description = "CTR block mode of operation"
7+
description = """
8+
ChaCha20Poly1305 Authenticated Encryption with Additional Data Algorithm (RFC 8439)
9+
"""
810
documentation = "chacha20poly1305"
911
repository = "https://github.com/RustCrypto/AEADs"
1012
keywords = ["crypto", "cipher", "aead"]
1113
categories = ["cryptography", "no-std"]
1214

1315
[dependencies]
16+
aead = { version = "0.1", git = "https://github.com/RustCrypto/traits" }
17+
chacha20 = { version = "0.2.1", features = ["zeroize"] }
18+
poly1305 = "0.2"
19+
zeroize = { version = "0.9", default-features = false }

chacha20poly1305/src/lib.rs

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//! ChaCha20Poly1305 Authenticated Encryption with Additional Data Algorithm
2+
//! (RFC 8439)
3+
4+
#![no_std]
5+
6+
extern crate alloc;
7+
8+
pub use aead;
9+
10+
use aead::generic_array::typenum::{U0, U12, U16, U32};
11+
use aead::{generic_array::GenericArray, StatelessAead, Error, NewAead};
12+
use alloc::vec::Vec;
13+
use chacha20::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
14+
use chacha20::ChaCha20;
15+
use core::convert::TryInto;
16+
use poly1305::{Poly1305, Tag};
17+
use zeroize::{Zeroize, Zeroizing};
18+
19+
/// ChaCha20Poly1305 AEAD
20+
#[derive(Clone)]
21+
pub struct ChaCha20Poly1305 {
22+
/// Secret key
23+
key: GenericArray<u8, U32>,
24+
}
25+
26+
impl NewAead for ChaCha20Poly1305 {
27+
type KeySize = U32;
28+
29+
fn new(key: GenericArray<u8, U32>) -> Self {
30+
ChaCha20Poly1305 { key }
31+
}
32+
}
33+
34+
impl StatelessAead for ChaCha20Poly1305 {
35+
type NonceSize = U12;
36+
type TagSize = U16;
37+
type CiphertextOverhead = U0;
38+
39+
fn encrypt(
40+
&self,
41+
associated_data: &[u8],
42+
nonce: &GenericArray<u8, Self::NonceSize>,
43+
plaintext: &[u8],
44+
) -> Result<Vec<u8>, Error> {
45+
CipherInstance::new(&self.key, nonce).encrypt(associated_data, plaintext)
46+
}
47+
48+
fn decrypt(
49+
&self,
50+
associated_data: &[u8],
51+
nonce: &GenericArray<u8, Self::NonceSize>,
52+
ciphertext: &[u8],
53+
) -> Result<Vec<u8>, Error> {
54+
CipherInstance::new(&self.key, nonce).decrypt(associated_data, ciphertext)
55+
}
56+
}
57+
58+
impl Drop for ChaCha20Poly1305 {
59+
fn drop(&mut self) {
60+
self.key.as_mut_slice().zeroize();
61+
}
62+
}
63+
64+
/// ChaCha20Poly1305 instantiated with a particular nonce
65+
struct CipherInstance {
66+
chacha20: ChaCha20,
67+
poly1305: Poly1305,
68+
}
69+
70+
impl CipherInstance {
71+
/// Instantiate the underlying cipher with a particular nonce
72+
fn new(key: &GenericArray<u8, U32>, nonce: &GenericArray<u8, U12>) -> Self {
73+
let mut chacha20 = ChaCha20::new(key, nonce);
74+
75+
// Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream
76+
let mut auth_key = Zeroizing::new([0u8; poly1305::KEY_SIZE]);
77+
chacha20.apply_keystream(&mut *auth_key);
78+
79+
// Set ChaCha20 counter to 1
80+
chacha20.seek(chacha20::BLOCK_SIZE as u64);
81+
82+
let poly1305 = Poly1305::new(&auth_key);
83+
Self { chacha20, poly1305 }
84+
}
85+
86+
/// Encrypt the given message, allocating a vector for the resulting ciphertext
87+
fn encrypt(self, associated_data: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
88+
let mut buffer = Vec::with_capacity(plaintext.len() + poly1305::BLOCK_SIZE);
89+
buffer.extend_from_slice(plaintext);
90+
91+
let tag = self.encrypt_in_place(associated_data, &mut buffer)?;
92+
buffer.extend_from_slice(tag.code().as_slice());
93+
Ok(buffer)
94+
}
95+
96+
/// Encrypt the given message in-place, returning the authentication tag
97+
fn encrypt_in_place(mut self, associated_data: &[u8], buffer: &mut [u8]) -> Result<Tag, Error> {
98+
if buffer.len() / chacha20::BLOCK_SIZE >= chacha20::MAX_BLOCKS {
99+
return Err(Error);
100+
}
101+
102+
self.poly1305.input_padded(associated_data);
103+
self.chacha20.apply_keystream(buffer);
104+
self.poly1305.input_padded(buffer);
105+
self.authenticate_lengths(associated_data, buffer)?;
106+
Ok(self.poly1305.result())
107+
}
108+
109+
/// Decrypt the given message, allocating a vector for the resulting plaintext
110+
fn decrypt(self, associated_data: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
111+
if ciphertext.len() < poly1305::BLOCK_SIZE {
112+
return Err(Error);
113+
}
114+
115+
let tag_start = ciphertext.len() - poly1305::BLOCK_SIZE;
116+
let mut buffer = Vec::from(&ciphertext[..tag_start]);
117+
let tag: [u8; poly1305::BLOCK_SIZE] = ciphertext[tag_start..].try_into().unwrap();
118+
self.decrypt_in_place(associated_data, &mut buffer, &tag)?;
119+
120+
Ok(buffer)
121+
}
122+
123+
/// Decrypt the given message, first authenticating ciphertext integrity
124+
/// and returning an error if it's been tampered with.
125+
fn decrypt_in_place(
126+
mut self,
127+
associated_data: &[u8],
128+
buffer: &mut [u8],
129+
tag: &[u8; poly1305::BLOCK_SIZE],
130+
) -> Result<(), Error> {
131+
if buffer.len() / chacha20::BLOCK_SIZE >= chacha20::MAX_BLOCKS {
132+
return Err(Error);
133+
}
134+
135+
self.poly1305.input_padded(associated_data);
136+
self.poly1305.input_padded(buffer);
137+
self.authenticate_lengths(associated_data, buffer)?;
138+
139+
// This performs a constant-time comparison using the `subtle` crate
140+
if self.poly1305.result() == Tag::new(*GenericArray::from_slice(tag)) {
141+
self.chacha20.apply_keystream(buffer);
142+
Ok(())
143+
} else {
144+
Err(Error)
145+
}
146+
}
147+
148+
/// Authenticate the lengths of the associated data and message
149+
fn authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error> {
150+
let associated_data_len: u64 = associated_data.len().try_into().map_err(|_| Error)?;
151+
let buffer_len: u64 = buffer.len().try_into().map_err(|_| Error)?;
152+
153+
self.poly1305.input(&associated_data_len.to_le_bytes());
154+
self.poly1305.input(&buffer_len.to_le_bytes());
155+
Ok(())
156+
}
157+
}

chacha20poly1305/tests/lib.rs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! ChaCha20Poly1305 test vectors.
2+
//!
3+
//! From RFC 8439 Section 2.8.2:
4+
//! <https://tools.ietf.org/html/rfc8439#section-2.8.2>
5+
6+
use chacha20poly1305::aead::{Aead, NewAead};
7+
use chacha20poly1305::aead::generic_array::GenericArray;
8+
use chacha20poly1305::ChaCha20Poly1305;
9+
10+
const KEY: &[u8; 32] = &[
11+
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
12+
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
13+
];
14+
15+
const NONCE: &[u8; 12] = &[
16+
0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
17+
];
18+
19+
const AAD: &[u8; 12] = &[
20+
0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
21+
];
22+
23+
const PLAINTEXT: &[u8] = b"Ladies and Gentlemen of the class of '99: \
24+
If I could offer you only one tip for the future, sunscreen would be it.";
25+
26+
const CIPHERTEXT: &[u8] = &[
27+
0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
28+
0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
29+
0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
30+
0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
31+
0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58,
32+
0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
33+
0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
34+
0x61, 0x16,
35+
];
36+
37+
const TAG: &[u8] = &[
38+
0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91,
39+
];
40+
41+
#[test]
42+
fn encrypt() {
43+
let key = GenericArray::from_slice(KEY);
44+
let nonce = GenericArray::from_slice(NONCE);
45+
46+
let mut cipher = ChaCha20Poly1305::new(*key);
47+
let ciphertext = cipher.encrypt(AAD, nonce, PLAINTEXT).unwrap();
48+
49+
let tag_begins = ciphertext.len() - 16;
50+
assert_eq!(CIPHERTEXT, &ciphertext[..tag_begins]);
51+
assert_eq!(TAG, &ciphertext[tag_begins..]);
52+
}
53+
54+
#[test]
55+
fn decrypt() {
56+
let key = GenericArray::from_slice(KEY);
57+
let nonce = GenericArray::from_slice(NONCE);
58+
59+
let mut ciphertext = Vec::from(CIPHERTEXT);
60+
ciphertext.extend_from_slice(TAG);
61+
62+
let mut cipher = ChaCha20Poly1305::new(*key);
63+
let plaintext = cipher.decrypt(AAD, nonce, &ciphertext).unwrap();
64+
65+
assert_eq!(PLAINTEXT, plaintext.as_slice());
66+
}
67+
68+
#[test]
69+
fn decrypt_modified() {
70+
let key = GenericArray::from_slice(KEY);
71+
let nonce = GenericArray::from_slice(NONCE);
72+
73+
let mut ciphertext = Vec::from(CIPHERTEXT);
74+
ciphertext.extend_from_slice(TAG);
75+
76+
// Tweak the first byte
77+
ciphertext[0] ^= 0xaa;
78+
79+
let mut cipher = ChaCha20Poly1305::new(*key);
80+
assert!(cipher.decrypt(AAD, nonce, &ciphertext).is_err());
81+
}

0 commit comments

Comments
 (0)