Skip to main content

envvault/crypto/
encryption.rs

1//! AES-256-GCM authenticated encryption.
2//!
3//! Each call to `encrypt` generates a fresh random 12-byte nonce and
4//! prepends it to the ciphertext.  `decrypt` splits the nonce back out
5//! before decrypting.
6//!
7//! Layout of the returned byte buffer:
8//!   [ 12-byte nonce | ciphertext + 16-byte auth tag ]
9
10use aes_gcm::aead::{Aead, KeyInit, OsRng};
11use aes_gcm::{AeadCore, Aes256Gcm, Nonce};
12
13use crate::errors::{EnvVaultError, Result};
14
15/// Size of the AES-256-GCM nonce in bytes.
16const NONCE_LEN: usize = 12;
17
18/// Encrypt `plaintext` with a 32-byte `key`.
19///
20/// Returns the nonce prepended to the ciphertext (nonce || ciphertext).
21pub fn encrypt(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
22    // Build the cipher from the raw key bytes.
23    let cipher = Aes256Gcm::new_from_slice(key)
24        .map_err(|e| EnvVaultError::EncryptionFailed(format!("invalid key length: {e}")))?;
25
26    // Generate a random 12-byte nonce.
27    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
28
29    // Encrypt and authenticate the plaintext.
30    let ciphertext = cipher
31        .encrypt(&nonce, plaintext)
32        .map_err(|e| EnvVaultError::EncryptionFailed(format!("encryption error: {e}")))?;
33
34    // Prepend the nonce so the caller only needs to store one blob.
35    let mut output = Vec::with_capacity(NONCE_LEN + ciphertext.len());
36    output.extend_from_slice(&nonce);
37    output.extend_from_slice(&ciphertext);
38    Ok(output)
39}
40
41/// Decrypt data that was produced by `encrypt`.
42///
43/// Expects the first 12 bytes to be the nonce, followed by the ciphertext.
44pub fn decrypt(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>> {
45    // Make sure we have at least a nonce worth of bytes.
46    if ciphertext_with_nonce.len() < NONCE_LEN {
47        return Err(EnvVaultError::DecryptionFailed);
48    }
49
50    // Split nonce from ciphertext.
51    let (nonce_bytes, ciphertext) = ciphertext_with_nonce.split_at(NONCE_LEN);
52    let nonce = Nonce::from_slice(nonce_bytes);
53
54    // Build the cipher from the raw key bytes.
55    let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| EnvVaultError::DecryptionFailed)?;
56
57    // Decrypt and verify the auth tag.
58    let plaintext = cipher
59        .decrypt(nonce, ciphertext)
60        .map_err(|_| EnvVaultError::DecryptionFailed)?;
61
62    Ok(plaintext)
63}