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.
- X25519, passphrase (scrypt), SSH (RSA, Ed25519), tag, and tagpq recipients – all core recipient types supported by the age specification.
- Multiple recipients – encrypt once and let any of the specified private keys decrypt.
- Multiple identities – provide several identities when decrypting, and the library will try each one.
- ASCII armour (PEM) – optional armoured output for text-safe storage and transmission.
- Streaming – incremental encryption and decryption for large files or network streams.
- Memory safety
- Secret keys (
KeyGenData::secret_key) are wrapped inZeroizing<String>and automatically zeroized on drop. - Ciphertext and plaintext outputs are also wrapped in
Zeroizing<Vec<u8>>.
- Secret keys (
- File helpers – read recipients and identities directly from files (ignoring comments and blank lines), including multi‑line SSH private keys.
- Clean error handling – every error includes a machine‑readable code and a human‑readable message;
LibrageErrorimplementsstd::error::Error. - Uniform JSON‑ready responses – all functions return
LibrageResponse<T>withsuccess,data, anderrorfields. Call.to_json()to obtain a JSON string. - Full test coverage and lint‑free code (
cargo fmt,cargo clippy -- -D warnings).
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.
// 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()
);
}| Function | Signature | Description |
|---|---|---|
generate_keypair |
() -> LibrageResponse<KeyGenData> |
Generates a new X25519 keypair. |
KeyGenData contains:
public_key: Stringsecret_key: Zeroizing<String>(automatically zeroized on drop)
| 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:
ciphertext: Zeroizing<Vec<u8>>armored: boolas_string() -> String– returns PEM if armoured, hex‑encoded otherwise.
DecryptOutput contains:
plaintext: Zeroizing<Vec<u8>>as_string() -> Stringas_bytes() -> &[u8]
| 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. |
| 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. |
| 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. |
| 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. |
LibrageError is an enum with the following variants:
KeyGenFailedSerializationFailedInvalidPublicKeyInvalidSecretKeyInvalidSshKeyEncryptionFailedDecryptionFailedIo(transparently fromstd::io::Error)InvalidInputArmorErrorExcessiveWork { required: u8, target: u8 }NoMatchingKeysMissingRecipientsMixedRecipientAndPassphraseUnknownFormat
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:
code: String– machine‑readable error code (e.g.,INVALID_PUBLIC_KEY)message: String– human‑readable description
Example:
let res = encrypt(b"data", "bad_key");
assert!(!res.success);
let err = res.error.unwrap();
println!("Error code: {}, message: {}", err.code, err.message);- Secret key zeroization:
KeyGenData::secret_keyis wrapped inZeroizing<String>. Once the value goes out of scope, it is overwritten with zeros. - Ciphertext/plaintext zeroization: Both
EncryptOutputandDecryptOutputwrap their byte vectors inZeroizing<Vec<u8>>. - Passphrase handling:
- Functions that accept
&str(e.g.,encrypt_with_passphrase) do not zeroize the input. Use them only when the passphrase lifetime is short or the environment is trusted. - For long‑lived or sensitive applications, consider using
age::secrecy::SecretStringexternally and then passing the passphrase as&str(the library will internally process it withSecretStringwhere possible).
- Functions that accept
- SSH private keys: When decrypting with an encrypted SSH key, the passphrase is processed through
SecretStringinternally, but the original&strprovided by the caller is not altered. Consider reading passphrases directly intoSecretStringif possible.
cargo fmt --all
cargo clippy -- -D warnings
cargo testThis project is licensed under the MIT License.
Contributions are welcome. Please ensure your code passes the checks above before submitting a pull request.