age_core/
primitives.rs

1//! Primitive cryptographic operations used across various `age` components.
2
3use chacha20poly1305::{
4    aead::{self, generic_array::typenum::Unsigned, Aead, AeadCore, KeyInit},
5    ChaCha20Poly1305,
6};
7use hkdf::Hkdf;
8use sha2::Sha256;
9
10/// `encrypt[key](plaintext)` - encrypts a message with a one-time key.
11///
12/// ChaCha20-Poly1305 from [RFC 7539] with a zero nonce.
13///
14/// [RFC 7539]: https://tools.ietf.org/html/rfc7539
15pub fn aead_encrypt(key: &[u8; 32], plaintext: &[u8]) -> Vec<u8> {
16    let c = ChaCha20Poly1305::new(key.into());
17    c.encrypt(&[0; 12].into(), plaintext)
18        .expect("we won't overflow the ChaCha20 block counter")
19}
20
21/// `decrypt[key](ciphertext)` - decrypts a message of an expected fixed size.
22///
23/// ChaCha20-Poly1305 from [RFC 7539] with a zero nonce.
24///
25/// The message size is limited to mitigate multi-key attacks, where a ciphertext can be
26/// crafted that decrypts successfully under multiple keys. Short ciphertexts can only
27/// target two keys, which has limited impact.
28///
29/// [RFC 7539]: https://tools.ietf.org/html/rfc7539
30pub fn aead_decrypt(
31    key: &[u8; 32],
32    size: usize,
33    ciphertext: &[u8],
34) -> Result<Vec<u8>, aead::Error> {
35    if ciphertext.len() != size + <ChaCha20Poly1305 as AeadCore>::TagSize::to_usize() {
36        return Err(aead::Error);
37    }
38
39    let c = ChaCha20Poly1305::new(key.into());
40    c.decrypt(&[0; 12].into(), ciphertext)
41}
42
43/// `HKDF[salt, label](key, 32)`
44///
45/// HKDF from [RFC 5869] with SHA-256.
46///
47/// [RFC 5869]: https://tools.ietf.org/html/rfc5869
48pub fn hkdf(salt: &[u8], label: &[u8], ikm: &[u8]) -> [u8; 32] {
49    let mut okm = [0; 32];
50    Hkdf::<Sha256>::new(Some(salt), ikm)
51        .expand(label, &mut okm)
52        .expect("okm is the correct length");
53    okm
54}
55
56#[cfg(test)]
57mod tests {
58    use super::{aead_decrypt, aead_encrypt};
59
60    #[test]
61    fn aead_round_trip() {
62        let key = [14; 32];
63        let plaintext = b"12345678";
64        let encrypted = aead_encrypt(&key, plaintext);
65        let decrypted = aead_decrypt(&key, plaintext.len(), &encrypted).unwrap();
66        assert_eq!(decrypted, plaintext);
67    }
68}