use chacha20poly1305::{
aead::{AeadInPlace, KeyInit},
ChaCha20Poly1305, Nonce, Tag,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AuthError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SealError;
pub fn seal(
key: &[u8; 32],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8; 32],
) -> Result<([u8; 32], [u8; 16]), SealError> {
let cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(key));
let mut ct = *plaintext;
let tag = cipher
.encrypt_in_place_detached(Nonce::from_slice(nonce), aad, &mut ct)
.map_err(|_| SealError)?;
let mut tag_bytes = [0u8; 16];
tag_bytes.copy_from_slice(&tag);
Ok((ct, tag_bytes))
}
pub fn open(
key: &[u8; 32],
nonce: &[u8; 12],
aad: &[u8],
ciphertext: &[u8; 32],
tag: &[u8; 16],
) -> Result<[u8; 32], AuthError> {
let cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(key));
let mut pt = *ciphertext;
cipher
.decrypt_in_place_detached(Nonce::from_slice(nonce), aad, &mut pt, Tag::from_slice(tag))
.map_err(|_| AuthError)?;
Ok(pt)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aead_known_answer_against_primitives() {
let 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,
];
let nonce: [u8; 12] = [
0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
];
let plaintext = [0xdeu8; 32];
let (ct, tag) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
let decrypted = open(&key, &nonce, b"", &ct, &tag).expect("round-trip must succeed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn aead_roundtrip_rfc8439_params() {
let 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,
];
let nonce: [u8; 12] = [
0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
];
let plaintext = [
0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e,
0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20,
0x63, 0x6c, 0x61, 0x73,
];
let (ciphertext, tag) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
let decrypted = open(&key, &nonce, b"", &ciphertext, &tag)
.expect("roundtrip with RFC params should succeed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn aead_known_answer_matches_rfc8439_chacha20_poly1305() {
let 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,
];
let nonce: [u8; 12] = [
0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
];
let plaintext = [
0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e,
0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20,
0x63, 0x6c, 0x61, 0x73,
];
let expected_ciphertext = [
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,
];
let expected_tag = [
0xe2, 0x8a, 0x1b, 0xe5, 0x81, 0x77, 0x79, 0xa8, 0xa5, 0xae, 0xee, 0x8c, 0x1e, 0xcf,
0xef, 0x33,
];
let (ciphertext, tag) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
assert_eq!(ciphertext, expected_ciphertext);
assert_eq!(tag, expected_tag);
}
#[test]
fn aead_roundtrip() {
let key = [0xabu8; 32];
let nonce = [0x42u8; 12];
let plaintext = [0xdeu8; 32];
let (ciphertext, tag) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
assert_ne!(ciphertext, plaintext, "encryption should change data");
let decrypted = open(&key, &nonce, b"", &ciphertext, &tag)
.expect("decryption should succeed with correct key and nonce");
assert_eq!(
decrypted, plaintext,
"roundtrip should recover original plaintext"
);
}
#[test]
fn aead_wrong_key_fails() {
let key_a = [0xabu8; 32];
let key_b = [0x42u8; 32];
let nonce = [0x01u8; 12];
let plaintext = [0xdeu8; 32];
let (ciphertext, tag) = seal(&key_a, &nonce, b"", &plaintext).expect("seal must succeed");
let result = open(&key_b, &nonce, b"", &ciphertext, &tag);
assert!(result.is_err(), "decryption with wrong key must fail");
}
#[test]
fn aead_wrong_nonce_fails() {
let key = [0xabu8; 32];
let nonce_a = [0x01u8; 12];
let nonce_b = [0x02u8; 12];
let plaintext = [0xdeu8; 32];
let (ciphertext, tag) = seal(&key, &nonce_a, b"", &plaintext).expect("seal must succeed");
let result = open(&key, &nonce_b, b"", &ciphertext, &tag);
assert!(result.is_err(), "decryption with wrong nonce must fail");
}
#[test]
fn aead_tampered_ciphertext_fails() {
let key = [0xabu8; 32];
let nonce = [0x42u8; 12];
let plaintext = [0xdeu8; 32];
let (mut ciphertext, tag) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
ciphertext[15] ^= 0x01;
let result = open(&key, &nonce, b"", &ciphertext, &tag);
assert!(
result.is_err(),
"decryption with tampered ciphertext must fail"
);
}
#[test]
fn aead_tampered_tag_fails() {
let key = [0xabu8; 32];
let nonce = [0x42u8; 12];
let plaintext = [0xdeu8; 32];
let (ciphertext, mut tag) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
tag[0] ^= 0x01;
let result = open(&key, &nonce, b"", &ciphertext, &tag);
assert!(result.is_err(), "decryption with tampered tag must fail");
}
#[test]
fn aead_same_input_same_output() {
let key = [0xabu8; 32];
let nonce = [0x42u8; 12];
let plaintext = [0xdeu8; 32];
let (ct1, tag1) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
let (ct2, tag2) = seal(&key, &nonce, b"", &plaintext).expect("seal must succeed");
assert_eq!(ct1, ct2, "same input must produce same ciphertext");
assert_eq!(tag1, tag2, "same input must produce same tag");
}
#[test]
fn aead_aad_binding_roundtrip() {
let key = [0x11u8; 32];
let nonce = [0x22u8; 12];
let plaintext = [0x33u8; 32];
let aad: &[u8] = &[0x01, 0x02, 0x03, 0x04];
let (ct, tag) = seal(&key, &nonce, aad, &plaintext).expect("seal must succeed");
let decrypted = open(&key, &nonce, aad, &ct, &tag).expect("correct AAD must verify");
assert_eq!(decrypted, plaintext);
let wrong_aad: &[u8] = &[0xFF, 0xFF, 0xFF, 0xFF];
assert!(
open(&key, &nonce, wrong_aad, &ct, &tag).is_err(),
"wrong AAD must fail"
);
assert!(
open(&key, &nonce, b"", &ct, &tag).is_err(),
"missing AAD must fail"
);
}
#[test]
fn aead_tampered_aad_fails() {
let key = [0x55u8; 32];
let nonce = [0x66u8; 12];
let plaintext = [0x77u8; 32];
let aad = [0x01u8, 0x00, 0x00, 0x00];
let (ct, tag) = seal(&key, &nonce, &aad, &plaintext).expect("seal must succeed");
let mut tampered_aad = aad;
tampered_aad[0] ^= 0x01;
assert!(
open(&key, &nonce, &tampered_aad, &ct, &tag).is_err(),
"tampered AAD must fail AEAD verification"
);
}
}