age-crypto 0.2.0

A safe, ergonomic Rust wrapper around the age encryption library with strong typing, comprehensive error handling, and passphrase support.
Documentation

age-crypto

Crates.io Documentation License

A safe, ergonomic, and pure Rust wrapper around the age encryption library. age-crypto provides a high-level, idiomatic API for encrypting and decrypting data using modern cryptographic primitives. It supports both X25519 public-key encryption and passphrase-based encryption (scrypt), with options for both binary and PEM-armored (ASCII) output formats.

Designed for seamless integration with age-setup, this crate handles the heavy lifting of encryption while ensuring type safety and secure memory handling.

Features

  • Multiple Encryption Modes:
    • Public Key (X25519): Encrypt data for specific recipients using their public keys.
    • Passphrase (Scrypt): Encrypt data using a passphrase, suitable for backups and personal use.
  • Output Formats:
    • Binary: Compact, efficient for storage and network transmission.
    • Armored (PEM): Text-safe format (Base64) suitable for email, JSON, or copy-pasting.
  • Multiple Recipients: Encrypt a single file for multiple recipients in one operation.
  • Type Safety: Strong types (EncryptedData, ArmoredData) prevent misuse of ciphertext.
  • Secure Memory: Automatic zeroization of sensitive data (passphrases) upon drop.
  • C Binding Support: Full Foreign Function Interface (FFI) for integration with C/C++ projects.

Installation

Add this to your Cargo.toml:

[dependencies]
age-crypto = "0.2" # Check crates.io for the latest version

For key generation capabilities, we recommend using the companion crate:

[dependencies]
age-setup = "0.1"

Quick Start

1. Passphrase-Based Encryption

The simplest way to encrypt data. No key management required.

use age_crypto::{encrypt_with_passphrase, decrypt_with_passphrase};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let plaintext = b"My secret data";
    let passphrase = "strong-password-123";

    // Encrypt
    let encrypted = encrypt_with_passphrase(plaintext, passphrase)?;

    // Decrypt
    let decrypted = decrypt_with_passphrase(encrypted.as_bytes(), passphrase)?;

    assert_eq!(decrypted, plaintext);
    Ok(())
}

2. Public Key Encryption (with age-setup)

Ideal for secure communication between parties.

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Generate keys (usually done once per user)
    let keypair = build_keypair()?;
    let public_key = keypair.public.expose(); // "age1..."

    // 2. Encrypt for the recipient using their public key
    let plaintext = b"Confidential message";
    let encrypted = encrypt(plaintext, &[public_key])?;

    // 3. Decrypt using the secret key
    let decrypted = decrypt(encrypted.as_bytes(), keypair.secret.expose())?;

    assert_eq!(decrypted, plaintext);
    Ok(())
}

API Reference

The library provides 8 core functions divided by encryption mode and output format.

Encryption Functions

Function Mode Output Format Description
[encrypt] Public Key Binary (Vec<u8>) Encrypts for one or more recipients. Most compact format.
[encrypt_armor] Public Key Armored (String) Encrypts for recipients, wrapped in PEM armor.
[encrypt_with_passphrase] Passphrase Binary (Vec<u8>) Encrypts using a passphrase via scrypt.
[encrypt_with_passphrase_armor] Passphrase Armored (String) Encrypts with passphrase, wrapped in PEM armor.

Decryption Functions

Function Mode Input Format Description
[decrypt] Public Key Binary Decrypts using a secret key (AGE-SECRET-KEY-1...).
[decrypt_armor] Public Key Armored Decrypts armored data using a secret key.
[decrypt_with_passphrase] Passphrase Binary Decrypts using the passphrase.
[decrypt_with_passphrase_armor] Passphrase Armored Decrypts armored data using the passphrase.

Usage Guide

Output Types

The library uses wrapper types to ensure data integrity and prevent accidental misuse.

EncryptedData

Represents raw binary encrypted data.

let data = encrypt(b"test", &[recipient])?;
let bytes: &[u8] = data.as_bytes();
let vec: Vec<u8> = data.to_vec();

ArmoredData

Represents ASCII-armored encrypted data. It implements Display to output the armored string.

let armored = encrypt_with_passphrase_armor(b"test", "pass")?;
println!("{}", armored); // Prints the full PEM block
assert!(armored.starts_with("-----BEGIN AGE ENCRYPTED FILE-----"));

