lockbox/
lib.rs

1//! Lockbox
2//!
3//! This library provides encryption and decryption using the AES-GCM (Galois/Counter Mode) algorithm.
4//! It ensures data integrity and confidentiality while providing flexibility for various use cases.
5//!
6//! # Features
7//!
8//! - Simple and intuitive API for encrypting and decrypting data.
9//! - Support for customizable tags, Additional Authenticated Data (AAD), and Initialization Vectors (IV).
10//! - Secure default settings to avoid common cryptographic pitfalls.
11//! - Error handling with detailed, meaningful messages.
12
13mod tag;
14
15use crate::tag::{TagDecoder, TagEncoder};
16use aes_gcm::aead::rand_core::RngCore;
17use aes_gcm::aead::{Aead, KeyInit, OsRng, Payload};
18use aes_gcm::{Aes256Gcm, Key, Nonce};
19use thiserror::Error;
20
21#[derive(Debug, Error)]
22pub enum Error {
23    #[error("AES-GCM encrypt error")]
24    Encrypt,
25
26    #[error("AES-GCM decrypt error")]
27    Decrypt,
28
29    #[error("Unsupported version")]
30    UnsupportedVersion,
31
32    #[error("Unsupported tag")]
33    UnsupportedTag,
34
35    #[error("UTF-8 error")]
36    Utf8(#[from] std::string::FromUtf8Error),
37}
38
39/// Generates a random 256-bit (32-byte) key for AES-256 encryption.
40///
41/// # Returns
42///
43/// A `Vec<u8>` containing the generated key.
44///
45/// # Example
46///
47/// ```
48/// let key = lockbox::generate_key();
49/// println!("Generated key: {:?}", key);
50/// ```
51pub fn generate_key() -> Vec<u8> {
52    Aes256Gcm::generate_key(OsRng).to_vec()
53}
54
55/// Vault provides methods for encrypting and decrypting data using the AES-GCM algorithm.
56///
57/// This struct supports customizable tags, Initialization Vectors (IV), and Additional Authenticated Data (AAD).
58pub struct Vault {
59    cipher: Aes256Gcm,
60    tag: String,
61}
62
63impl Vault {
64    /// Creates a new `Vault` instance.
65    ///
66    /// # Arguments
67    ///
68    /// * `key` - A byte slice representing the encryption key (32 bytes for AES-256).
69    /// * `tag` - A string that represents a version or identifier for the cipher.
70    ///
71    /// # Returns
72    ///
73    /// A new `Vault` instance.
74    ///
75    /// # Example
76    ///
77    /// ```
78    /// use lockbox::Vault;
79    ///
80    /// let key = [0u8; 32]; // 256-bit key for AES-256
81    /// let vault = Vault::new(&key, "AES.GCM.V1");
82    /// ```
83    pub fn new(key: &[u8], tag: &str) -> Self {
84        Self {
85            cipher: Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key)),
86            tag: tag.to_string(),
87        }
88    }
89
90    /// Encrypts the provided plaintext.
91    ///
92    /// Generates a random 96-bit (12-byte) Initialization Vector (IV) for each encryption.
93    ///
94    /// # Arguments
95    ///
96    /// * `plaintext` - A byte slice of the data to be encrypted.
97    ///
98    /// # Returns
99    ///
100    /// A `Result` containing the encrypted data as a `Vec<u8>` or an `Error`.
101    ///
102    /// # Errors
103    ///
104    /// Returns an `Error::Encrypt` if encryption fails.
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// use lockbox::{Vault, generate_key};
110    ///
111    /// let key = generate_key();
112    /// let vault = Vault::new(&key, "AES.GCM.V1");
113    ///
114    /// let encrypted = vault.encrypt(b"Hello, world!").unwrap();
115    /// ```
116    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
117        let mut iv = [0u8; 12];
118        OsRng.fill_bytes(&mut iv);
119
120        let nonce = Nonce::from_slice(&iv);
121
122        // Additional Authenticated Data
123        // TODO: make this configurable
124        let aad = b"AES256GCM";
125
126        // Encrypt the plaintext with AAD
127        let ciphertext_with_tag = self
128            .cipher
129            .encrypt(
130                nonce,
131                Payload {
132                    msg: plaintext,
133                    aad,
134                },
135            )
136            .map_err(|_| Error::Encrypt)?;
137
138        // Split ciphertext and authentication tag
139        let (ciphertext, ciphertag) = ciphertext_with_tag.split_at(ciphertext_with_tag.len() - 16);
140
141        // Encode the tag using TagEncoder
142        let encoded_tag = TagEncoder::encode(self.tag.as_bytes());
143
144        // Concatenate Encoded Tag, IV, Ciphertag, and Ciphertext
145        let mut encoded = Vec::new();
146        encoded.extend_from_slice(&encoded_tag); // Encoded Tag
147        encoded.extend_from_slice(&iv); // 12-byte IV
148        encoded.extend_from_slice(ciphertag); // 16-byte Ciphertag
149        encoded.extend_from_slice(ciphertext); // Ciphertext
150
151        Ok(encoded)
152    }
153
154    /// Decrypts the provided ciphertext.
155    ///
156    /// # Arguments
157    ///
158    /// * `ciphertext` - A byte slice of the encrypted data.
159    ///
160    /// # Returns
161    ///
162    /// A `Result` containing the decrypted data as a `String` or an `Error`.
163    ///
164    /// # Errors
165    ///
166    /// Returns an `Error::Decrypt` if decryption fails.
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// use lockbox::{Vault, generate_key};
172    ///
173    /// let key = generate_key();
174    /// let vault = Vault::new(&key, "AES.GCM.V1");
175    ///
176    /// let encrypted = vault.encrypt(b"Hello, world!").unwrap();
177    /// let decrypted = vault.decrypt(&encrypted).unwrap();
178    /// assert_eq!(decrypted.as_bytes(), b"Hello, world!");
179    /// ```
180    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<String, Error> {
181        // Decode the tag using TagDecoder
182        let (tag, remainder) =
183            TagDecoder::decode(ciphertext).map_err(|_| Error::UnsupportedVersion)?;
184        if tag != self.tag.as_bytes() {
185            return Err(Error::UnsupportedTag);
186        }
187
188        // Extract IV, Ciphertag, and Ciphertext
189        let iv = &remainder[..12]; // Full 12-byte IV
190        let ciphertag = &remainder[12..28]; // 16-byte Ciphertag
191        let ciphertext = &remainder[28..]; // Remaining is ciphertext
192
193        // Combine ciphertext and ciphertag for decryption
194        let mut combined_ciphertext = Vec::new();
195        combined_ciphertext.extend_from_slice(ciphertext);
196        combined_ciphertext.extend_from_slice(ciphertag); // Append the tag for decryption
197
198        let nonce = Nonce::from_slice(&iv);
199        let aad = b"AES256GCM";
200
201        let plaintext = self
202            .cipher
203            .decrypt(
204                nonce,
205                Payload {
206                    msg: &combined_ciphertext,
207                    aad,
208                },
209            )
210            .map_err(|_| Error::Decrypt)?;
211
212        // Return the decrypted data as a UTF-8 string
213        Ok(String::from_utf8(plaintext)?)
214    }
215}