198 lines
6.6 KiB
Rust
198 lines
6.6 KiB
Rust
// SPDX-License-Identifier: CC0-1.0
|
|
|
|
//! # ChaCha20 - Poly1305
|
|
//!
|
|
//! Combine the ChaCha20 stream cipher with the Poly1305 message authentication code
|
|
//! to form an authenticated encryption with additional data (AEAD) algorithm.
|
|
|
|
#![no_std]
|
|
// Experimental features we need.
|
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![cfg_attr(bench, feature(test))]
|
|
// Coding conventions.
|
|
#![warn(missing_docs)]
|
|
#![warn(deprecated_in_future)]
|
|
#![doc(test(attr(warn(unused))))]
|
|
// Exclude lints we don't think are valuable.
|
|
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
|
|
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
|
|
#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")`
|
|
|
|
#[cfg(feature = "alloc")]
|
|
extern crate alloc;
|
|
#[cfg(feature = "std")]
|
|
extern crate std;
|
|
|
|
#[cfg(bench)]
|
|
mod benches;
|
|
#[cfg(bench)]
|
|
extern crate test;
|
|
pub mod chacha20;
|
|
pub mod poly1305;
|
|
|
|
use core::fmt;
|
|
|
|
use chacha20::ChaCha20;
|
|
use poly1305::Poly1305;
|
|
|
|
pub use self::chacha20::{Key, Nonce};
|
|
|
|
/// Zero array for padding slices.
|
|
const ZEROES: [u8; 16] = [0u8; 16];
|
|
|
|
/// Errors encrypting and decrypting messages with ChaCha20 and Poly1305 authentication tags.
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum Error {
|
|
/// Additional data showing up when it is not expected.
|
|
UnauthenticatedAdditionalData,
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Error::UnauthenticatedAdditionalData => write!(f, "Unauthenticated aad."),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "std")]
|
|
impl std::error::Error for Error {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
match self {
|
|
Error::UnauthenticatedAdditionalData => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Encrypt and decrypt content along with a authentication tag.
|
|
pub struct ChaCha20Poly1305 {
|
|
key: Key,
|
|
nonce: Nonce,
|
|
}
|
|
|
|
impl ChaCha20Poly1305 {
|
|
/// Make a new instance of a ChaCha20Poly1305 AEAD.
|
|
pub const fn new(key: Key, nonce: Nonce) -> Self { ChaCha20Poly1305 { key, nonce } }
|
|
|
|
/// Encrypt content in place and return the Poly1305 16-byte authentication tag.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// - `content` - Plaintext to be encrypted in place.
|
|
/// - `aad` - Optional metadata covered by the authentication tag.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The 16-byte authentication tag.
|
|
pub fn encrypt(self, content: &mut [u8], aad: Option<&[u8]>) -> [u8; 16] {
|
|
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
|
|
chacha.apply_keystream(content);
|
|
let keystream = chacha.get_keystream(0);
|
|
let mut poly =
|
|
Poly1305::new(keystream[..32].try_into().expect("slicing produces 32-byte slice"));
|
|
let aad = aad.unwrap_or(&[]);
|
|
// AAD and ciphertext are padded if not 16-byte aligned.
|
|
poly.input(aad);
|
|
let aad_overflow = aad.len() % 16;
|
|
if aad_overflow > 0 {
|
|
poly.input(&ZEROES[0..(16 - aad_overflow)]);
|
|
}
|
|
|
|
poly.input(content);
|
|
let text_overflow = content.len() % 16;
|
|
if text_overflow > 0 {
|
|
poly.input(&ZEROES[0..(16 - text_overflow)]);
|
|
}
|
|
|
|
let len_buffer = encode_lengths(aad.len() as u64, content.len() as u64);
|
|
poly.input(&len_buffer);
|
|
poly.tag()
|
|
}
|
|
|
|
/// Decrypt the ciphertext in place if authentication tag is correct.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// - `content` - Ciphertext to be decrypted in place.
|
|
/// - `tag` - 16-byte authentication tag.
|
|
/// - `aad` - Optional metadata covered by the authentication tag.
|
|
pub fn decrypt(
|
|
self,
|
|
content: &mut [u8],
|
|
tag: [u8; 16],
|
|
aad: Option<&[u8]>,
|
|
) -> Result<(), Error> {
|
|
let chacha = ChaCha20::new_from_block(self.key, self.nonce, 0);
|
|
let keystream = chacha.get_keystream(0);
|
|
let mut poly =
|
|
Poly1305::new(keystream[..32].try_into().expect("slicing produces 32-byte slice"));
|
|
let aad = aad.unwrap_or(&[]);
|
|
poly.input(aad);
|
|
// AAD and ciphertext are padded if not 16-byte aligned.
|
|
let aad_overflow = aad.len() % 16;
|
|
if aad_overflow > 0 {
|
|
poly.input(&ZEROES[0..(16 - aad_overflow)]);
|
|
}
|
|
poly.input(content);
|
|
let msg_overflow = content.len() % 16;
|
|
if msg_overflow > 0 {
|
|
poly.input(&ZEROES[0..(16 - msg_overflow)]);
|
|
}
|
|
|
|
let len_buffer = encode_lengths(aad.len() as u64, content.len() as u64);
|
|
poly.input(&len_buffer);
|
|
let derived_tag = poly.tag();
|
|
if derived_tag == tag {
|
|
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
|
|
chacha.apply_keystream(content);
|
|
Ok(())
|
|
} else {
|
|
Err(Error::UnauthenticatedAdditionalData)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// AAD and content lengths are each encoded in 8-bytes.
|
|
fn encode_lengths(aad_len: u64, content_len: u64) -> [u8; 16] {
|
|
let aad_len_bytes = aad_len.to_le_bytes();
|
|
let content_len_bytes = content_len.to_le_bytes();
|
|
let mut len_buffer = [0u8; 16];
|
|
let (aad_len_buffer, content_len_buffer) = len_buffer.split_at_mut(8);
|
|
aad_len_buffer.copy_from_slice(&aad_len_bytes[..]);
|
|
content_len_buffer.copy_from_slice(&content_len_bytes[..]);
|
|
|
|
len_buffer
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[cfg(feature = "alloc")]
|
|
mod tests {
|
|
use alloc::vec::Vec;
|
|
|
|
use hex::prelude::*;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn rfc7539() {
|
|
let mut message = *b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
|
|
let aad = Vec::from_hex("50515253c0c1c2c3c4c5c6c7").unwrap();
|
|
let key = Key::new(
|
|
Vec::from_hex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
|
|
.unwrap()
|
|
.try_into()
|
|
.unwrap(),
|
|
);
|
|
let nonce =
|
|
Nonce::new(Vec::from_hex("070000004041424344454647").unwrap().try_into().unwrap());
|
|
let cipher = ChaCha20Poly1305::new(key, nonce);
|
|
let tag = cipher.encrypt(&mut message, Some(&aad));
|
|
|
|
let mut buffer = [0u8; 130];
|
|
buffer[..message.len()].copy_from_slice(&message);
|
|
buffer[message.len()..].copy_from_slice(&tag);
|
|
|
|
assert_eq!(&buffer.to_lower_hex_string(), "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691");
|
|
}
|
|
}
|