Skip to main content

Crate age_crypto

Crate age_crypto 

Source
Expand description

§age-crypto

A safe, ergonomic Rust wrapper around the age 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, 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};

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);

§Key‑based encryption (using age‑setup)

use age_crypto::{encrypt, decrypt};
use age_setup::build_keypair;

// 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);

§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;

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);

§Multiple recipients example

use age_crypto::encrypt;
use age_crypto::decrypt;
use age_setup::build_keypair;

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);

§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};

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);

§Passphrase strength warning

Weak passphrases can be brute‑forced. Use a long, high‑entropy passphrase.

use rand::{thread_rng, Rng};

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"

§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};

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);

§Binary vs Armored Comparison

AspectBinary (encrypt)Armored (encrypt_armor)
SizeSmaller (~30% less)Slightly larger (base64 overhead)
FormatVec<u8> (raw bytes)String (ASCII text)
Typical use caseBinary files, network streamsConfiguration files, email, JSON, copy‑paste
Transport safetyRequires binary‑safe handlingSafe for all text‑based systems

§API Reference

§Public functions

FunctionDescriptionReturn type
[encrypt]Binary key‑based encryptionResult<EncryptedData>
[encrypt_armor]Armored key‑based encryptionResult<ArmoredData>
[encrypt_with_passphrase]Binary passphrase‑based encryptionResult<EncryptedData>
[encrypt_with_passphrase_armor]Armored passphrase‑based encryptionResult<ArmoredData>
[decrypt]Binary key‑based decryptionResult<Vec<u8>>
[decrypt_armor]Armored key‑based decryptionResult<Vec<u8>>
[decrypt_with_passphrase]Binary passphrase‑based decryptionResult<Vec<u8>>
[decrypt_with_passphrase_armor]Armored passphrase‑based decryptionResult<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;

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();
§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;

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));

§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;

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

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 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;

// 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);

§Real‑World Examples

§Secure config loader

Load an encrypted configuration file that only the application can read.

use age_crypto::decrypt_with_passphrase_armor;
use serde::Deserialize;
use std::env;
use std::error::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)
}

§Client‑server secure message exchange

A client encrypts a message with the server’s public key; only the server can decrypt it.

// --- 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)
}

§Automated encrypted backup

use age_crypto::encrypt_with_passphrase_armor;
use chrono::Local;
use std::{fs, path::Path};

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(())
}

§License

Licensed under either of

at your option.

§Contribution

Contributions are welcome. Please ensure cargo test and cargo clippy pass before submitting a pull request.

Re-exports§

pub use apis::decrypt;
pub use apis::decrypt;
pub use apis::decrypt_armor;
pub use apis::decrypt_armor;
pub use apis::decrypt_with_passphrase;
pub use apis::decrypt_with_passphrase;
pub use apis::decrypt_with_passphrase_armor;
pub use apis::decrypt_with_passphrase_armor;
pub use apis::encrypt;
pub use apis::encrypt;
pub use apis::encrypt_armor;
pub use apis::encrypt_armor;
pub use apis::encrypt_with_passphrase;
pub use apis::encrypt_with_passphrase;
pub use apis::encrypt_with_passphrase_armor;
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;

Modules§

apis
Public API – Encryption & Decryption functions
errors
Error handling for the age_crypto library
types
Data types for encrypted and armored outputs, and passphrases