age-crypto 0.1.0

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

pub mod apis;
pub mod errors;
pub mod types;

pub use apis::decrypt;
pub use apis::decrypt_armor;
pub use apis::decrypt_with_passphrase;
pub use apis::decrypt_with_passphrase_armor;
pub use apis::encrypt;
pub use apis::encrypt_armor;
pub use apis::encrypt_with_passphrase;
pub use apis::encrypt_with_passphrase_armor;
pub use errors::Error;
pub use errors::Result;
pub use errors::decrypt::DecryptError;
pub use errors::encrypt::EncryptError;
pub use types::ArmoredData;
pub use types::EncryptedData;
pub use types::Passphrase;