Add the ChaCha20Poly1305 AEAD crate
* The ChaCha20 stream cipher and the Poly1305 message authenticator are used in BIP324's encrypted P2P transport protocol.
This commit is contained in:
parent
da462d67ef
commit
a5f5c795f5
|
@ -179,6 +179,13 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20-poly1305"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-conservative",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.0"
|
||||
|
|
|
@ -172,6 +172,13 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20-poly1305"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-conservative",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["addresses", "base58", "bitcoin", "fuzz", "hashes", "internals", "io", "primitives", "units"]
|
||||
members = ["addresses", "base58", "bitcoin", "chacha20_poly1305", "fuzz", "hashes", "internals", "io", "primitives", "units"]
|
||||
resolver = "2"
|
||||
|
||||
[patch.crates-io.bitcoin-addresses]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "chacha20-poly1305"
|
||||
version = "0.1.0"
|
||||
license = "CC0-1.0"
|
||||
repository = "https://github.com/rust-bitcoin/rust-bitcoin/"
|
||||
description = "The ChaCha20 stream cipher and Poly1305 MAC based AEAD."
|
||||
categories = ["cryptography"]
|
||||
keywords = ["crypto", "encryption"]
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
rust-version = "1.63.0"
|
||||
exclude = ["tests", "contrib"]
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["alloc"]
|
||||
alloc = []
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { package = "hex-conservative", version = "0.2.0", default-features = false, features = ["alloc"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
@ -0,0 +1,7 @@
|
|||
# ChaCha20-Poly1305
|
||||
|
||||
An authenticated encryption with associated data (AEAD) algorithm implemented with the ChaCha20 stream cipher and the Poly1305 message authentication code (MAC).
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
This library should always compile with any combination of features on **Rust 1.63.0**.
|
|
@ -0,0 +1,14 @@
|
|||
# No shebang, this file should not be executed.
|
||||
# shellcheck disable=SC2148
|
||||
#
|
||||
# disable verify unused vars, despite the fact that they are used when sourced
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
# Test these features with "std" enabled.
|
||||
FEATURES_WITH_STD=""
|
||||
|
||||
# Test these features without "std" enabled.
|
||||
FEATURES_WITHOUT_STD="alloc"
|
||||
|
||||
# Run these examples.
|
||||
EXAMPLES=""
|
|
@ -0,0 +1,450 @@
|
|||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
//! The ChaCha20 stream cipher from RFC8439.
|
||||
|
||||
use core::ops::BitXor;
|
||||
|
||||
/// The first four words (32-bit) of the ChaCha stream cipher state are constants.
|
||||
const WORD_1: u32 = 0x61707865;
|
||||
const WORD_2: u32 = 0x3320646e;
|
||||
const WORD_3: u32 = 0x79622d32;
|
||||
const WORD_4: u32 = 0x6b206574;
|
||||
|
||||
/// The cipher's block size is 64 bytes.
|
||||
const CHACHA_BLOCKSIZE: usize = 64;
|
||||
|
||||
/// A 256-bit secret key shared by the parties communicating.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Key([u8; 32]);
|
||||
|
||||
impl Key {
|
||||
/// Create a new key.
|
||||
pub const fn new(key: [u8; 32]) -> Self {
|
||||
Key(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// A 96-bit initialization vector (IV), or nonce.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Nonce([u8; 12]);
|
||||
|
||||
impl Nonce {
|
||||
/// Create a new nonce.
|
||||
pub const fn new(nonce: [u8; 12]) -> Self {
|
||||
Nonce(nonce)
|
||||
}
|
||||
}
|
||||
|
||||
/// A SIMD-friendly structure which holds 25% of the cipher state.
|
||||
///
|
||||
/// The cipher's quarter round function is the bulk of its work
|
||||
/// and there are large performance gains to be had if the function
|
||||
/// leverages SIMD instructions on architectures which support them. Because
|
||||
/// the algorithm allows for the cipher's state to be operated on in
|
||||
/// parallel (each round only touches a quarter of the state), then theoretically
|
||||
/// the parallel SIMD instructions should be used. But sometimes the
|
||||
/// compiler needs a few hints to ensure it recognizes a "vectorizable" function.
|
||||
/// That is the goal of this type, which clearly breaks the state up into four
|
||||
/// chunks and exposes functions which align with SIMD lanes.
|
||||
///
|
||||
/// This type is attempting to be as close as possible to the experimental [`core::simd::u32x4`]
|
||||
/// which at this time is feature gated and well beyond the project's MSRV. But ideally
|
||||
/// an easy transistion can be made in the future.
|
||||
///
|
||||
/// A few SIMD relevant design choices:
|
||||
/// * Heavy use of inline functions to help the compiler recognize vectorizable sections.
|
||||
/// * For-each loops are easy for the compiler to recognize as vectorizable.
|
||||
/// * The type is a based on an array instead of tuple since the heterogeneous
|
||||
/// nature of tuples can confuse the compiler into thinking it is not vectorizable.
|
||||
/// * Memory alignment lines up with SIMD size.
|
||||
///
|
||||
/// In the future, a "blacklist" for the alignment option might be useful to
|
||||
/// disable it on architectures which definitely do not support SIMD in order to avoid
|
||||
/// needless memory inefficientcies.
|
||||
#[repr(align(16))]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
struct U32x4([u32; 4]);
|
||||
|
||||
impl U32x4 {
|
||||
#[inline(always)]
|
||||
fn wrapping_add(self, rhs: Self) -> Self {
|
||||
let mut result = [0u32; 4];
|
||||
(0..4).for_each(|i| {
|
||||
result[i] = self.0[i].wrapping_add(rhs.0[i]);
|
||||
});
|
||||
U32x4(result)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn rotate_left(self, n: u32) -> Self {
|
||||
let mut result = [0u32; 4];
|
||||
(0..4).for_each(|i| {
|
||||
result[i] = self.0[i].rotate_left(n);
|
||||
});
|
||||
U32x4(result)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn rotate_elements_left<const N: u32>(self) -> Self {
|
||||
let mut result = [0u32; 4];
|
||||
(0..4).for_each(|i| {
|
||||
result[i] = self.0[(i + N as usize) % 4];
|
||||
});
|
||||
U32x4(result)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn rotate_elements_right<const N: u32>(self) -> Self {
|
||||
let mut result = [0u32; 4];
|
||||
(0..4).for_each(|i| {
|
||||
result[i] = self.0[(i + 4 - N as usize) % 4];
|
||||
});
|
||||
U32x4(result)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn to_le_bytes(self) -> [u8; 16] {
|
||||
let mut bytes = [0u8; 16];
|
||||
(0..4).for_each(|i| {
|
||||
bytes[i * 4..(i + 1) * 4].copy_from_slice(&self.0[i].to_le_bytes());
|
||||
});
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor for U32x4 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn bitxor(self, rhs: Self) -> Self {
|
||||
let mut result = [0u32; 4];
|
||||
(0..4).for_each(|i| {
|
||||
result[i] = self.0[i] ^ rhs.0[i];
|
||||
});
|
||||
U32x4(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// The 512-bit cipher state is chunk'd up into 16 32-bit words.
|
||||
///
|
||||
/// The 16 words can be visualized as a 4x4 matrix:
|
||||
///
|
||||
/// 0 1 2 3
|
||||
/// 4 5 6 7
|
||||
/// 8 9 10 11
|
||||
/// 12 13 14 15
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
struct State {
|
||||
matrix: [U32x4; 4],
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// New prepared state.
|
||||
const fn new(key: Key, nonce: Nonce, count: u32) -> Self {
|
||||
// Hardcoding indexes to keep the function const.
|
||||
let k0 = u32::from_le_bytes([key.0[0], key.0[1], key.0[2], key.0[3]]);
|
||||
let k1 = u32::from_le_bytes([key.0[4], key.0[5], key.0[6], key.0[7]]);
|
||||
let k2 = u32::from_le_bytes([key.0[8], key.0[9], key.0[10], key.0[11]]);
|
||||
let k3 = u32::from_le_bytes([key.0[12], key.0[13], key.0[14], key.0[15]]);
|
||||
let k4 = u32::from_le_bytes([key.0[16], key.0[17], key.0[18], key.0[19]]);
|
||||
let k5 = u32::from_le_bytes([key.0[20], key.0[21], key.0[22], key.0[23]]);
|
||||
let k6 = u32::from_le_bytes([key.0[24], key.0[25], key.0[26], key.0[27]]);
|
||||
let k7 = u32::from_le_bytes([key.0[28], key.0[29], key.0[30], key.0[31]]);
|
||||
|
||||
let n0 = u32::from_le_bytes([nonce.0[0], nonce.0[1], nonce.0[2], nonce.0[3]]);
|
||||
let n1 = u32::from_le_bytes([nonce.0[4], nonce.0[5], nonce.0[6], nonce.0[7]]);
|
||||
let n2 = u32::from_le_bytes([nonce.0[8], nonce.0[9], nonce.0[10], nonce.0[11]]);
|
||||
|
||||
State {
|
||||
matrix: [
|
||||
U32x4([WORD_1, WORD_2, WORD_3, WORD_4]),
|
||||
U32x4([k0, k1, k2, k3]),
|
||||
U32x4([k4, k5, k6, k7]),
|
||||
U32x4([count, n0, n1, n2]),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Four quarter rounds performed on the entire state of the cipher in a vectorized SIMD friendly fashion.
|
||||
#[inline(always)]
|
||||
fn quarter_round(a: U32x4, b: U32x4, c: U32x4, d: U32x4) -> (U32x4, U32x4, U32x4, U32x4) {
|
||||
let a = a.wrapping_add(b);
|
||||
let d = d.bitxor(a).rotate_left(16);
|
||||
|
||||
let c = c.wrapping_add(d);
|
||||
let b = b.bitxor(c).rotate_left(12);
|
||||
|
||||
let a = a.wrapping_add(b);
|
||||
let d = d.bitxor(a).rotate_left(8);
|
||||
|
||||
let c = c.wrapping_add(d);
|
||||
let b = b.bitxor(c).rotate_left(7);
|
||||
|
||||
(a, b, c, d)
|
||||
}
|
||||
|
||||
/// Perform a round on "columns" and then "diagonals" of the state.
|
||||
///
|
||||
/// The column quarter rounds are made up of indexes: `[0,4,8,12]`, `[1,5,9,13]`, `[2,6,10,14]`, `[3,7,11,15]`.
|
||||
/// The diagonals quarter rounds are made up of indexes: `[0,5,10,15]`, `[1,6,11,12]`, `[2,7,8,13]`, `[3,4,9,14]`.
|
||||
///
|
||||
/// The underlying quarter_round function is vectorized using the
|
||||
/// u32x4 type in order to perform 4 quarter round functions at the same time.
|
||||
/// This is a little more difficult to read, but it gives the compiler
|
||||
/// a strong hint to use the performant SIMD instructions.
|
||||
#[inline(always)]
|
||||
fn double_round(state: [U32x4; 4]) -> [U32x4; 4] {
|
||||
let [mut a, mut b, mut c, mut d] = state;
|
||||
|
||||
// Column round.
|
||||
(a, b, c, d) = Self::quarter_round(a, b, c, d);
|
||||
|
||||
// Diagonal round (with rotations).
|
||||
b = b.rotate_elements_left::<1>();
|
||||
c = c.rotate_elements_left::<2>();
|
||||
d = d.rotate_elements_left::<3>();
|
||||
(a, b, c, d) = Self::quarter_round(a, b, c, d);
|
||||
// Rotate the words back into their normal positions.
|
||||
b = b.rotate_elements_right::<1>();
|
||||
c = c.rotate_elements_right::<2>();
|
||||
d = d.rotate_elements_right::<3>();
|
||||
|
||||
[a, b, c, d]
|
||||
}
|
||||
|
||||
/// Transform the state by performing the ChaCha block function.
|
||||
fn chacha_block(&mut self) {
|
||||
let mut working_state = self.matrix;
|
||||
|
||||
for _ in 0..10 {
|
||||
working_state = Self::double_round(working_state);
|
||||
}
|
||||
|
||||
// Add the working state to the original state.
|
||||
(0..4).for_each(|i| {
|
||||
self.matrix[i] = working_state[i].wrapping_add(self.matrix[i]);
|
||||
});
|
||||
}
|
||||
|
||||
/// Expose the 512-bit state as a byte stream.
|
||||
fn keystream(&self) -> [u8; 64] {
|
||||
let mut keystream = [0u8; 64];
|
||||
for i in 0..4 {
|
||||
keystream[i * 16..(i + 1) * 16].copy_from_slice(&self.matrix[i].to_le_bytes());
|
||||
}
|
||||
keystream
|
||||
}
|
||||
}
|
||||
|
||||
/// The ChaCha20 stream cipher from RFC8439.
|
||||
///
|
||||
/// The 20-round IETF version uses a 96-bit nonce and 32-bit block counter. This is the
|
||||
/// variant used in the Bitcoin ecosystem, including BIP324.
|
||||
pub struct ChaCha20 {
|
||||
/// Secret key shared by the parties communicating.
|
||||
key: Key,
|
||||
/// A key and nonce pair should only be used once.
|
||||
nonce: Nonce,
|
||||
/// Internal block index of keystream.
|
||||
block_count: u32,
|
||||
/// Interal byte offset index of the block_count.
|
||||
seek_offset_bytes: usize,
|
||||
}
|
||||
|
||||
impl ChaCha20 {
|
||||
/// Make a new instance of ChaCha20 from an index in the keystream.
|
||||
pub const fn new(key: Key, nonce: Nonce, seek: u32) -> Self {
|
||||
let block_count = seek / 64;
|
||||
let seek_offset_bytes = (seek % 64) as usize;
|
||||
ChaCha20 { key, nonce, block_count, seek_offset_bytes }
|
||||
}
|
||||
|
||||
/// Make a new instance of ChaCha20 from a block in the keystream.
|
||||
pub const fn new_from_block(key: Key, nonce: Nonce, block: u32) -> Self {
|
||||
ChaCha20 { key, nonce, block_count: block, seek_offset_bytes: 0 }
|
||||
}
|
||||
|
||||
/// Apply the keystream to a buffer.
|
||||
pub fn apply_keystream(&mut self, buffer: &mut [u8]) {
|
||||
let num_full_blocks = buffer.len() / CHACHA_BLOCKSIZE;
|
||||
for block in 0..num_full_blocks {
|
||||
let keystream =
|
||||
keystream_at_slice(self.key, self.nonce, self.block_count, self.seek_offset_bytes);
|
||||
for (buffer_byte, keystream_byte) in buffer
|
||||
[block * CHACHA_BLOCKSIZE..(block + 1) * CHACHA_BLOCKSIZE]
|
||||
.iter_mut()
|
||||
.zip(keystream.iter())
|
||||
{
|
||||
*buffer_byte ^= *keystream_byte
|
||||
}
|
||||
self.block_count += 1;
|
||||
}
|
||||
if buffer.len() % 64 > 0 {
|
||||
let keystream =
|
||||
keystream_at_slice(self.key, self.nonce, self.block_count, self.seek_offset_bytes);
|
||||
for (buffer_byte, keystream_byte) in
|
||||
buffer[num_full_blocks * CHACHA_BLOCKSIZE..].iter_mut().zip(keystream.iter())
|
||||
{
|
||||
*buffer_byte ^= *keystream_byte
|
||||
}
|
||||
self.block_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the keystream block at a specified block.
|
||||
pub fn get_keystream(&mut self, block: u32) -> [u8; 64] {
|
||||
self.block(block);
|
||||
keystream_at_slice(self.key, self.nonce, self.block_count, self.seek_offset_bytes)
|
||||
}
|
||||
|
||||
/// Update the index of the keystream to the given byte.
|
||||
pub fn seek(&mut self, seek: u32) {
|
||||
self.block_count = seek / 64;
|
||||
self.seek_offset_bytes = (seek % 64) as usize;
|
||||
}
|
||||
|
||||
/// Update the index of the keystream to a block.
|
||||
pub fn block(&mut self, block: u32) {
|
||||
self.block_count = block;
|
||||
self.seek_offset_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn keystream_at_slice(key: Key, nonce: Nonce, count: u32, seek: usize) -> [u8; 64] {
|
||||
let mut keystream: [u8; 128] = [0; 128];
|
||||
let (first_half, second_half) = keystream.split_at_mut(64);
|
||||
|
||||
let mut state = State::new(key, nonce, count);
|
||||
state.chacha_block();
|
||||
first_half.copy_from_slice(&state.keystream());
|
||||
|
||||
let mut state = State::new(key, nonce, count + 1);
|
||||
state.chacha_block();
|
||||
second_half.copy_from_slice(&state.keystream());
|
||||
|
||||
let seeked_keystream: [u8; 64] =
|
||||
keystream[seek..seek + 64].try_into().expect("slicing produces 64-byte slice");
|
||||
seeked_keystream
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "alloc")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_chacha_block() {
|
||||
let mut state = State {
|
||||
matrix: [
|
||||
U32x4([0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]),
|
||||
U32x4([0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c]),
|
||||
U32x4([0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c]),
|
||||
U32x4([0x00000001, 0x09000000, 0x4a000000, 0x00000000]),
|
||||
],
|
||||
};
|
||||
state.chacha_block();
|
||||
|
||||
let expected = [
|
||||
U32x4([0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3]),
|
||||
U32x4([0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3]),
|
||||
U32x4([0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9]),
|
||||
U32x4([0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2]),
|
||||
];
|
||||
|
||||
for (actual, expected) in state.matrix.iter().zip(expected.iter()) {
|
||||
assert_eq!(actual.0, expected.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_state() {
|
||||
let key =
|
||||
Key(Vec::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap());
|
||||
let nonce = Nonce(Vec::from_hex("000000090000004a00000000").unwrap().try_into().unwrap());
|
||||
let count = 1;
|
||||
let state = State::new(key, nonce, count);
|
||||
assert_eq!(state.matrix[1].0[0].to_be_bytes().to_lower_hex_string(), "03020100");
|
||||
assert_eq!(state.matrix[2].0[2].to_be_bytes().to_lower_hex_string(), "1b1a1918");
|
||||
assert_eq!(state.matrix[3].0[2].to_be_bytes().to_lower_hex_string(), "4a000000");
|
||||
assert_eq!(state.matrix[3].0[3].to_be_bytes().to_lower_hex_string(), "00000000");
|
||||
assert_eq!(state.matrix[3].0[0].to_be_bytes().to_lower_hex_string(), "00000001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_small_plaintext() {
|
||||
let key =
|
||||
Key(Vec::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap());
|
||||
let nonce = Nonce(Vec::from_hex("000000090000004a00000000").unwrap().try_into().unwrap());
|
||||
let count = 1;
|
||||
let mut chacha = ChaCha20::new(key, nonce, count);
|
||||
let mut binding = [8; 3];
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
let mut chacha = ChaCha20::new(key, nonce, count);
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
assert_eq!([8; 3], binding);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modulo_64() {
|
||||
let key =
|
||||
Key(Vec::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap());
|
||||
let nonce = Nonce(Vec::from_hex("000000090000004a00000000").unwrap().try_into().unwrap());
|
||||
let count = 1;
|
||||
let mut chacha = ChaCha20::new(key, nonce, count);
|
||||
let mut binding = [8; 64];
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
let mut chacha = ChaCha20::new(key, nonce, count);
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
assert_eq!([8; 64], binding);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rfc_standard() {
|
||||
let key =
|
||||
Key(Vec::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap());
|
||||
let nonce = Nonce(Vec::from_hex("000000000000004a00000000").unwrap().try_into().unwrap());
|
||||
let count = 64;
|
||||
let mut chacha = ChaCha20::new(key, nonce, count);
|
||||
let mut binding = *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 to = binding;
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
assert_eq!(binding[..], Vec::from_hex("6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d").unwrap());
|
||||
let mut chacha = ChaCha20::new(key, nonce, count);
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
let binding = *b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
|
||||
assert_eq!(binding, to);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_from_block() {
|
||||
let key =
|
||||
Key(Vec::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap());
|
||||
let nonce = Nonce(Vec::from_hex("000000000000004a00000000").unwrap().try_into().unwrap());
|
||||
let block: u32 = 1;
|
||||
let mut chacha = ChaCha20::new_from_block(key, nonce, block);
|
||||
let mut binding = *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 to = binding;
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
assert_eq!(binding[..], Vec::from_hex("6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d").unwrap());
|
||||
chacha.block(block);
|
||||
chacha.apply_keystream(&mut binding[..]);
|
||||
let binding = *b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
|
||||
assert_eq!(binding, to);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
//! Combine the ChaCha20 stream cipher with the Poly1305 message authentication code
|
||||
//! to form an authenticated encryption with additional data (AEAD) algorithm.
|
||||
|
||||
pub mod chacha20;
|
||||
pub mod poly1305;
|
||||
|
||||
use chacha20::ChaCha20;
|
||||
use poly1305::Poly1305;
|
||||
|
||||
use core::fmt;
|
||||
|
||||
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.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `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.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `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 mut 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 super::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
//! Poly1305 one-time message authenticator from RFC8439.
|
||||
//!
|
||||
//! Heavily inspired by the ["Donna"](https://github.com/floodyberry/poly1305-donna/blob/master/poly1305-donna-32.h) implementation in C
|
||||
//! and Loup Vaillant's [Poly1305 design article](https://loup-vaillant.fr/tutorials/poly1305-design).
|
||||
|
||||
/// 2^26 for the 26-bit limbs.
|
||||
const BITMASK: u32 = 0x03ffffff;
|
||||
/// Number is encoded in five 26-bit limbs.
|
||||
const CARRY: u32 = 26;
|
||||
|
||||
/// Poly1305 authenticator takes a 32-byte one-time key and a message and produces a 16-byte tag.
|
||||
///
|
||||
/// 64-bit constant time multiplication and addition implementation.
|
||||
pub struct Poly1305 {
|
||||
/// r part of the secret key.
|
||||
r: [u32; 5],
|
||||
/// s part of the secret key.
|
||||
s: [u32; 4],
|
||||
/// State used to create tag.
|
||||
acc: [u32; 5],
|
||||
/// Leftovers between adds.
|
||||
leftovers: [u8; 16],
|
||||
/// Track relevant leftover bytes.
|
||||
leftovers_len: usize,
|
||||
}
|
||||
|
||||
impl Poly1305 {
|
||||
/// Initialize authenticator with a 32-byte one-time secret key.
|
||||
pub const fn new(key: [u8; 32]) -> Self {
|
||||
// Taken from Donna. Assigns r to a 26-bit 5-limb number while simultaneously 'clamping' r.
|
||||
let r0 = u32::from_le_bytes([key[0], key[1], key[2], key[3]]) & 0x3ffffff;
|
||||
let r1 = (u32::from_le_bytes([key[3], key[4], key[5], key[6]]) >> 2) & 0x03ffff03;
|
||||
let r2 = (u32::from_le_bytes([key[6], key[7], key[8], key[9]]) >> 4) & 0x03ffc0ff;
|
||||
let r3 = (u32::from_le_bytes([key[9], key[10], key[11], key[12]]) >> 6) & 0x03f03fff;
|
||||
let r4 = (u32::from_le_bytes([key[12], key[13], key[14], key[15]]) >> 8) & 0x000fffff;
|
||||
|
||||
let s0 = u32::from_le_bytes([key[16], key[17], key[18], key[19]]);
|
||||
let s1 = u32::from_le_bytes([key[20], key[21], key[22], key[23]]);
|
||||
let s2 = u32::from_le_bytes([key[24], key[25], key[26], key[27]]);
|
||||
let s3 = u32::from_le_bytes([key[28], key[29], key[30], key[31]]);
|
||||
|
||||
Poly1305 {
|
||||
r: [r0, r1, r2, r3, r4],
|
||||
s: [s0, s1, s2, s3],
|
||||
acc: [0; 5],
|
||||
leftovers: [0; 16],
|
||||
leftovers_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add message to be authenticated, can be called multiple times before creating tag.
|
||||
pub fn input(&mut self, message: &[u8]) {
|
||||
// Process previous leftovers if the message is long enough to fill the leftovers buffer. If
|
||||
// the message is too short then it will just be added to the leftovers at the end. Now if there
|
||||
// are no leftovers, but the message can fill the buffer, it will process that buffer and
|
||||
// and process the rest of the message later on.
|
||||
let fill =
|
||||
if self.leftovers_len + message.len() >= 16 { 16 - self.leftovers_len } else { 0 };
|
||||
|
||||
if fill > 0 {
|
||||
self.leftovers[self.leftovers_len..].copy_from_slice(&message[0..fill]);
|
||||
|
||||
let msg_slice = prepare_padded_message_slice(&self.leftovers, false);
|
||||
for (i, b) in msg_slice.iter().enumerate() {
|
||||
self.acc[i] += *b;
|
||||
}
|
||||
self.r_times_a();
|
||||
self.leftovers_len = 0;
|
||||
}
|
||||
|
||||
// Remove prefix already processed in leftovers.
|
||||
let remaining_message = &message[fill..];
|
||||
|
||||
// Add message to accumulator.
|
||||
let mut i = 0;
|
||||
while i < remaining_message.len() / 16 {
|
||||
let msg_slice =
|
||||
prepare_padded_message_slice(&remaining_message[i * 16..(i + 1) * 16], false);
|
||||
for (i, b) in msg_slice.iter().enumerate() {
|
||||
self.acc[i] += *b;
|
||||
}
|
||||
self.r_times_a();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Save any leftovers.
|
||||
if remaining_message.len() % 16 > 0 {
|
||||
let message_index = remaining_message.len() - (remaining_message.len() % 16);
|
||||
let new_len = self.leftovers_len + remaining_message.len() % 16;
|
||||
self.leftovers[self.leftovers_len..new_len]
|
||||
.copy_from_slice(&remaining_message[message_index..]);
|
||||
self.leftovers_len = new_len;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate authentication tag.
|
||||
pub fn tag(mut self) -> [u8; 16] {
|
||||
// Add any remaining leftovers to accumulator.
|
||||
if self.leftovers_len > 0 {
|
||||
let msg_slice =
|
||||
prepare_padded_message_slice(&self.leftovers[..self.leftovers_len], true);
|
||||
for (i, b) in msg_slice.iter().enumerate() {
|
||||
self.acc[i] += *b;
|
||||
}
|
||||
self.r_times_a();
|
||||
self.leftovers_len = 0;
|
||||
}
|
||||
|
||||
// Carry and mask.
|
||||
for i in 1..4 {
|
||||
self.acc[i + 1] += self.acc[i] >> CARRY;
|
||||
}
|
||||
self.acc[0] += (self.acc[4] >> CARRY) * 5;
|
||||
self.acc[1] += self.acc[0] >> CARRY;
|
||||
for i in 0..self.acc.len() {
|
||||
self.acc[i] &= BITMASK;
|
||||
}
|
||||
// Reduce.
|
||||
let mut t = self.acc;
|
||||
t[0] += 5;
|
||||
t[4] = t[4].wrapping_sub(1 << CARRY);
|
||||
for i in 0..3 {
|
||||
t[i + 1] += t[i] >> CARRY;
|
||||
}
|
||||
t[4] = t[4].wrapping_add(t[3] >> CARRY);
|
||||
for t in t.iter_mut().take(4) {
|
||||
*t &= BITMASK;
|
||||
}
|
||||
// Convert acc to a 4 item array.
|
||||
let mask = (t[4] >> 31).wrapping_sub(1);
|
||||
for (i, t) in t.iter().enumerate().take(self.acc.len()) {
|
||||
self.acc[i] = t & mask | self.acc[i] & !mask;
|
||||
}
|
||||
// Voodoo from donna to convert to [u32; 4].
|
||||
let a0 = self.acc[0] | self.acc[1] << 26;
|
||||
let a1 = self.acc[1] >> 6 | self.acc[2] << 20;
|
||||
let a2 = self.acc[2] >> 12 | self.acc[3] << 14;
|
||||
let a3 = self.acc[3] >> 18 | self.acc[4] << 8;
|
||||
let a = [a0, a1, a2, a3];
|
||||
// a + s
|
||||
let mut tag: [u64; 4] = [0; 4];
|
||||
for i in 0..4 {
|
||||
tag[i] = a[i] as u64 + self.s[i] as u64;
|
||||
}
|
||||
|
||||
// Carry.
|
||||
for i in 0..3 {
|
||||
tag[i + 1] += tag[i] >> 32;
|
||||
}
|
||||
|
||||
// Return the 16 least significant bytes.
|
||||
let mut ret: [u8; 16] = [0; 16];
|
||||
for i in 0..tag.len() {
|
||||
let bytes = (tag[i] as u32).to_le_bytes();
|
||||
ret[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn r_times_a(&mut self) {
|
||||
// Multiply and reduce.
|
||||
// While this looks complicated, it is a variation of schoolbook multiplication,
|
||||
// described well in an article here: https://loup-vaillant.fr/tutorials/poly1305-design
|
||||
let mut t = [0; 5];
|
||||
for i in 0..5 {
|
||||
for (j, t) in t.iter_mut().enumerate() {
|
||||
let modulus: u64 = if i > j { 5 } else { 1 };
|
||||
let start = (5 - i) % 5;
|
||||
*t += modulus * self.r[i] as u64 * self.acc[(start + j) % 5] as u64;
|
||||
}
|
||||
}
|
||||
// Carry.
|
||||
for i in 0..4 {
|
||||
t[i + 1] += t[i] >> CARRY;
|
||||
}
|
||||
// Mask.
|
||||
for (i, t) in t.iter().enumerate().take(self.acc.len()) {
|
||||
self.acc[i] = *t as u32 & BITMASK;
|
||||
}
|
||||
// Carry and mask first limb.
|
||||
self.acc[0] += (t[4] >> CARRY) as u32 * 5;
|
||||
self.acc[1] += self.acc[0] >> CARRY;
|
||||
self.acc[0] &= BITMASK;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode 16-byte (tag sized), unless is_last flag set to true, piece of message into 5 26-bit limbs.
|
||||
fn prepare_padded_message_slice(msg: &[u8], is_last: bool) -> [u32; 5] {
|
||||
let hi_bit: u32 = if is_last { 0 } else { 1 << 24 };
|
||||
let mut fmt_msg = [0u8; 17];
|
||||
fmt_msg[..msg.len()].copy_from_slice(msg);
|
||||
// Tack on a 1-byte so messages with buncha zeroes at the end don't have the same MAC.
|
||||
fmt_msg[msg.len()] = 0x01;
|
||||
// Encode number in five 26-bit limbs.
|
||||
let m0 = u32::from_le_bytes(fmt_msg[0..4].try_into().expect("Valid subset of 32.")) & BITMASK;
|
||||
let m1 =
|
||||
u32::from_le_bytes(fmt_msg[3..7].try_into().expect("Valid subset of 32.")) >> 2 & BITMASK;
|
||||
let m2 =
|
||||
u32::from_le_bytes(fmt_msg[6..10].try_into().expect("Valid subset of 32.")) >> 4 & BITMASK;
|
||||
let m3 =
|
||||
u32::from_le_bytes(fmt_msg[9..13].try_into().expect("Valid subset of 32.")) >> 6 & BITMASK;
|
||||
let m4 =
|
||||
u32::from_le_bytes(fmt_msg[12..16].try_into().expect("Valid subset of 32.")) >> 8 | hi_bit;
|
||||
[m0, m1, m2, m3, m4]
|
||||
}
|
||||
|
||||
fn _print_acc(num: &[u32; 5]) {
|
||||
let a0 = num[0] | num[1] << 26;
|
||||
let a1 = num[1] >> 6 | num[2] << 20;
|
||||
let a2 = num[2] >> 12 | num[3] << 14;
|
||||
let a3 = num[3] >> 18 | num[4] << 8;
|
||||
let a = [a0, a1, a2, a3];
|
||||
let mut ret: [u8; 16] = [0; 16];
|
||||
for i in 0..a.len() {
|
||||
let bytes = a[i].to_le_bytes();
|
||||
ret[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
|
||||
}
|
||||
ret.reverse();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "alloc")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_rfc7539() {
|
||||
let key = Vec::from_hex("85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b")
|
||||
.unwrap()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let mut poly = Poly1305::new(key);
|
||||
let message = b"Cryptographic Forum Research Group";
|
||||
poly.input(message);
|
||||
let tag = poly.tag();
|
||||
assert_eq!("a8061dc1305136c6c22b8baf0c0127a9", tag.to_lower_hex_string());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue