librage

A robust, idiomatic Rust wrapper around the rage encryption library (the Rust implementation of age).
librage exposes a safe, simple, and consistent API for encrypting and decrypting data using X25519 keys, scrypt passphrases, SSH keys, tag recipients, and more. Every public function returns a uniform LibrageResponse<T> that can be easily serialized to JSON.

Features

Installation

Add the following to your Cargo.toml:

[dependencies]
librage = { git = "https://github.com/mroczect/librage.git" }

Required dependencies (age, serde, serde_json, thiserror, zeroize, hex) are pulled in automatically.

Quick Start

// examples/simple_tests.rs
use librage::*;

fn main() {
    let res = generate_keypair();
    assert!(res.success);
    let kp = res.data.unwrap();
    println!("Public key : {}", kp.public_key);

    let plaintext = b"Hello, world!";
    let enc = encrypt(plaintext, &kp.public_key);
    assert!(enc.success);
    let dec = decrypt(&enc.data.unwrap().ciphertext, &kp.secret_key);
    assert!(dec.success);
    println!(
        "Decrypted X25519 : {}",
        dec.data.unwrap().as_string()
    );

    let enc = encrypt_with_passphrase(b"secret data", "my password");
    assert!(enc.success);
    let dec = decrypt_with_passphrase(&enc.data.unwrap().ciphertext, "my password");
    assert!(dec.success);
    println!(
        "Decrypted passphrase : {}",
        dec.data.unwrap().as_string()
    );

    let ssh_pub = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7iefA+0Lc9U8bU7NKov97K+I3b5DODVCow//16cpcPpBmUCCeP6f2x1lU+v30irOAy3RQ8BzZG8asG6hK95EI84RzxBPtaR5GpxRDTE3upGxZxa5ZWhjo51DLyoPduYHc35SFtDx9VtMmdk0Rc8o0CvTtsunt8Xg6ZZSoAK1C3PYS8WbkcZRUtZ88lezQ4SyYD2o+0YRWvd9ws8MGNbXAG7NOiPJA2eJDjAqBXSpDqf8t+7ULY17fjeJ9FWy7E8lgR5VbBB6zpv1UEXuG38zcVjUOEreKciKmJ+gRcay+W9sX8iR7CZKXBk6YFO1v4s3tWO1NKydl6LNa49C9AnuoXna80sIDs8Pf8A+Yn7i8I/5LnSuRtIQn5hAAsUkJxkvzAU+jbWyJbNgRJTvL98iJ2FoGVglGMLu6BhAaGCIMrRhl9KeKMOlwufSs+6j0dnyvKGa0J4L4jvyTH8pCGt0bY4zEHUIU6ZpHq22vYP3kP8qRyW8eCVceV4CCMCMZ+Uc=";
    let ssh_private_key = include_str!("../tests/common/test_rsa.asc");

    let enc = encrypt_with_ssh(b"data", ssh_pub);
    assert!(enc.success);
    let dec = decrypt_with_ssh(&enc.data.unwrap().ciphertext, ssh_private_key, None);
    assert!(dec.success);
    println!(
        "Decrypted SSH : {}",
        dec.data.unwrap().as_string()
    );
}

API Reference

Key Generation

Function Signature Description
generate_keypair () -> LibrageResponse<KeyGenData> Generates a new X25519 keypair.

KeyGenData contains:

X25519 Encryption / Decryption

Function Signature Description
encrypt (plaintext: &[u8], public_key: &str) -> LibrageResponse<EncryptOutput> Encrypts data to a single X25519 public key.
encrypt_armored (plaintext: &[u8], public_key: &str) -> LibrageResponse<EncryptOutput> Encrypts and returns ASCII‑armoured output (EncryptOutput.armored = true).
encrypt_multiple (plaintext: &[u8], public_keys: &[&str]) -> LibrageResponse<EncryptOutput> Encrypts to multiple X25519 public keys.
encrypt_multiple_armored (plaintext: &[u8], public_keys: &[&str]) -> LibrageResponse<EncryptOutput> Multiple recipients with armoured output.
encrypt_stream (reader: R, writer: W, public_key: &str) -> LibrageResponse<()> Streaming encryption to one recipient.
encrypt_stream_armored (reader: R, writer: W, public_key: &str) -> LibrageResponse<()> Streaming encryption with armoured output.
decrypt (ciphertext: &[u8], secret_key: &str) -> LibrageResponse<DecryptOutput> Decrypts with an X25519 secret key (auto‑detects armour).
decrypt_armored (ciphertext: &[u8], secret_key: &str) -> LibrageResponse<DecryptOutput> Alias for decrypt.
decrypt_with_identities (ciphertext: &[u8], identities: &[Box<dyn age::Identity>]) -> LibrageResponse<DecryptOutput> Decrypts using multiple identities (mix of X25519 and SSH).
decrypt_stream (reader: R, writer: W, secret_key: &str) -> LibrageResponse<()> Streaming decryption with one identity.
decrypt_stream_with_identities (reader: R, writer: W, identities: &[Box<dyn age::Identity>]) -> LibrageResponse<()> Streaming decryption with multiple identities.

EncryptOutput contains:

DecryptOutput contains:

Passphrase (scrypt)

Function Signature Description
encrypt_with_passphrase (plaintext: &[u8], passphrase: &str) -> LibrageResponse<EncryptOutput> Encrypts with a passphrase. Note: the &str is not zeroized.
decrypt_with_passphrase (ciphertext: &[u8], passphrase: &str) -> LibrageResponse<DecryptOutput> Decrypts with a passphrase. The &str is not zeroized.

SSH Keys

Function Signature Description
encrypt_with_ssh (plaintext: &[u8], ssh_public_key: &str) -> LibrageResponse<EncryptOutput> Encrypts to an SSH public key (RSA or Ed25519).
decrypt_with_ssh (ciphertext: &[u8], ssh_private_key: &str, passphrase: Option<&str>) -> LibrageResponse<DecryptOutput> Decrypts with an SSH private key. Supports encrypted keys via passphrase.

Tag & TagPQ Recipients

Function Signature Description
encrypt_with_tag (plaintext: &[u8], tag: &str) -> LibrageResponse<EncryptOutput> Encrypts to a tag recipient.
encrypt_with_tagpq (plaintext: &[u8], tagpq: &str) -> LibrageResponse<EncryptOutput> Encrypts to a tagpq recipient.

File Utilities

Function Signature Description
read_recipients_from_file (path: impl AsRef<Path>) -> Result<Vec<String>, LibrageError> Reads recipients (one per line, # comments allowed).
read_identities_from_file (path: impl AsRef<Path>) -> Result<Vec<Box<dyn age::Identity>>, LibrageError> Reads unencrypted identities (age and SSH) from a file.
read_identities_from_file_with_passphrase (path, passphrase: Option<&str>) -> Result<Vec<Box<dyn age::Identity>>, LibrageError> Reads identities, supporting encrypted SSH keys with the given passphrase.
read_identity_file (path: impl AsRef<Path>) -> Result<Vec<Box<dyn age::Identity>>, LibrageError> Alias for read_identities_from_file.
read_identity_file_with_passphrase (path, passphrase: Option<&str>) -> Result<Vec<Box<dyn age::Identity>>, LibrageError> Alias for read_identities_from_file_with_passphrase.

Error Handling

LibrageError is an enum with the following variants:

LibrageError implements std::error::Error and From<age::EncryptError>, From<age::DecryptError>, From<age::IdentityFileConvertError>.
It also implements Into<ErrorBody> for automatic conversion to a structured error.

ErrorBody contains:

Example:

let res = encrypt(b"data", "bad_key");
assert!(!res.success);
let err = res.error.unwrap();
println!("Error code: {}, message: {}", err.code, err.message);

Security Notes

Running Tests

cargo fmt --all
cargo clippy -- -D warnings
cargo test

License

This project is licensed under the MIT License.

Contributing

Contributions are welcome. Please ensure your code passes the checks above before submitting a pull request.