# 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.
[](https://crates.io/crates/cryptainer)
[](https://docs.rs/cryptainer)
[](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:
```toml
[dependencies]
cryptainer = "2.3"
rand = "0.10"
```
### Encrypt bytes
```rust
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:
```rust
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:
```toml
[dependencies]
cryptainer = { version = "2.3", features = ["derive"] }
rand = "0.10"
serde = { version = "1", features = ["derive"] }
```
Then derive `Cryptainer` alongside Serde:
```rust
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
```rust
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:
```rust
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