cryptainer 2.3.0

tiny encrypted byte array container (Argon2id + AES256-GCM)
Documentation

cryptainer

Small, password-based authenticated encryption for Rust.

Encrypt byte slices or Serde-compatible structs with Argon2id and AES-256-GCM, then decrypt them with a simple, typed API. cryptainer is no_std-friendly and keeps file I/O optional.

Crates.io Documentation License

Features

  • Password-based encryption with Argon2id
  • AES-256-GCM authenticated encryption
  • no_std support with alloc
  • Optional derived-key zeroization
  • Pre-derived 256-bit key APIs
  • Optional file helpers through std
  • Optional bincode-backed struct encryption through derive

Quick start

Add the crate with its standard-library defaults:

[dependencies]
cryptainer = "2.3"
rand = "0.10"

Encrypt bytes

use cryptainer::{decrypt_data, encrypt_data};

let mut rng = rand::rng();
let encrypted = encrypt_data("correct horse battery staple", b"hello world", &mut rng)?;
let plaintext = decrypt_data(&encrypted, "correct horse battery staple")?;

encrypt_data returns a CryptData value containing the salt, nonce, and ciphertext. Keep it together until you are ready to persist it or decrypt it.

For applications that already manage key derivation, derive a 256-bit key once using a fresh, randomly generated salt, then use the key-based APIs. The salt is not secret, but retain it with the encrypted data if the key must later be re-derived:

use cryptainer::{decrypt_with_key, derive_key, encrypt_with_key};
use rand::Rng;

let mut rng = rand::rng();
let mut salt = [0u8; 16];
rng.fill_bytes(&mut salt);
let key = derive_key("correct horse battery staple", &salt)?;
let encrypted = encrypt_with_key(&key, &salt, b"hello world", &mut rng)?;
let plaintext = decrypt_with_key(&encrypted, &key)?;

Encrypt structs

Enable the derive feature:

[dependencies]
cryptainer = { version = "2.3", features = ["derive"] }
rand = "0.10"
serde = { version = "1", features = ["derive"] }

Then derive Cryptainer alongside Serde:

use cryptainer::Cryptainer;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Cryptainer)]
struct Secret {
    message: String,
}

let mut rng = rand::rng();
let secret = Secret { message: "hello world".into() };
let encrypted = secret.encrypt("password", &mut rng)?;
let decrypted = Secret::decrypt(&encrypted, "password")?;

The derive generates password-based and pre-derived-key methods: encrypt, decrypt, encrypt_with_key, decrypt_with_key, decrypt_bytes, and decrypt_bytes_with_key. Serialization uses bincode 2; encrypted data is still authenticated by AES-GCM.

Save and load files

use cryptainer::{decrypt_file, encrypt_data, load_file, save_file};

let mut rng = rand::rng();
let encrypted = encrypt_data("password", b"secret", &mut rng)?;
save_file("secret.stuff", &encrypted)?;
let plaintext = decrypt_file("secret.stuff", "password")?;
let encrypted_again = load_file("secret.stuff")?;

For derived structs, enable derive and use load_value to load, decrypt, and deserialize a file in one step:

let secret: Secret = cryptainer::load_value("secret.stuff", "password")?;

Format and security

Encrypted output is stored as:

salt (16 bytes) || nonce (12 bytes) || ciphertext

The ciphertext includes the AES-GCM authentication tag. Passwords are processed with Argon2id using 19,456 KiB of memory, 2 iterations, 1 lane, version 0x13, and a 32-byte output to derive a 256-bit encryption key. A fresh salt and nonce are generated for every call to encrypt_data; callers must provide a cryptographically secure RNG. Key-based encryption still requires a 16-byte salt because the salt is retained in the compatible wire format.

When enabled, the zeroize feature clears derived keys and temporary serialization buffers when they are dropped. It cannot clear passwords, caller-owned plaintext, decrypted return values, or encrypted data retained for persistence.

cryptainer does not provide password storage, password strength checking, key rotation, or secure password transport. Applications should handle those concerns separately.

API at a glance

  • encrypt_data(password, plaintext, rng) -> Result<CryptData, CryptError>
  • decrypt_data(data, password) -> Result<Vec<u8>, CryptError>
  • decrypt_bytes(data, password) -> Result<Vec<u8>, CryptError>
  • derive_key(password, salt) -> Result<KeyBuffer, CryptError>
  • encrypt_with_key(key, salt, plaintext, rng) -> Result<CryptData, CryptError>
  • decrypt_with_key(data, key) -> Result<Vec<u8>, CryptError>
  • decrypt_bytes_with_key(data, key) -> Result<Vec<u8>, CryptError>
  • encrypt_value(value, password, rng) -> Result<CryptData, CryptError> (with derive)
  • decrypt_value(data, password) -> Result<T, CryptError> (with derive)
  • encrypt_value_with_key(value, key, salt, rng) -> Result<CryptData, CryptError> (with derive)
  • decrypt_value_with_key(data, key) -> Result<T, CryptError> (with derive)
  • save_file(file_path, data) -> Result<(), CryptError> (std only)
  • load_file(file_path) -> Result<CryptData, CryptError> (std only)
  • decrypt_file(file_path, password) -> Result<Vec<u8>, CryptError> (std only)
  • decrypt_file_with_key(file_path, key) -> Result<Vec<u8>, CryptError> (std only)
  • load_value(file_path, password) -> Result<T, CryptError> (std + derive)
  • load_value_with_key(file_path, key) -> Result<T, CryptError> (std + derive)

Invalid salts, nonces, ciphertexts, malformed serialized data, authentication failures, serialization failures, and file I/O failures are returned through CryptError. Matching on this enum should include a wildcard arm so future error variants can be added without breaking downstream code.

Features

  • std (default): enables file I/O and standard library integration
  • zeroize (default): zeroizes derived keys and temporary serialized plaintext buffers where supported
  • derive: enables bincode-backed struct encryption and the Cryptainer derive

License

MIT