Expand description
§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:
[dependencies]
vitaminc-encrypt = "0.2.0-pre.1"
vitaminc-random = "0.2.0-pre.1" # For key generation§Feature flags
hlist— static heterogeneous-list encryption support (enablesvitaminc-aead’shlistfeature)
§Quick Start
use vitaminc_encrypt::{Aes256Cipher, Key};
use vitaminc_random::{SafeRand, SeedableRng, Generatable};
// Generate a key
let mut rng = SafeRand::from_entropy().expect("Failed to seed RNG");
let key = Key::random(&mut rng).expect("Failed to generate key");
// Encrypt a message
let ciphertext = vitaminc_encrypt::encrypt(&key, "secret message").expect("Failed to encrypt");
// Decrypt it back via a cipher constructed from the same key
let cipher = Aes256Cipher::new(&key).expect("Failed to create cipher");
let plaintext: String = cipher.decrypt(ciphertext).expect("Failed to decrypt");
assert_eq!(plaintext, "secret message");§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 vitaminc_encrypt::Key;
use vitaminc_random::{SafeRand, SeedableRng, Generatable};
let mut rng = SafeRand::from_entropy().expect("Failed to seed RNG");
let key = Key::random(&mut rng).expect("Failed to generate key");§Creating a Key from Bytes
use vitaminc_encrypt::Key;
let key_bytes = [0u8; 32]; // In practice, use securely generated bytes
let key = Key::from(key_bytes);§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 vitaminc_encrypt::{encrypt, Key};
use vitaminc_random::{SafeRand, SeedableRng, Generatable};
let key = Key::random(&mut SafeRand::from_entropy().expect("Failed to seed RNG")).expect("Failed to generate key");
// Encrypt a string
let ciphertext = encrypt(&key, "secret message").expect("encryption failed");
// Encrypt a collection (element-wise; each element must implement `Encrypt`)
let data = vec!["one".to_string(), "two".to_string()];
let ciphertext = encrypt(&key, data).expect("encryption failed");
// Encrypt a fixed-size array
let array = [0u8; 32];
let ciphertext = encrypt(&key, array).expect("encryption failed");§Decrypting Data
Decryption goes through Aes256Cipher: construct a cipher from the key, then call Aes256Cipher::decrypt, annotating the expected plaintext type:
use vitaminc_encrypt::{encrypt, Aes256Cipher, Key};
let key = Key::from([0u8; 32]);
let cipher = Aes256Cipher::new(&key).expect("cipher creation failed");
// Decrypt to String
let ciphertext = encrypt(&key, "secret message").expect("encryption failed");
let plaintext: String = cipher.decrypt(ciphertext).expect("decryption failed");
// Decrypt a collection
let ciphertext = encrypt(&key, vec!["one".to_string(), "two".to_string()]).expect("encryption failed");
let strings: Vec<String> = cipher.decrypt(ciphertext).expect("decryption failed");
// Decrypt to fixed-size array
let ciphertext = encrypt(&key, [0u8; 32]).expect("encryption failed");
let array: [u8; 32] = cipher.decrypt(ciphertext).expect("decryption failed");§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 vitaminc_encrypt::{Aes256Cipher, Encrypt, Key};
let key = Key::from([0u8; 32]);
let cipher = Aes256Cipher::new(&key).expect("cipher creation failed");
// Encrypt with context
let user_id = b"user_123";
let ciphertext = "secret message".encrypt_with_aad(&cipher, user_id).expect("encryption failed");
// Decrypt with the same context
let plaintext: String = cipher.decrypt_with_aad(ciphertext, user_id).expect("decryption failed");
assert_eq!(plaintext, "secret message");§AAD Must Match
Decryption will fail if the AAD doesn’t match:
use vitaminc_encrypt::{Aes256Cipher, Encrypt, Key};
let key = Key::from([0u8; 32]);
let cipher = Aes256Cipher::new(&key).expect("cipher creation failed");
// Encrypt with one context
let ciphertext = "secret".encrypt_with_aad(&cipher, b"context_1").expect("encryption failed");
// Try to decrypt with a different context - this will fail!
let result: Result<String, _> = cipher.decrypt_with_aad(ciphertext, b"context_2");
assert!(result.is_err());§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.
#![allow(deprecated)]
use vitaminc_encrypt::{Aad, Context};
let aad: Aad<'static> = Aad::empty();
let context: Context<'static> = aad;
assert!(context.is_empty());§Working with Protected Types
Vitamin C Encrypt integrates with vitaminc-protected to ensure sensitive data is handled securely:
use vitaminc_protected::Protected;
use vitaminc_encrypt::{encrypt, Aes256Cipher, Key};
let key = Key::from([0u8; 32]);
let cipher = Aes256Cipher::new(&key).expect("cipher creation failed");
// Encrypt protected data
let sensitive = Protected::new("password123".to_string());
let ciphertext = encrypt(&key, sensitive).expect("encryption failed");
// Decrypt back to protected data
let decrypted: Protected<String> = cipher.decrypt(ciphertext).expect("decryption failed");§Encrypting Keys (Key Wrapping)
Keys can be encrypted with other keys, enabling key hierarchy and key wrapping:
use vitaminc_encrypt::{encrypt, Aes256Cipher, Key};
use vitaminc_protected::Protected;
use vitaminc_random::{SafeRand, SeedableRng, Generatable};
let mut rng = SafeRand::from_entropy().expect("Failed to seed RNG");
// Generate a key encryption key (KEK)
let kek = Key::random(&mut rng).expect("key generation failed");
// Generate a data encryption key (DEK)
let dek = Key::random(&mut rng).expect("key generation failed");
// Wrap the DEK with the KEK
let wrapped_dek = encrypt(&kek, dek).expect("encryption failed");
// 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 = Aes256Cipher::new(&kek).expect("cipher creation failed");
let unwrapped: Protected<[u8; 32]> = cipher.decrypt(wrapped_dek).expect("decryption failed");§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 AADAes256Cipher::decrypt- Decrypt with no AADAes256Cipher::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 encryptableDecrypt(visitor-style, invitaminc-aead) - implement to make your types decryptableCipher- Implement to create custom cipher algorithms
Example using the traits directly:
use vitaminc_encrypt::{Aes256Cipher, Encrypt, Key};
let key = Key::from([0u8; 32]);
let cipher = Aes256Cipher::new(&key).expect("cipher creation failed");
let ciphertext = "secret".encrypt(&cipher).expect("encryption failed");
let plaintext: String = cipher.decrypt(ciphertext).expect("decryption failed");§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 vitaminc_encrypt::{Key, encrypt, Unspecified};
use vitaminc_random::{SafeRand, SeedableRng, Generatable};
let key = Key::random(&mut SafeRand::from_entropy().expect("Failed to seed RNG")).expect("Failed to generate key");
match encrypt(&key, "message") {
Ok(ciphertext) => println!("Encrypted successfully"),
Err(Unspecified) => eprintln!("Encryption failed"),
}§CipherStash
Vitamin C is brought to you by the team at CipherStash.
License: MIT
Structs§
- Aes256
Cipher - Implements AES-256-GCM. Backend is selected at compile time:
aws-lc-rson native targets,aes-gcm(RustCrypto) onwasm32. - AesDecipher
- A
Decipherover a singleAesCipherText, produced byAes256Cipher::decipher. Carries the cipher and ciphertext; the AAD is supplied per call byDecrypt::decrypt_with_aad. - Context
- The canonical encoding of a context, as bytes.
- Key
- 256-bit key type for use with symmetric encryption algorithms like AES-256-GCM. Vitaminc does not support smaller key sizes to ensure quantum security and compatibility with AWS-LC.
- Local
Cipher Text - A sealed leaf:
version(1) ‖ nonce ‖ ciphertext ‖ tag. - Nonce
- Represents a nonce used in AEAD encryption of
Nbytes length. - Unspecified
- An error that provides no information about the failure. It is crucial when returning an error from a cipher operation that does not reveal any details about the failure as this can lead to side channel attacks.
Traits§
- Cipher
- A driver of an encryption operation, analogous to serde’s
Serializer. - Decrypt
- The counterpart to
Encrypt— a type that knows how to decrypt itself using aDecipher. Analogous to serde’sDeserialize. - Encrypt
- A type that knows how to encrypt itself by driving a
Cipher. - IntoAad
- Types that can be used as the associated data of an AEAD call.
- Into
Context - Types that describe themselves as a context.
Functions§
- encrypt
- Encrypt the given plaintext using the provided key.
Any type that implements the
Encrypttrait can be used.
Type Aliases§
- Aad
Deprecated - The associated data a context is authenticated as. Kept as a name for
the transition; it is
Context. - AesCipher
Text - The recursive ciphertext container produced by
Aes256Cipher— the sharedCipherTexttree instantiated withLocalCipherTextleaves andBoxedPassthroughpassthrough values. - Boxed
Passthrough - The passthrough payload type used by
Aes256Cipher: type-erased boxed values, downcast back to a concrete type at decrypt time viaAesDecipher::decrypt_passthrough_as.
Derive Macros§
- Decrypt
- Derive
Decryptfor a struct. See the crate documentation for the wire shape this consumes; the attributes it accepts are reproduced below. - Encrypt
- Derive
Encryptfor a struct. See the crate documentation for the wire shape this produces; the attributes it accepts are reproduced below.