Skip to main content

joy_crypt/
aead.rs

1//! AES-256-GCM authenticated encryption with associated data.
2//!
3//! `seal`/`open` are the low-level primitives that take an explicit nonce
4//! and AAD. Callers that just want to wrap a key under a KEK without
5//! managing nonces should use `wrap` instead.
6
7use aes_gcm::aead::{Aead, KeyInit, Payload};
8use aes_gcm::{Aes256Gcm, Key, Nonce};
9
10use crate::Error;
11
12/// AES-256-GCM seal. Produces ciphertext || 16-byte auth tag.
13pub fn seal(
14    key: &[u8; 32],
15    nonce: &[u8; 12],
16    aad: &[u8],
17    plaintext: &[u8],
18) -> Result<Vec<u8>, Error> {
19    let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
20    cipher
21        .encrypt(
22            Nonce::from_slice(nonce),
23            Payload {
24                msg: plaintext,
25                aad,
26            },
27        )
28        .map_err(|_| Error::Aead)
29}
30
31/// AES-256-GCM open. Expects ciphertext || 16-byte auth tag.
32pub fn open(
33    key: &[u8; 32],
34    nonce: &[u8; 12],
35    aad: &[u8],
36    ciphertext: &[u8],
37) -> Result<Vec<u8>, Error> {
38    let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
39    cipher
40        .decrypt(
41            Nonce::from_slice(nonce),
42            Payload {
43                msg: ciphertext,
44                aad,
45            },
46        )
47        .map_err(|_| Error::Aead)
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    fn key() -> [u8; 32] {
55        [9u8; 32]
56    }
57    fn nonce() -> [u8; 12] {
58        [3u8; 12]
59    }
60
61    #[test]
62    fn roundtrip() {
63        let k = key();
64        let n = nonce();
65        let ct = seal(&k, &n, b"context", b"plaintext").unwrap();
66        let pt = open(&k, &n, b"context", &ct).unwrap();
67        assert_eq!(pt, b"plaintext");
68    }
69
70    #[test]
71    fn empty_plaintext_roundtrips() {
72        let k = key();
73        let n = nonce();
74        let ct = seal(&k, &n, b"", b"").unwrap();
75        let pt = open(&k, &n, b"", &ct).unwrap();
76        assert!(pt.is_empty());
77    }
78
79    #[test]
80    fn tampered_ciphertext_rejected() {
81        let k = key();
82        let n = nonce();
83        let mut ct = seal(&k, &n, b"", b"plaintext").unwrap();
84        ct[0] ^= 0x01;
85        assert!(matches!(open(&k, &n, b"", &ct).unwrap_err(), Error::Aead));
86    }
87
88    #[test]
89    fn wrong_aad_rejected() {
90        let k = key();
91        let n = nonce();
92        let ct = seal(&k, &n, b"context-a", b"plaintext").unwrap();
93        assert!(matches!(
94            open(&k, &n, b"context-b", &ct).unwrap_err(),
95            Error::Aead
96        ));
97    }
98
99    #[test]
100    fn wrong_key_rejected() {
101        let n = nonce();
102        let ct = seal(&[1u8; 32], &n, b"", b"plaintext").unwrap();
103        assert!(matches!(
104            open(&[2u8; 32], &n, b"", &ct).unwrap_err(),
105            Error::Aead
106        ));
107    }
108}