Vitamin C Encrypt
Secure, flexible and fast encryption for Rust types.
This crate is part of the Vitamin C framework to make cryptography code healthy.
Features
- Type-safe encryption: Encrypt and decrypt Rust types with compile-time safety
- AES-256-GCM: Hardware-accelerated authenticated encryption —
aws-lc-rson native targets, RustCrypto'saes-gcmonwasm32. Both produce byte-identical ciphertext, so a value sealed in one environment opens cleanly in the other. wasm32-unknown-unknownsupport: Builds and runs in browsers, Node.js, and edge runtimes (e.g. Supabase Edge Functions, Cloudflare Workers) with no C toolchain or feature flags- 256-bit keys only: Enforces quantum-resistant key sizes for future security
- Protected types integration: Works seamlessly with
vitaminc-protectedfor sensitive data handling - Additional Authenticated Data (AAD): Support for authenticated but unencrypted data
- Automatic nonce generation: Secure random nonce generation for each encryption operation
Installation
Add this to your Cargo.toml:
[]
= "0.2.0-pre.1"
= "0.2.0-pre.1" # For key generation
Feature flags
hlist— static heterogeneous-list encryption support (enablesvitaminc-aead'shlistfeature)
Quick Start
use ;
use ;
// Generate a key
let mut rng = from_entropy.expect;
let key = random.expect;
// Encrypt a message
let ciphertext = encrypt.expect;
// Decrypt it back via a cipher constructed from the same key
let cipher = new.expect;
let plaintext: String = cipher.decrypt.expect;
assert_eq!;
Usage
Key Management
The [Key] type represents a 256-bit encryption key. Vitamin C only supports 256-bit keys to ensure quantum security and consistent behaviour across both backends.
Generating a Key
use Key;
use ;
let mut rng = from_entropy.expect;
let key = random.expect;
Creating a Key from Bytes
use Key;
let key_bytes = ; // In practice, use securely generated bytes
let key = from;
Encrypting Data
The [encrypt] function can encrypt any type that implements the [Encrypt] trait. Built-in support includes:
String&str[u8; N](fixed-size byte arrays)Vec<T>andOption<T>whereTimplementsEncryptProtected<T>whereTimplementsEncrypt
use ;
use ;
let key = random.expect;
// Encrypt a string
let ciphertext = encrypt.expect;
// Encrypt a collection (element-wise; each element must implement `Encrypt`)
let data = vec!;
let ciphertext = encrypt.expect;
// Encrypt a fixed-size array
let array = ;
let ciphertext = encrypt.expect;
Decrypting Data
Decryption goes through [Aes256Cipher]: construct a cipher from the key, then call [Aes256Cipher::decrypt], annotating the expected plaintext type:
use ;
let key = from;
let cipher = new.expect;
// Decrypt to String
let ciphertext = encrypt.expect;
let plaintext: String = cipher.decrypt.expect;
// Decrypt a collection
let ciphertext = encrypt.expect;
let strings: = cipher.decrypt.expect;
// Decrypt to fixed-size array
let ciphertext = encrypt.expect;
let array: = cipher.decrypt.expect;
Additional Authenticated Data (AAD)
AAD allows you to authenticate additional context alongside the ciphertext without encrypting it. This is useful for binding metadata to encrypted data.
AAD is supplied on the encrypt side via the [Encrypt] trait's encrypt_with_aad, and on the decrypt side via [Aes256Cipher::decrypt_with_aad]. Any [IntoAad] type works — byte slices, byte arrays, or String.
use ;
let key = from;
let cipher = new.expect;
// Encrypt with context
let user_id = b"user_123";
let ciphertext = "secret message".encrypt_with_aad.expect;
// Decrypt with the same context
let plaintext: String = cipher.decrypt_with_aad.expect;
assert_eq!;
AAD Must Match
Decryption will fail if the AAD doesn't match:
use ;
let key = from;
let cipher = new.expect;
// Encrypt with one context
let ciphertext = "secret".encrypt_with_aad.expect;
// Try to decrypt with a different context - this will fail!
let result: = cipher.decrypt_with_aad;
assert!;
Deprecated Aad alias
Aad remains available as a deprecated alias of [Context] during the
transition. Existing imports continue to compile; use Context in new code.
use ;
let aad: = empty;
let context: = aad;
assert!;
Working with Protected Types
Vitamin C Encrypt integrates with vitaminc-protected to ensure sensitive data is handled securely:
use Protected;
use ;
let key = from;
let cipher = new.expect;
// Encrypt protected data
let sensitive = new;
let ciphertext = encrypt.expect;
// Decrypt back to protected data
let decrypted: = cipher.decrypt.expect;
Encrypting Keys (Key Wrapping)
Keys can be encrypted with other keys, enabling key hierarchy and key wrapping:
use ;
use Protected;
use ;
let mut rng = from_entropy.expect;
// Generate a key encryption key (KEK)
let kek = random.expect;
// Generate a data encryption key (DEK)
let dek = random.expect;
// Wrap the DEK with the KEK
let wrapped_dek = encrypt.expect;
// Later, unwrap the DEK's protected key bytes.
// (`Key` implements `Encrypt` but not yet `Decrypt`, so decrypt to the
// protected byte array and rebuild the key from it.)
let cipher = new.expect;
let unwrapped: = cipher.decrypt.expect;
Convenience Functions vs Traits
This crate provides both convenience functions and traits:
Convenience API (recommended for most use cases):
- [
encrypt] - One-shot encrypt with no AAD - [
Aes256Cipher::decrypt] - Decrypt with no AAD - [
Aes256Cipher::decrypt_with_aad] - Decrypt with AAD
Traits (defined in vitaminc-aead):
- [
Encrypt] (re-exported here) -plaintext.encrypt(&cipher)/plaintext.encrypt_with_aad(&cipher, aad); implement to make your types encryptable Decrypt(visitor-style, invitaminc-aead) - implement to make your types decryptable- [
Cipher] - Implement to create custom cipher algorithms
Example using the traits directly:
use ;
let key = from;
let cipher = new.expect;
let ciphertext = "secret".encrypt.expect;
let plaintext: String = cipher.decrypt.expect;
Custom Encryptable Types
You can implement [Encrypt] (and vitaminc_aead::Decrypt) for your own types to enable selective field encryption — for example, encrypting only the sensitive field of a struct while leaving the rest in plaintext. The Decrypt trait is visitor-style; see the vitaminc-aead documentation for the trait definitions and the built-in implementations to model your own on.
Security Features
AES-256-GCM
This crate uses AES-256-GCM (Galois/Counter Mode) which provides:
- Confidentiality: Data is encrypted with AES-256
- Authenticity: Built-in authentication prevents tampering
- Performance: Hardware-accelerated on most modern CPUs
Cryptographic Backends
The AES-256-GCM implementation is selected at compile time based on the target architecture — there is no feature flag to choose between them, and downstream code uses the same Aes256Cipher API regardless.
| Target | Backend | Notes |
|---|---|---|
cfg(not(target_arch = "wasm32")) |
AWS-LC | FIPS-validated, AES-NI accelerated. Maintained by Amazon Web Services. |
cfg(target_arch = "wasm32") |
RustCrypto aes-gcm |
Pure Rust, no C toolchain. Required because aws-lc-rs's C deps don't cross-compile to wasm. |
Both backends conform to RFC 5116 AES-256-GCM and produce byte-identical ciphertext for the same inputs. CI gates this with a Known-Answer Test that runs against all three configurations on every PR — native aws-lc-rs, native RustCrypto, and RustCrypto compiled to wasm32-unknown-unknown and executed in Node via wasm-pack test --node.
This means ciphertext written from a native server can be opened in a browser or edge runtime (and vice versa) without any compatibility shim.
256-bit Keys Only
Vitamin C enforces 256-bit keys to ensure:
- Resistance to quantum computer attacks (via AES-256)
- Consistent parameters across both the AWS-LC and RustCrypto backends
- No risk of accidentally using weaker key sizes
Automatic Nonce Generation
Each encryption operation generates a unique random nonce, preventing nonce reuse which could compromise security.
Unspecified Errors
Encryption and decryption operations return an [Unspecified] error type that reveals no information about failures. This prevents side-channel attacks that could leak information through error messages.
Best Practices
- Generate keys securely: Always use
Key::random()with a cryptographically secure RNG - Protect keys in memory: The
Keytype usesProtectedinternally to zeroize keys when dropped - Use AAD for context: Include relevant context (user ID, record ID, etc.) as AAD to prevent ciphertext substitution
- Never reuse keys across environments: Use different keys for development, staging, and production
- Rotate keys regularly: Implement key rotation policies for long-lived systems
- Store keys securely: Use key management systems (KMS) for production deployments
Performance
Vitamin C Encrypt is designed for both security and performance:
- Zero-copy operations where possible
- Hardware acceleration via AWS-LC's AES-NI support on native targets
- Minimal allocations during encryption/decryption
On wasm32-unknown-unknown the RustCrypto backend takes over and runs as pure Rust — slower than AES-NI but still constant-time and substantially faster than a JavaScript polyfill.
Error Handling
All encryption operations return Result<T, Unspecified> where [Unspecified] is an opaque error type that reveals no details about the failure. This is intentional to prevent side-channel attacks.
use ;
use ;
let key = random.expect;
match encrypt
CipherStash
Vitamin C is brought to you by the team at CipherStash.
License: MIT