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}