| Crates.io | anubis-age |
| lib.rs | anubis-age |
| version | 1.4.0 |
| created_at | 2025-10-09 16:43:05.229966+00 |
| updated_at | 2025-10-10 07:30:04.779133+00 |
| description | Post-quantum secure encryption library with hybrid X25519+ML-KEM-1024 mode (internal dependency for anubis-rage) |
| homepage | |
| repository | https://github.com/AnubisQuantumCipher/anubis-rage |
| max_upload_size | |
| id | 1875892 |
| size | 1,962,660 |
Post-quantum secure file encryption library with hybrid X25519+ML-KEM-1024 support
age is a simple, modern, and secure file encryption library. This is the Anubis Rage edition, which extends the original age library with defense-in-depth post-quantum cryptography through hybrid mode combining X25519 and ML-KEM-1024.
This crate provides a set of Rust APIs that can be used to build tools based on the age format, with defense-in-depth post-quantum cryptography:
The primary consumer of these APIs is the anubis-rage CLI tool, which provides straightforward quantum-resistant encryption and decryption of files or streams.
The age format specification is at age-encryption.org/v1.
Anubis Rage extends this with two post-quantum recipient stanza formats:
-> hybrid [base64-x25519-epk] [base64-mlkem-ciphertext]
[base64-encoded-wrapped-file-key]
-> mlkem1024 [base64-encoded-ciphertext]
[base64-encoded-wrapped-file-key]
The age format was designed by @Benjojo and @FiloSottile.
The reference interoperable Go implementation is available at filippo.io/age.
Add this line to your Cargo.toml:
anubis-age = "2.0"
For post-quantum features, ensure you have liboqs installed:
macOS:
brew install liboqs
Ubuntu/Debian:
sudo apt-get install cmake ninja-build
git clone https://github.com/open-quantum-safe/liboqs.git
cd liboqs && mkdir build && cd build
cmake -GNinja -DCMAKE_INSTALL_PREFIX=/usr/local ..
ninja && sudo ninja install
use age::pqc::hybrid::{Identity, Recipient};
use age::{Encryptor, Decryptor};
use std::io::{Read, Write};
// Generate a new hybrid identity (X25519 + ML-KEM-1024)
let identity = Identity::generate();
let recipient = identity.to_public();
// Encrypt
let encryptor = Encryptor::with_recipients(vec![Box::new(recipient)])
.expect("we provided a recipient");
let mut encrypted = vec![];
let mut writer = encryptor.wrap_output(&mut encrypted)?;
writer.write_all(b"Secret message with defense-in-depth security")?;
writer.finish()?;
// Decrypt
let decryptor = match Decryptor::new(&encrypted[..])? {
Decryptor::Recipients(d) => d,
_ => unreachable!(),
};
let mut decrypted = vec![];
let mut reader = decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity))?;
reader.read_to_end(&mut decrypted)?;
assert_eq!(decrypted, b"Secret message with defense-in-depth security");
use age::pqc::mlkem::{Identity, Recipient};
// Generate a new ML-KEM-1024 identity (quantum-resistant only)
let identity = Identity::generate();
let recipient = identity.to_public();
// Use same Encryptor/Decryptor API as above
use age::x25519;
let identity = x25519::Identity::generate();
let recipient = identity.to_public();
// Use same Encryptor/Decryptor API as above
use age::scrypt;
let identity = scrypt::Identity::new("correct horse battery staple");
// Encrypt
let encryptor = Encryptor::with_user_passphrase(
secrecy::SecretString::new("correct horse battery staple".to_string())
);
// Decrypt using scrypt::Identity
See the documentation for complete API details and examples.
pqc-mlkem - Enables ML-KEM-1024 and hybrid mode support (enabled by default)armor - Enables the age::armor module for ASCII-armored age filesasync - Enables asynchronous APIs for encryption and decryptioncli-common - Common helper functions for building age CLI toolsssh - Enables the age::ssh module for reusing SSH key filesweb-sys - WebAssembly support for passphrase work factor calculationunstable - In-development functionality (no stability guarantees)Hybrid mode provides defense-in-depth by requiring an attacker to break BOTH:
Key Properties:
ML-KEM-1024 provides:
X25519, scrypt, and SSH support remain available for:
| Use Case | Recommended Mode | Rationale |
|---|---|---|
| Long-term data protection | Hybrid | Defense-in-depth, no single point of failure |
| High-security scenarios | Hybrid | Industry best practice |
| General file encryption | Hybrid | Future-proof with minimal overhead |
| Legacy compatibility | X25519 | Interoperability with original age |
| Passphrase-based | scrypt | Simple, password-based encryption |
| Feature | Anubis Rage v2.0 | Original rage |
|---|---|---|
| Hybrid Mode | ✅ X25519 + ML-KEM-1024 | ❌ No |
| Post-Quantum Security | ✅ ML-KEM-1024 | ❌ No |
| Defense-in-Depth | ✅ Dual-algorithm | ❌ Single algorithm |
| NIST Standardized PQC | ✅ FIPS 203 | ❌ |
| X25519 Support | ✅ Yes | ✅ Yes |
| SSH Key Support | ✅ Yes | ✅ Yes |
| Passphrase Encryption | ✅ Yes | ✅ Yes |
| File Compatibility | ✅ Full (with age v1) | ✅ Standard age |
| Quantum Resistant | ✅ With hybrid/ML-KEM | ❌ No |
use age::pqc::hybrid;
let hybrid_identity = hybrid::Identity::generate();
// Encrypt to hybrid recipient (recommended)
let recipients: Vec<Box<dyn age::Recipient>> = vec![
Box::new(hybrid_identity.to_public()),
];
let encryptor = Encryptor::with_recipients(recipients)
.expect("we provided recipients");
use age::Encryptor;
use age::pqc::hybrid;
use std::io::Write;
let recipient = hybrid::Identity::generate().to_public();
let encryptor = Encryptor::with_recipients(vec![Box::new(recipient)])?;
let output = std::fs::File::create("encrypted.age")?;
let mut writer = encryptor.wrap_output(output)?;
// Stream data in chunks
for chunk in data_chunks {
writer.write_all(chunk)?;
}
writer.finish()?;
use age::armor::ArmoredWriter;
use age::pqc::hybrid;
let recipient = hybrid::Identity::generate().to_public();
let encryptor = Encryptor::with_recipients(vec![Box::new(recipient)])?;
let output = Vec::new();
let armored = ArmoredWriter::wrap_output(output, age::armor::Format::AsciiArmor)?;
let mut writer = encryptor.wrap_output(armored)?;
writer.write_all(b"Secret data")?;
let armored_output = writer.finish()?.into_inner()?;
// armored_output contains ASCII-armored ciphertext
Files encrypted with v1.x (pure ML-KEM-1024) can still be decrypted by v2.0:
// Old v1.x files using pure ML-KEM-1024
let old_mlkem_identity = age::pqc::mlkem::Identity::from_string("mlkem1024-sk-...");
// Works fine in v2.0
let decryptor = Decryptor::new(old_file_data)?;
let reader = decryptor.decrypt(std::iter::once(&old_mlkem_identity))?;
For new encryptions, we recommend migrating to hybrid mode:
// New v2.0 hybrid mode (recommended)
let new_hybrid_identity = age::pqc::hybrid::Identity::generate();
let recipient = new_hybrid_identity.to_public();
// Encrypt with defense-in-depth
let encryptor = Encryptor::with_recipients(vec![Box::new(recipient)])?;
# Build the library
cargo build --release
# Run tests
cargo test
# Build with all features
cargo build --all-features
# Run all tests
cargo test
# Test hybrid mode specifically
cargo test hybrid
# Test with sanitizers (requires nightly)
RUSTFLAGS="-Z sanitizer=address" cargo +nightly test
See CONTRIBUTING.md for guidelines on:
Licensed under either of:
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Anubis Rage v2.0 - Defense-in-depth protection for the quantum era.