Multiple Recipients

You can encrypt data for multiple recipients simultaneously. Any one of the corresponding secret keys can decrypt the file.

use age_setup::build_keypair;

let alice = build_keypair()?;
let bob = build_keypair()?;

let recipients = [alice.public.expose(), bob.public.expose()];
let encrypted = encrypt(b"Shared secret", &recipients)?;

// Bob can decrypt it
let dec = decrypt(encrypted.as_bytes(), bob.secret.expose())?;
assert_eq!(dec, b"Shared secret");

Error Handling

Errors are categorized into EncryptError and DecryptError wrapped in a top-level Error enum.

use age_crypto::{decrypt, Error, errors::DecryptError};

match decrypt(bytes, "invalid-key") {
    Ok(_) => println!("Success"),
    Err(Error::Decrypt(DecryptError::InvalidIdentity(msg))) => {
        eprintln!("The secret key format was invalid: {}", msg);
    }
    Err(Error::Decrypt(DecryptError::Failed(msg))) => {
        eprintln!("Decryption failed (wrong key or tampered data): {}", msg);
    }
    Err(e) => eprintln!("Other error: {}", e),
}

C/C++ Integration (FFI)

age-crypto provides a stable C API for use in other languages. The bindings are generated using cbindgen.

Building the Shared Library

  1. Ensure you have the cdylib crate type in your Cargo.toml.
  2. Build with cargo:
    cargo build --release
    
    The output will be located at target/release/libage_crypto.so (Linux), .dylib (macOS), or .dll (Windows).

Header File (age-crypto.h)

The header file defines the available functions. You can generate it using cbindgen or find it in the clib/ directory of the repository.

C API Reference

Memory Management

  • age_free_string(char *s): Frees a string returned by the library.
  • age_free_bytes(uint8_t *data, size_t len): Frees a byte array returned by the library.

Encryption

  • age_encrypt(...): Binary public key encryption.
  • age_encrypt_armor(...): Armored public key encryption.
  • age_encrypt_with_passphrase(...): Binary passphrase encryption.
  • age_encrypt_with_passphrase_armor(...): Armored passphrase encryption.

Decryption

  • age_decrypt(...): Binary public key decryption.
  • age_decrypt_armor(...): Armored public key decryption.
  • age_decrypt_with_passphrase(...): Binary passphrase decryption.
  • age_decrypt_with_passphrase_armor(...): Armored passphrase decryption.

C Example

#include <stdio.h>
#include <stdlib.h>
#include "age-crypto.h"

int main() {
    const char *passphrase = "my-secret-pass";
    const char *plaintext = "Hello from C!";
    size_t pt_len = 14;

    // --- Encrypt (Armored) ---
    char *armored_out = NULL;
    int res = age_encrypt_with_passphrase_armor(
        (uint8_t*)plaintext, pt_len, passphrase, &armored_out
    );

    if (res == 0 && armored_out) {
        printf("Encrypted Armored Data:\n%s\n", armored_out);

        // --- Decrypt (Armored) ---
        uint8_t *decrypted = NULL;
        size_t dec_len = 0;

        res = age_decrypt_with_passphrase_armor(armored_out, passphrase, &decrypted, &dec_len);

        if (res == 0 && decrypted) {
            printf("Decrypted: %.*s\n", (int)dec_len, decrypted);
            age_free_bytes(decrypted, dec_len);
        }

        age_free_string(armored_out);
    } else {
        printf("Encryption failed with code: %d\n", res);
    }

    return 0;
}

Security Considerations

  1. Memory Zeroization: The Passphrase type and secret keys managed by age-setup are automatically zeroized when they go out of scope, minimizing the risk of secrets remaining in memory.
  2. Nonce Management: The library automatically generates unique nonces for every encryption operation. You do not need to manage them.
  3. Passphrase Strength: For passphrase-based encryption, the security relies entirely on the strength of the passphrase. Use long, high-entropy passphrases (e.g., Diceware).
  4. Armored Data: While armored data is base64 encoded, it is not "encrypted twice". It is simply a text-safe representation of the ciphertext.

License

Licensed under either of

Contribution

Contributions are welcome! Please ensure cargo test passes and run cargo clippy to check for linting issues before submitting a pull request.