Skip to main content

Crate vitaminc_encrypt

Crate vitaminc_encrypt 

Source
Expand description

§Vitamin C Encrypt

Crates.io Workflow Status

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-rs on native targets, RustCrypto’s aes-gcm on wasm32. Both produce byte-identical ciphertext, so a value sealed in one environment opens cleanly in the other.
  • wasm32-unknown-unknown support: 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-protected for 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.1.0-pre4"
vitaminc-random = "0.1.0-pre4"  # For key generation

§Quick Start

use vitaminc_encrypt::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
let plaintext: String = vitaminc_encrypt::decrypt(&key, 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
  • Vec<u8>
  • [u8; N] (fixed-size byte arrays)
  • Protected<T> where T implements Encrypt
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 bytes
let data = vec![1, 2, 3, 4, 5];
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

The [decrypt] function requires you to specify the expected type:


// Decrypt to String
let ciphertext = encrypt(&key, "secret message").expect("encryption failed");
let plaintext: String = decrypt(&key, ciphertext).expect("decryption failed");

// Decrypt to Vec<u8>
let ciphertext = encrypt(&key, vec![1, 2, 3, 4, 5]).expect("encryption failed");
let bytes: Vec<u8> = decrypt(&key, ciphertext).expect("decryption failed");

// Decrypt to fixed-size array
let ciphertext = encrypt(&key, [0u8; 32]).expect("encryption failed");
let array: [u8; 32] = decrypt(&key, 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.

use vitaminc_encrypt::{encrypt_with_aad, decrypt_with_aad, Key};

let key = Key::from([0u8; 32]);

// Encrypt with context
let user_id = "user_123";
let ciphertext = encrypt_with_aad(&key, "secret message", user_id).expect("encryption failed");

// Decrypt with the same context
let plaintext: String = decrypt_with_aad(&key, 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::{encrypt_with_aad, decrypt_with_aad};

// Encrypt with one context
let ciphertext = encrypt_with_aad(&key, "secret", "context_1").expect("encryption failed");

// Try to decrypt with different context - this will fail!
let result: Result<String, _> = decrypt_with_aad(&key, ciphertext, "context_2");
assert!(result.is_err());

§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, decrypt, 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 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> = decrypt(&key, ciphertext).expect("decryption failed");

§Encrypting Keys (Key Wrapping)

Keys can be encrypted with other keys, enabling key hierarchy and key wrapping:

use vitaminc_encrypt::{Key, encrypt, decrypt};
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
let unwrapped_dek: Key = decrypt(&kek, wrapped_dek).expect("decryption failed");

§Convenience Functions vs Traits

This crate provides both convenience functions and traits:

Convenience functions (recommended for most use cases):

  • encrypt - Encrypt with no AAD
  • [encrypt_with_aad] - Encrypt with AAD
  • [decrypt] - Decrypt with no AAD
  • [decrypt_with_aad] - Decrypt with AAD

Traits (for custom implementations):

  • Encrypt - Implement to make your types encryptable
  • [Decrypt] - Implement to make your types decryptable
  • Cipher - Implement to create custom cipher algorithms

Example using traits directly:

use vitaminc_encrypt::{Encrypt, Decrypt, Aes256Cipher, 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 = String::decrypt(ciphertext, &cipher).expect("decryption failed");

§Custom Encryptable Types

You can implement Encrypt and [Decrypt] for your own types to enable selective field encryption:

use vitaminc_encrypt::{Encrypt, Decrypt, Cipher, IntoAad, Unspecified, LocalCipherText};

struct User {
    id: u64,
    email: String,
    ssn: String,
}

struct EncryptedUser {
    id: u64,
    email: String,
    ssn: LocalCipherText,  // Only encrypt the SSN
}

// TODO: Update to new Encrypt/Decrypt API

§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.

TargetBackendNotes
cfg(not(target_arch = "wasm32"))AWS-LCFIPS-validated, AES-NI accelerated. Maintained by Amazon Web Services.
cfg(target_arch = "wasm32")RustCrypto aes-gcmPure 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

  1. Generate keys securely: Always use Key::random() with a cryptographically secure RNG
  2. Protect keys in memory: The Key type uses Protected internally to zeroize keys when dropped
  3. Use AAD for context: Include relevant context (user ID, record ID, etc.) as AAD to prevent ciphertext substitution
  4. Never reuse keys across environments: Use different keys for development, staging, and production
  5. Rotate keys regularly: Implement key rotation policies for long-lived systems
  6. 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§

Aad
Associated Authenticated Data passed to an AEAD cipher.
Aes256Cipher
Implements AES-256-GCM. Backend is selected at compile time: aws-lc-rs on native targets, aes-gcm (RustCrypto) on wasm32.
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.
LocalCipherText
Nonce
Represents a nonce used in AEAD encryption of N bytes 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.

Enums§

AesCipherText
The recursive ciphertext container produced by Aes256Cipher.

Traits§

Cipher
A driver of an encryption operation, analogous to serde’s Serializer.
Encrypt
A type that knows how to encrypt itself by driving a Cipher.
IntoAad
Types that can be canonically converted into an Aad.

Functions§

encrypt
Encrypt the given plaintext using the provided key. Any type that implements the Encrypt trait can be used.