Skip to main content

age_crypto/
lib.rs

1//! # age-crypto
2//!
3//! A safe, ergonomic Rust wrapper around the [age](https://age-encryption.org) encryption
4//! library. It provides a high‑level, idiomatic API for encrypting and decrypting data
5//! using the modern age file encryption format. The crate supports both X25519 key‑based
6//! and passphrase‑based encryption, and can produce either binary or PEM‑armored output.
7//!
8//! This crate is designed to be used in conjunction with
9//! [`age‑setup`](https://crates.io/crates/age‑setup), which handles secure key pair
10//! generation, validation, and zeroized memory for secret keys. All examples in this
11//! documentation use `age‑setup` for key management.
12//!
13//! ## Features
14//!
15//! - **Key‑based encryption** using X25519 identities (`age1...` public keys).
16//! - **Passphrase‑based encryption** using scrypt key derivation.
17//! - **Armored output** (PEM‑like) for text‑safe transport.
18//! - **Authenticated encryption** (AEAD) – any tampering is detected.
19//! - **Secure memory handling** – passphrases are zeroised after use.
20//! - **No panics** – all failures are returned as `Result`.
21//! - **Newtype wrappers** ([`EncryptedData`], [`ArmoredData`]) for type safety.
22//! - **Fine‑grained error types** ([`DecryptError`], [`EncryptError`]) for precise error handling.
23//!
24//! ## Quick Start
25//!
26//! ### Passphrase‑based encryption (simplest)
27//!
28//! ```
29//! use age_crypto::{encrypt_with_passphrase, decrypt_with_passphrase};
30//!
31//! # fn main() -> age_crypto::errors::Result<()> {
32//! let plaintext = b"secret message";
33//! let passphrase = "strong‑passphrase‑here";
34//!
35//! let encrypted = encrypt_with_passphrase(plaintext, passphrase)?;
36//! let decrypted = decrypt_with_passphrase(encrypted.as_bytes(), passphrase)?;
37//! assert_eq!(decrypted, plaintext);
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! ### Key‑based encryption (using `age‑setup`)
43//!
44//! ```
45//! use age_crypto::{encrypt, decrypt};
46//! use age_setup::build_keypair;
47//!
48//! # fn main() -> age_crypto::errors::Result<()> {
49//! // Generate a key pair
50//! let keypair = build_keypair().expect("key generation failed");
51//! let public_key_str = keypair.public.expose();   // "age1..."
52//! let secret_key_str = keypair.secret.expose();   // "AGE‑SECRET‑KEY‑1..."
53//!
54//! // Encrypt with the public key
55//! let plaintext = b"sensitive data";
56//! let encrypted = encrypt(plaintext, &[public_key_str])?;
57//!
58//! // Decrypt with the secret key
59//! let decrypted = decrypt(encrypted.as_bytes(), secret_key_str)?;
60//! assert_eq!(decrypted, plaintext);
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! ## Key‑Based Encryption
66//!
67//! Key‑based encryption uses X25519 public keys (`age1...`). It is suitable for:
68//! - Secure communication between users.
69//! - Encrypting files for multiple recipients.
70//! - Systems with explicit key management.
71//!
72//! ### Single recipient example
73//!
74//! ```
75//! use age_crypto::{encrypt, decrypt};
76//! use age_setup::build_keypair;
77//!
78//! # fn main() -> age_crypto::errors::Result<()> {
79//! let recipient = build_keypair().expect("key generation failed");
80//!
81//! let data = b"production server configuration";
82//! let encrypted = encrypt(data, &[recipient.public.expose()])?;
83//!
84//! // Only the holder of the matching secret key can decrypt
85//! let decrypted = decrypt(encrypted.as_bytes(), recipient.secret.expose())?;
86//! assert_eq!(decrypted, data);
87//! # Ok(())
88//! # }
89//! ```
90//!
91//! ### Multiple recipients example
92//!
93//! ```
94//! use age_crypto::encrypt;
95//! use age_crypto::decrypt;
96//! use age_setup::build_keypair;
97//!
98//! # fn main() -> age_crypto::errors::Result<()> {
99//! let alice = build_keypair().expect("key generation failed");
100//! let bob   = build_keypair().expect("key generation failed");
101//! let carol = build_keypair().expect("key generation failed");
102//!
103//! // Encrypt once; any of the three can decrypt with their own secret key
104//! let recipients = [
105//!     alice.public.expose(),
106//!     bob.public.expose(),
107//!     carol.public.expose(),
108//! ];
109//!
110//! let secret_document = b"company secret";
111//! let encrypted = encrypt(secret_document, &recipients)?;
112//!
113//! // Alice decrypts
114//! let decrypted = decrypt(encrypted.as_bytes(), alice.secret.expose())?;
115//! assert_eq!(decrypted, secret_document);
116//!
117//! // Bob can also decrypt
118//! let decrypted = decrypt(encrypted.as_bytes(), bob.secret.expose())?;
119//! assert_eq!(decrypted, secret_document);
120//! # Ok(())
121//! # }
122//! ```
123//!
124//! ## Passphrase‑Based Encryption
125//!
126//! Passphrase‑based encryption relies on a user‑chosen secret string. It is useful for:
127//! - Encrypted backups that can be remembered by a human.
128//! - Personal files where key distribution is not practical.
129//! - Scenarios where a full key management system is overkill.
130//!
131//! ### Basic example
132//!
133//! ```
134//! use age_crypto::{encrypt_with_passphrase, decrypt_with_passphrase};
135//!
136//! # fn main() -> age_crypto::errors::Result<()> {
137//! let backup_data = b"database credentials: user=admin, pass=supersecret";
138//! let passphrase = "MyStrongPassphrase2024!";
139//!
140//! let encrypted = encrypt_with_passphrase(backup_data, passphrase)?;
141//! // Store `encrypted` on disk / in cloud ...
142//!
143//! // Later, decrypt with the same passphrase
144//! let decrypted = decrypt_with_passphrase(encrypted.as_bytes(), passphrase)?;
145//! assert_eq!(decrypted, backup_data);
146//! # Ok(())
147//! # }
148//! ```
149//!
150//! ### Passphrase strength warning
151//!
152//! Weak passphrases can be brute‑forced. Use a long, high‑entropy passphrase.
153//!
154//! ```
155//! use rand::{thread_rng, Rng};
156//!
157//! # fn main() {
158//! let words = ["correct", "horse", "battery", "staple", "mountain", "river"];
159//! let mut rng = thread_rng();
160//! let passphrase: String = (0..6)
161//!     .map(|_| words[rng.gen_range(0..words.len())])
162//!     .collect::<Vec<_>>()
163//!     .join("-");
164//! // Example output: "battery‑river‑correct‑horse‑staple‑mountain"
165//! # let _ = passphrase;
166//! # }
167//! ```
168//!
169//! ## Armored Output
170//!
171//! Armor encoding wraps the encrypted data in a PEM‑like text envelope
172//! (`-----BEGIN AGE ENCRYPTED FILE-----` / `-----END AGE ENCRYPTED FILE-----`).
173//! It makes the ciphertext safe for text‑only channels such as email, JSON,
174//! or copy‑paste operations.
175//!
176//! ### Passphrase‑based armored encryption
177//!
178//! ```
179//! use age_crypto::{encrypt_with_passphrase_armor, decrypt_with_passphrase_armor};
180//!
181//! # fn main() -> age_crypto::errors::Result<()> {
182//! let config = b"api_key=sk_live_abc123xyz";
183//! let passphrase = "deploy‑secret‑2024";
184//!
185//! let armored = encrypt_with_passphrase_armor(config, passphrase)?;
186//!
187//! // The output is a valid age armored string
188//! assert!(armored.starts_with("-----BEGIN AGE ENCRYPTED FILE-----"));
189//!
190//! // It can be written to a text file
191//! std::fs::write("config.age", armored.as_str()).expect("failed to write file");
192//!
193//! // Decryption from the armored string
194//! let loaded = std::fs::read_to_string("config.age").expect("failed to read file");
195//! let decrypted = decrypt_with_passphrase_armor(&loaded, passphrase)?;
196//! assert_eq!(decrypted, config);
197//! # Ok(())
198//! # }
199//! ```
200//!
201//! ### Binary vs Armored Comparison
202//!
203//! | Aspect               | Binary (`encrypt`)                           | Armored (`encrypt_armor`)                                           |
204//! |----------------------|----------------------------------------------|---------------------------------------------------------------------|
205//! | Size                 | Smaller (~30% less)                          | Slightly larger (base64 overhead)                                   |
206//! | Format               | `Vec<u8>` (raw bytes)                        | `String` (ASCII text)                                               |
207//! | Typical use case     | Binary files, network streams                | Configuration files, email, JSON, copy‑paste                        |
208//! | Transport safety     | Requires binary‑safe handling                | Safe for all text‑based systems                                     |
209//!
210//! ## API Reference
211//!
212//! ### Public functions
213//!
214//! | Function                             | Description                                         | Return type                |
215//! |--------------------------------------|-----------------------------------------------------|----------------------------|
216//! | [`encrypt`]                          | Binary key‑based encryption                         | `Result<EncryptedData>`    |
217//! | [`encrypt_armor`]                    | Armored key‑based encryption                        | `Result<ArmoredData>`      |
218//! | [`encrypt_with_passphrase`]          | Binary passphrase‑based encryption                  | `Result<EncryptedData>`    |
219//! | [`encrypt_with_passphrase_armor`]    | Armored passphrase‑based encryption                 | `Result<ArmoredData>`      |
220//! | [`decrypt`]                          | Binary key‑based decryption                         | `Result<Vec<u8>>`          |
221//! | [`decrypt_armor`]                    | Armored key‑based decryption                        | `Result<Vec<u8>>`          |
222//! | [`decrypt_with_passphrase`]          | Binary passphrase‑based decryption                  | `Result<Vec<u8>>`          |
223//! | [`decrypt_with_passphrase_armor`]    | Armored passphrase‑based decryption                 | `Result<Vec<u8>>`          |
224//!
225//! ### Output types
226//!
227//! #### `EncryptedData`
228//!
229//! A newtype over `Vec<u8>` representing binary age‑encrypted data. It prevents
230//! accidentally mixing plaintext and ciphertext.
231//!
232//! ```
233//! use age_crypto::{encrypt, EncryptedData};
234//! use age_setup::build_keypair;
235//!
236//! # fn main() -> age_crypto::errors::Result<()> {
237//! let keys = build_keypair().expect("key generation failed");
238//! let encrypted: EncryptedData = encrypt(b"test", &[keys.public.expose()])?;
239//!
240//! // Access as a byte slice
241//! let bytes: &[u8] = encrypted.as_bytes();
242//! // Convert to owned Vec<u8>
243//! let owned: Vec<u8> = encrypted.to_vec();
244//! # let _ = (bytes, owned);
245//! # Ok(())
246//! # }
247//! ```
248//!
249//! #### `ArmoredData`
250//!
251//! A newtype over `String` representing an armored age ciphertext. It provides
252//! built‑in format validation.
253//!
254//! ```
255//! use age_crypto::{encrypt_armor, ArmoredData};
256//! use age_setup::build_keypair;
257//!
258//! # fn main() -> age_crypto::errors::Result<()> {
259//! let keys = build_keypair().expect("key generation failed");
260//! let armored: ArmoredData = encrypt_armor(b"test", &[keys.public.expose()])?;
261//!
262//! let text: &str = armored.as_str();
263//! // Quick validation: is this a valid age armored string?
264//! assert!(ArmoredData::is_valid_armored(text));
265//! # Ok(())
266//! # }
267//! ```
268//!
269//! ## Error Handling
270//!
271//! Every function returns `age_crypto::errors::Result<T>`, an alias for
272//! `std::result::Result<T, age_crypto::errors::Error>`. The top‑level `Error` enum
273//! categorises failures into two groups:
274//!
275//! - `Error::Encrypt(`[`EncryptError`]`)` – encryption‑related failures.
276//! - `Error::Decrypt(`[`DecryptError`]`)` – decryption‑related failures.
277//!
278//! ```
279//! use age_crypto::{decrypt, Error};
280//! use age_crypto::errors::DecryptError;
281//!
282//! # fn example(ciphertext: &[u8], key: &str) {
283//! match decrypt(ciphertext, key) {
284//!     Ok(plaintext) => println!("Decryption succeeded: {} bytes", plaintext.len()),
285//!     Err(Error::Decrypt(DecryptError::InvalidIdentity(msg))) =>
286//!         eprintln!("Malformed secret key: {}", msg),
287//!     Err(Error::Decrypt(DecryptError::Failed(msg))) =>
288//!         eprintln!("Wrong key or tampered data: {}", msg),
289//!     Err(Error::Decrypt(DecryptError::InvalidCiphertext(msg))) =>
290//!         eprintln!("Not a valid age file: {}", msg),
291//!     other => eprintln!("Unexpected error: {:?}", other),
292//! }
293//! # }
294//! ```
295//!
296//! ### Error structure
297//!
298//! ```text
299//! Error
300//! +-- Encrypt(EncryptError)
301//! |   +-- NoRecipients
302//! |   +-- InvalidRecipient { recipient, reason }
303//! |   +-- Failed(String)
304//! |   +-- Io(io::Error)
305//! +-- Decrypt(DecryptError)
306//!     +-- InvalidIdentity(String)
307//!     +-- InvalidCiphertext(String)
308//!     +-- Failed(String)
309//!     +-- Io(io::Error)
310//! ```
311//!
312//! ## Security Best Practices
313//!
314//! - **Use `age‑setup` to generate key pairs** – it guarantees valid format and
315//!   securely zeroes secret key memory on drop.
316//! - **Never hard‑code or log secret keys or passphrases.** `SecretKey`'s
317//!   `Display` implementation redacts the content, but you should still avoid
318//!   printing it.
319//! - **Use strong passphrases.** For password‑based encryption, prefer long,
320//!   randomly generated passphrases (diceware style) or a password manager.
321//! - **Leverage memory zeroing.** Both `age‑setup::SecretKey` and
322//!   `age_crypto::Passphrase` are automatically zeroized on drop. For
323//!   plaintext buffers, consider using the `zeroize` crate explicitly.
324//! - **Do not reuse nonces.** The crate handles nonce generation automatically;
325//!   do not attempt to override it.
326//! - **For very large files**, consider using the lower‑level `age` streaming API
327//!   directly to avoid loading the entire plaintext into memory at once.
328//!
329//! ## Integration with `age‑setup`
330//!
331//! The companion crate [`age‑setup`](https://crates.io/crates/age‑setup) provides:
332//! - Generation of X25519 key pairs (`build_keypair()`).
333//! - Zeroizing memory for secret keys.
334//! - Safe wrappers (`PublicKey`, `SecretKey`, `KeyPair`).
335//!
336//! ### Complete workflow: generate, encrypt, decrypt
337//!
338//! ```
339//! use age_crypto::{encrypt, decrypt};
340//! use age_setup::build_keypair;
341//!
342//! # fn main() -> age_crypto::errors::Result<()> {
343//! // 1. Setup: generate a key pair for a new user
344//! let user_keys = build_keypair().expect("key generation failed");
345//! println!("Public key (share freely): {}", user_keys.public);
346//! // Store user_keys.secret securely – do not log it!
347//!
348//! // 2. Encrypt: send sensitive data to this user
349//! let sensitive = b"Q4 2024 financial report";
350//! let encrypted = encrypt(sensitive, &[user_keys.public.expose()])?;
351//!
352//! // 3. Transport: send `encrypted` over the network or save to a file
353//!
354//! // 4. Decrypt: the user decrypts with their secret key
355//! let decrypted = decrypt(encrypted.as_bytes(), user_keys.secret.expose())?;
356//! assert_eq!(decrypted, sensitive);
357//! # Ok(())
358//! # }
359//! ```
360//!
361//! ## Real‑World Examples
362//!
363//! ### Secure config loader
364//!
365//! Load an encrypted configuration file that only the application can read.
366//!
367//! ```no_run
368//! use age_crypto::decrypt_with_passphrase_armor;
369//! use serde::Deserialize;
370//! use std::env;
371//! use std::error::Error;
372//!
373//! # fn main() -> Result<(), Box<dyn Error>> {
374//! #[derive(Deserialize)]
375//! struct AppConfig {
376//!     database_url: String,
377//!     api_key: String,
378//! }
379//!
380//! fn load_config(armored_file: &str, pass_env_var: &str) -> Result<AppConfig, Box<dyn Error>> {
381//!     let armored = std::fs::read_to_string(armored_file)?;
382//!     let passphrase = env::var(pass_env_var)
383//!         .map_err(|_| format!("Set {} environment variable", pass_env_var))?;
384//!
385//!     let config_json = decrypt_with_passphrase_armor(&armored, &passphrase)
386//!         .map_err(|e| format!("Decryption failed: {}", e))?;
387//!     let config: AppConfig = serde_json::from_slice(&config_json)?;
388//!     Ok(config)
389//! }
390//! # Ok(())
391//! # }
392//! ```
393//!
394//! ### Client‑server secure message exchange
395//!
396//! A client encrypts a message with the server's public key; only the server
397//! can decrypt it.
398//!
399//! ```no_run
400//! // --- Server setup (once) ---
401//! use age_setup::build_keypair;
402//! use std::fs;
403//!
404//! fn setup_server_keys() -> Result<(), Box<dyn std::error::Error>> {
405//!     let keys = build_keypair().map_err(|e| format!("Key gen failed: {}", e))?;
406//!     fs::write("server.pub", keys.public.expose()).expect("failed to write public key");
407//!     fs::write("server.sec", keys.secret.expose()).expect("failed to write secret key");
408//!     // On Unix, restrict permissions
409//!     #[cfg(unix)]
410//!     std::process::Command::new("chmod").arg("600").arg("server.sec").status()?;
411//!     Ok(())
412//! }
413//!
414//! // --- Client side ---
415//! use age_crypto::encrypt_armor;
416//!
417//! fn send_secure_message(server_pub_key: &str, msg: &str) -> Result<String, Box<dyn std::error::Error>> {
418//!     let armored = encrypt_armor(msg.as_bytes(), &[server_pub_key])
419//!         .map_err(|e| format!("Encryption failed: {}", e))?;
420//!     Ok(armored.to_string())  // safe to send as text
421//! }
422//!
423//! // --- Server: receive and decrypt ---
424//! use age_crypto::decrypt_armor;
425//!
426//! fn receive_message(armored: &str, server_secret: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
427//!     let plaintext = decrypt_armor(armored, server_secret)
428//!         .map_err(|e| format!("Decryption failed: {}", e))?;
429//!     // Process plaintext ...
430//!     Ok(plaintext)
431//! }
432//! # fn main() {}
433//! ```
434//!
435//! ### Automated encrypted backup
436//!
437//! ```no_run
438//! use age_crypto::encrypt_with_passphrase_armor;
439//! use chrono::Local;
440//! use std::{fs, path::Path};
441//!
442//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
443//! fn backup_and_encrypt(source_dir: &str, prefix: &str, passphrase: &str) -> Result<(), Box<dyn std::error::Error>> {
444//!     let mut archive = Vec::new();
445//!     for entry in fs::read_dir(source_dir)? {
446//!         let entry = entry?;
447//!         if entry.path().extension().and_then(|s| s.to_str()) == Some("txt") {
448//!             let content = fs::read(entry.path())?;
449//!             archive.extend_from_slice(&content);
450//!             archive.extend_from_slice(b"\n---\n");
451//!         }
452//!     }
453//!
454//!     let timestamp = Local::now().format("%Y%m%d_%H%M%S");
455//!     let filename = format!("{}_backup_{}.age", prefix, timestamp);
456//!
457//!     let armored = encrypt_with_passphrase_armor(&archive, passphrase)
458//!         .map_err(|e| format!("Encryption failed: {}", e))?;
459//!     fs::write(&filename, armored.as_str())?;
460//!     println!("Encrypted backup saved to {}", filename);
461//!     Ok(())
462//! }
463//! # Ok(())
464//! # }
465//! ```
466//!
467//! ## License
468//!
469//! Licensed under either of
470//!
471//! - MIT license ([LICENSE‑MIT](LICENSE‑MIT) or <http://opensource.org/licenses/MIT>)
472//!
473//! at your option.
474//!
475//! ## Contribution
476//!
477//! Contributions are welcome. Please ensure `cargo test` and `cargo clippy` pass
478//! before submitting a pull request.
479
480pub mod apis;
481pub mod errors;
482pub mod types;
483
484pub use apis::decrypt;
485pub use apis::decrypt_armor;
486pub use apis::decrypt_with_passphrase;
487pub use apis::decrypt_with_passphrase_armor;
488pub use apis::encrypt;
489pub use apis::encrypt_armor;
490pub use apis::encrypt_with_passphrase;
491pub use apis::encrypt_with_passphrase_armor;
492pub use errors::Error;
493pub use errors::Result;
494pub use errors::decrypt::DecryptError;
495pub use errors::encrypt::EncryptError;
496pub use types::ArmoredData;
497pub use types::EncryptedData;
498pub use types::Passphrase;