Documentation Index Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live Run AES-GCM examples in the interactive playground
Overview
AES-GCM is an authenticated encryption algorithm combining AES (Advanced Encryption Standard) in Galois/Counter Mode, providing both confidentiality and authenticity with a single key.
Ethereum context : Not on Ethereum - Used for encrypted wallet storage (e.g., UTC/JSON keystore format) and secure messaging. Not part of Ethereum protocol.
Key features:
Authenticated encryption : Confidentiality + integrity in one operation
Performance : Hardware-accelerated on modern CPUs
Parallelizable : Can encrypt/decrypt blocks in parallel
Additional data : Authenticate without encrypting (AAD)
Standards-compliant : NIST approved, widely used
Key sizes : 128, 192, or 256 bits
Implementations : Native Zig (16KB), NO WASM (not in browser crypto standard libs)
Quick Start
import * as AesGcm from '@tevm/voltaire/AesGcm' ;
// 1. Generate key (256-bit recommended)
const key = await AesGcm . generateKey ( 256 );
// 2. Generate nonce (12 bytes)
const nonce = AesGcm . generateNonce ();
// 3. Encrypt data
const plaintext = new TextEncoder (). encode ( 'Secret message' );
const ciphertext = await AesGcm . encrypt ( plaintext , key , nonce );
// 4. Decrypt data
const decrypted = await AesGcm . decrypt ( ciphertext , key , nonce );
const message = new TextDecoder (). decode ( decrypted );
console . log ( message ); // "Secret message"
// 5. With additional authenticated data (AAD)
const aad = new TextEncoder (). encode ( 'metadata' );
const ciphertextWithAAD = await AesGcm . encrypt ( plaintext , key , nonce , aad );
const decryptedWithAAD = await AesGcm . decrypt ( ciphertextWithAAD , key , nonce , aad );
API Reference
Key Management
generateKey(bits: 128 | 256): Promise<CryptoKey>
Generates a cryptographically secure AES key.
Parameters:
bits - Key size (128 or 256 bits)
128-bit: Faster, still very secure
256-bit: Maximum security, recommended for sensitive data
// AES-256 (recommended)
const key256 = await AesGcm . generateKey ( 256 );
// AES-128 (faster)
const key128 = await AesGcm . generateKey ( 128 );
deriveKey(password: string | Uint8Array, salt: Uint8Array, iterations: number, bits: 128 | 256): Promise<CryptoKey>
Derives key from password using PBKDF2-HMAC-SHA256.
Parameters:
password - User password (string or bytes)
salt - Salt for key derivation (≥16 bytes recommended)
iterations - PBKDF2 iterations (≥100,000 recommended)
bits - Key size (128 or 256)
// Generate salt (store with encrypted data)
import * as Hex from '@tevm/voltaire/Hex' ;
const salt = Hex ( '0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d' );
// Derive key from password
const key = await AesGcm . deriveKey (
'user-password' ,
salt ,
100000 , // Iterations (adjust for security/performance)
256 // 256-bit key
);
// Use derived key for encryption
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( plaintext , key , nonce );
importKey(keyData: Uint8Array): Promise<CryptoKey>
Imports raw key bytes as CryptoKey.
import * as Hex from '@tevm/voltaire/Hex' ;
// Import 32-byte key (256-bit)
const rawKey = Hex ( '0xf8e9a72d4c3b1a6e7d5f2c8b9e1a4d7f3c6b9e2a5d8f1c4b7e0a3d6f9c2b5e8a' );
const key = await AesGcm . importKey ( rawKey );
exportKey(key: CryptoKey): Promise<Uint8Array>
Exports CryptoKey to raw bytes.
const key = await AesGcm . generateKey ( 256 );
const rawBytes = await AesGcm . exportKey ( key );
console . log ( rawBytes ); // Uint8Array(32)
// Store securely (encrypted, not plaintext!)
Encryption/Decryption
encrypt(plaintext: Uint8Array, key: CryptoKey, nonce: Uint8Array, additionalData?: Uint8Array): Promise<Uint8Array>
Encrypts data with AES-GCM, returns ciphertext with authentication tag appended.
Parameters:
plaintext - Data to encrypt
key - AES key (from generateKey or deriveKey)
nonce - 12-byte nonce/IV (must be unique per encryption)
additionalData - Optional AAD (authenticated but not encrypted)
const plaintext = new TextEncoder (). encode ( 'Secret data' );
const key = await AesGcm . generateKey ( 256 );
const nonce = AesGcm . generateNonce ();
// Basic encryption
const ciphertext = await AesGcm . encrypt ( plaintext , key , nonce );
// With additional authenticated data (AAD)
const metadata = new TextEncoder (). encode ( 'version:1.0' );
const ciphertextWithAAD = await AesGcm . encrypt ( plaintext , key , nonce , metadata );
Output format:
[encrypted_data][16-byte_authentication_tag]
decrypt(ciphertext: Uint8Array, key: CryptoKey, nonce: Uint8Array, additionalData?: Uint8Array): Promise<Uint8Array>
Decrypts AES-GCM ciphertext, verifies authentication tag.
Parameters:
ciphertext - Encrypted data with tag
key - Same key used for encryption
nonce - Same nonce used for encryption
additionalData - Same AAD used for encryption (if any)
try {
const decrypted = await AesGcm . decrypt ( ciphertext , key , nonce );
const message = new TextDecoder (). decode ( decrypted );
console . log ( message );
} catch ( error ) {
console . error ( 'Decryption failed:' , error );
// Could be: wrong key, tampered data, or corrupted ciphertext
}
// With AAD (must match encryption)
const decryptedWithAAD = await AesGcm . decrypt (
ciphertextWithAAD ,
key ,
nonce ,
metadata
);
Throws:
InvalidNonceError - Nonce not 12 bytes
DecryptionError - Authentication tag verification fails (data tampered), wrong key/nonce/AAD used, or corrupted ciphertext
Nonce Generation
generateNonce(): Uint8Array
Generates cryptographically secure 12-byte nonce.
const nonce = AesGcm . generateNonce ();
console . log ( nonce ); // Uint8Array(12)
// Store nonce with ciphertext (not secret, but must be unique)
Constants
AesGcm . AES128_KEY_SIZE // 16 bytes (128 bits)
AesGcm . AES256_KEY_SIZE // 32 bytes (256 bits)
AesGcm . NONCE_SIZE // 12 bytes (96 bits)
AesGcm . TAG_SIZE // 16 bytes (128 bits)
Nonce Management
Critical: Never reuse a nonce with the same key!
Safe Nonce Usage
// Generate new nonce for each encryption
const key = await AesGcm . generateKey ( 256 );
const msg1 = new TextEncoder (). encode ( 'First message' );
const nonce1 = AesGcm . generateNonce ();
const ct1 = await AesGcm . encrypt ( msg1 , key , nonce1 );
const msg2 = new TextEncoder (). encode ( 'Second message' );
const nonce2 = AesGcm . generateNonce (); // New nonce!
const ct2 = await AesGcm . encrypt ( msg2 , key , nonce2 );
Store nonce with ciphertext (nonce is not secret):
// Encrypt
const key = await AesGcm . generateKey ( 256 );
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( plaintext , key , nonce );
// Store together (common format: nonce + ciphertext)
const stored = new Uint8Array ( nonce . length + ciphertext . length );
stored . set ( nonce , 0 );
stored . set ( ciphertext , nonce . length );
// Later: Extract and decrypt
const extractedNonce = stored . slice ( 0 , AesGcm . NONCE_SIZE );
const extractedCiphertext = stored . slice ( AesGcm . NONCE_SIZE );
const decrypted = await AesGcm . decrypt ( extractedCiphertext , key , extractedNonce );
Nonce Collision Risk
With random nonces (12 bytes), collision probability:
After 2³² encryptions: ~0.005% chance
After 2⁴⁸ encryptions: 50% chance (birthday paradox)
Recommendations:
Random nonces : Safe for up to ~2³² encryptions per key
Counter-based : Increment counter for each encryption (no collisions)
Key rotation : Generate new key periodically to reset nonce space
// Counter-based nonce (for high-volume scenarios)
import * as Hex from '@tevm/voltaire/Hex' ;
class NonceCounter {
constructor () {
this . counter = 0 n ;
}
next () {
const counterHex = this . counter . toString ( 16 ). padStart ( 24 , '0' );
this . counter ++ ;
return Hex ( '0x' + counterHex );
}
}
const counter = new NonceCounter ();
const nonce1 = counter . next ();
const nonce2 = counter . next (); // Guaranteed unique
Additional Authenticated Data (AAD)
AAD is authenticated but not encrypted - useful for metadata:
// Encrypt message with metadata
const message = new TextEncoder (). encode ( 'Transfer $100 to Alice' );
const metadata = new TextEncoder (). encode ( JSON . stringify ({
timestamp: Date . now (),
version: '1.0' ,
sender: 'Bob'
}));
const key = await AesGcm . generateKey ( 256 );
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( message , key , nonce , metadata );
// Metadata is authenticated (tampering will fail decryption)
// But metadata itself is not encrypted (can be read)
Use cases:
Protocol version numbers
Timestamps
User IDs
Packet headers
Database row IDs
Security:
AAD is authenticated (tampering detected)
AAD is NOT encrypted (readable by anyone)
Must provide same AAD for decryption
Password-Based Encryption
Derive key from user password using PBKDF2:
import * as AesGcm from '@tevm/voltaire/AesGcm' ;
async function encryptWithPassword ( plaintext , password ) {
// 1. Generate random salt
import * as Hex from '@tevm/voltaire/Hex' ;
const salt = Hex ( '0x3e4d5c6b7a8910293847566574839201' );
// 2. Derive key from password
const key = await AesGcm . deriveKey ( password , salt , 100000 , 256 );
// 3. Encrypt
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( plaintext , key , nonce );
// 4. Return salt + nonce + ciphertext
return {
salt ,
nonce ,
ciphertext
};
}
async function decryptWithPassword ( encrypted , password ) {
// 1. Derive same key from password + salt
const key = await AesGcm . deriveKey (
password ,
encrypted . salt ,
100000 ,
256
);
// 2. Decrypt
return await AesGcm . decrypt ( encrypted . ciphertext , key , encrypted . nonce );
}
// Usage
const data = new TextEncoder (). encode ( 'Secret data' );
const encrypted = await encryptWithPassword ( data , 'user-password' );
// Store: encrypted.salt, encrypted.nonce, encrypted.ciphertext
// Later:
const decrypted = await decryptWithPassword ( encrypted , 'user-password' );
Key Storage
Secure Storage Patterns
1. Environment variables (server-side)
// Store base64-encoded key
const key = await AesGcm . generateKey ( 256 );
const keyBytes = await AesGcm . exportKey ( key );
const keyBase64 = btoa ( String . fromCharCode ( ... keyBytes ));
// Store in .env (keep out of version control!)
// ENCRYPTION_KEY=rK7J...
// Load and use
const storedKeyBytes = Uint8Array (
atob ( process . env . ENCRYPTION_KEY ),
c => c . charCodeAt ( 0 )
);
const storedKey = await AesGcm . importKey ( storedKeyBytes );
2. Browser (encrypted with password)
import * as Hex from '@tevm/voltaire/Hex' ;
// Encrypt master key with user password
async function storeEncryptedKey ( masterKey , userPassword ) {
const salt = Hex ( '0x7f8e9d0c1b2a3948576e8d9c0b1a2938' );
const passwordKey = await AesGcm . deriveKey ( userPassword , salt , 100000 , 256 );
const masterKeyBytes = await AesGcm . exportKey ( masterKey );
const nonce = AesGcm . generateNonce ();
const encryptedKey = await AesGcm . encrypt ( masterKeyBytes , passwordKey , nonce );
// Store in localStorage
localStorage . setItem ( 'encryptedKey' , JSON . stringify ({
salt: Array ( salt ),
nonce: Array ( nonce ),
ciphertext: Array ( encryptedKey )
}));
}
async function loadEncryptedKey ( userPassword ) {
const stored = JSON . parse ( localStorage . getItem ( 'encryptedKey' ));
const salt = new Uint8Array ( stored . salt );
const nonce = new Uint8Array ( stored . nonce );
const ciphertext = new Uint8Array ( stored . ciphertext );
const passwordKey = await AesGcm . deriveKey ( userPassword , salt , 100000 , 256 );
const masterKeyBytes = await AesGcm . decrypt ( ciphertext , passwordKey , nonce );
return await AesGcm . importKey ( masterKeyBytes );
}
3. Hardware Security Modules (HSM)
// Keys never leave secure hardware
// Use HSM APIs to encrypt/decrypt without exposing key
Security
Critical Warnings
1. Never reuse nonce with same key
// DANGEROUS - Same nonce with same key
const key = await AesGcm . generateKey ( 256 );
const nonce = AesGcm . generateNonce ();
const ct1 = await AesGcm . encrypt ( msg1 , key , nonce ); // OK
const ct2 = await AesGcm . encrypt ( msg2 , key , nonce ); // BREAKS SECURITY!
// Attacker can XOR ciphertexts to reveal plaintext relationship
2. Use cryptographically secure random
// CORRECT - Uses crypto.getRandomValues()
const nonce = AesGcm . generateNonce ();
// WRONG - Never use Math.random() for cryptographic values
// Math.random() is predictable and NOT cryptographically secure!
3. Verify authentication tag (automatic)
// decrypt() verifies tag automatically
try {
const plaintext = await AesGcm . decrypt ( ciphertext , key , nonce );
// If we reach here, authentication passed
} catch ( error ) {
// Authentication failed - data was tampered or wrong key
console . error ( 'Tampering detected!' );
}
4. Protect keys at rest
// WRONG - Store raw key
localStorage . setItem ( 'key' , JSON . stringify ( keyBytes ));
// RIGHT - Encrypt key with password or use secure storage
const encryptedKey = await encryptWithPassword ( keyBytes , userPassword );
localStorage . setItem ( 'key' , JSON . stringify ( encryptedKey ));
5. Use strong passwords for derivation
// Weak password = weak encryption
const weakKey = await AesGcm . deriveKey ( '12345' , salt , 100000 , 256 );
// Strong password = strong encryption
const strongKey = await AesGcm . deriveKey (
'correct-horse-battery-staple-2024' ,
salt ,
100000 ,
256
);
Best Practices
1. Key size: Use 256-bit keys for sensitive data
const key = await AesGcm . generateKey ( 256 ); // Recommended
2. PBKDF2 iterations: Balance security vs performance
// Minimum: 100,000 iterations
// Recommended: 600,000+ iterations (OWASP 2023)
const key = await AesGcm . deriveKey ( password , salt , 600000 , 256 );
3. Salt randomness: Use 16+ byte random salt
import * as Hex from '@tevm/voltaire/Hex' ;
const salt = Hex ( '0xa7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2' );
4. Key rotation: Periodically generate new keys
// Rotate keys every N encryptions or time period
if ( encryptionCount > 1000000 || Date . now () - keyCreatedTime > 30 * 86400000 ) {
key = await AesGcm . generateKey ( 256 );
encryptionCount = 0 ;
keyCreatedTime = Date . now ();
}
5. Clear sensitive memory (when possible)
// After use, zero out key bytes
const keyBytes = await AesGcm . exportKey ( key );
// Use key...
keyBytes . fill ( 0 ); // Clear memory
Common Attacks
Nonce Reuse Attack:
Same nonce + key reveals XOR of plaintexts
Protection: Always generate new nonce
Key Exhaustion:
Too many encryptions with same key increases collision risk
Protection: Rotate keys periodically
Weak Password:
Brute-force PBKDF2-derived keys
Protection: Strong passwords + high iteration count
Timing Attacks:
Constant-time operations in WebCrypto API
Protection: Use native crypto.subtle (not hand-rolled crypto)
Padding Oracle:
Not applicable to GCM (no padding)
GCM uses stream cipher mode
Benchmarks (typical)
Encryption speed (AES-256-GCM):
Modern CPU with AES-NI: 1-5 GB/s
Without hardware acceleration: 50-200 MB/s
Key derivation (PBKDF2):
100,000 iterations: ~50-100ms
600,000 iterations: ~300-600ms
Optimization Tips
1. Batch operations when possible
// Encrypt multiple messages
const messages = [ ... ];
const encrypted = await Promise . all (
messages . map ( async msg => {
const nonce = AesGcm . generateNonce ();
const ct = await AesGcm . encrypt ( msg , key , nonce );
return { nonce , ciphertext: ct };
})
);
2. Reuse keys (but rotate periodically)
// Generate key once
const key = await AesGcm . generateKey ( 256 );
// Reuse for multiple encryptions (with different nonces!)
for ( const message of messages ) {
const nonce = AesGcm . generateNonce ();
await AesGcm . encrypt ( message , key , nonce );
}
3. Adjust PBKDF2 iterations for use case
// High security (cold storage)
const key = await AesGcm . deriveKey ( password , salt , 1000000 , 256 );
// Moderate security (frequent decryption)
const key = await AesGcm . deriveKey ( password , salt , 100000 , 256 );
Use Cases
File Encryption
import * as Hex from '@tevm/voltaire/Hex' ;
async function encryptFile ( fileData , password ) {
const salt = Hex ( '0x9e8d7c6b5a4938271605948372615049' );
const key = await AesGcm . deriveKey ( password , salt , 600000 , 256 );
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( fileData , key , nonce );
// Format: salt (16) + nonce (12) + ciphertext + tag (16)
const encrypted = new Uint8Array ( salt . length + nonce . length + ciphertext . length );
encrypted . set ( salt , 0 );
encrypted . set ( nonce , 16 );
encrypted . set ( ciphertext , 28 );
return encrypted ;
}
async function decryptFile ( encryptedFile , password ) {
const salt = encryptedFile . slice ( 0 , 16 );
const nonce = encryptedFile . slice ( 16 , 28 );
const ciphertext = encryptedFile . slice ( 28 );
const key = await AesGcm . deriveKey ( password , salt , 600000 , 256 );
return await AesGcm . decrypt ( ciphertext , key , nonce );
}
Database Field Encryption
class EncryptedDatabase {
constructor ( key ) {
this . key = key ;
}
async encryptField ( value ) {
const plaintext = new TextEncoder (). encode ( JSON . stringify ( value ));
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( plaintext , this . key , nonce );
return {
nonce: Array ( nonce ),
ciphertext: Array ( ciphertext )
};
}
async decryptField ( encrypted ) {
const nonce = new Uint8Array ( encrypted . nonce );
const ciphertext = new Uint8Array ( encrypted . ciphertext );
const plaintext = await AesGcm . decrypt ( ciphertext , this . key , nonce );
return JSON . parse ( new TextDecoder (). decode ( plaintext ));
}
}
Secure Messaging
async function sendEncryptedMessage ( message , recipientPublicKey , senderPrivateKey ) {
// 1. Derive shared secret (ECDH)
const sharedSecret = await deriveSharedSecret ( senderPrivateKey , recipientPublicKey );
// 2. Use shared secret as key
const key = await AesGcm . importKey ( sharedSecret );
// 3. Encrypt message
const plaintext = new TextEncoder (). encode ( message );
const nonce = AesGcm . generateNonce ();
const ciphertext = await AesGcm . encrypt ( plaintext , key , nonce );
return { nonce , ciphertext };
}
Error Handling
All AesGcm functions throw typed errors that extend CryptoError:
Error Code When InvalidKeyErrorINVALID_KEYKey not 16 or 32 bytes on import InvalidNonceErrorINVALID_NONCENonce not 12 bytes DecryptionErrorDECRYPTION_FAILEDAuth tag verification fails, wrong key/nonce/AAD, or ciphertext too short AesGcmErrorAES_GCM_ERRORGeneric encryption failure
import * as AesGcm from '@tevm/voltaire/AesGcm' ;
import { DecryptionError , InvalidNonceError , InvalidKeyError } from '@tevm/voltaire/AesGcm' ;
try {
const decrypted = await AesGcm . decrypt ( ciphertext , key , nonce );
} catch ( e ) {
if ( e instanceof DecryptionError ) {
console . error ( 'Authentication failed:' , e . message );
console . error ( 'Code:' , e . code ); // "DECRYPTION_FAILED"
} else if ( e instanceof InvalidNonceError ) {
console . error ( 'Invalid nonce:' , e . message );
}
}
All error classes have:
name - Error class name (e.g., "DecryptionError")
code - Machine-readable error code
message - Human-readable description
docsPath - Link to relevant documentation
Implementation Notes
Uses native WebCrypto API (crypto.subtle)
Hardware-accelerated on modern CPUs (AES-NI)
Constant-time operations (timing attack resistant)
NIST SP 800-38D compliant
128-bit authentication tag (maximum security)
96-bit nonce (12 bytes, standard for GCM)
References