# std.crypto - Cryptography Library (Zig 0.16.0) Comprehensive cryptographic primitives: hashing, encryption, signatures, key exchange, password hashing, and secure utilities. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html ## Zig 0.16 Entropy Rule Do not use `std.crypto.random` directly in new Zig 0.16 code. Entropy is owned by `std.Io`. ```zig var key: [32]u8 = undefined; io.random(&key); const rng_source: std.Random.IoSource = .{ .io = io }; const rng = rng_source.interface(); ``` Use `io.randomSecure(&bytes)` when fresh OS-backed secure entropy is required and failures should be reported. Zig 0.16 also adds AES-SIV, AES-GCM-SIV, and Ascon AEAD/hash constructions. ## Quick Reference | Category | Types/Functions | |----------|-----------------| | **Hash** | `hash.sha2.Sha256`, `hash.sha2.Sha512`, `hash.sha3.*`, `hash.Blake3`, `hash.blake2.*`, `hash.Md5`, `hash.Sha1` | | **AEAD** | `aead.aes_gcm.Aes256Gcm`, `aead.chacha_poly.ChaCha20Poly1305`, `aead.aegis.*` | | **MAC** | `auth.hmac.*`, `auth.siphash.*`, `auth.cmac.*` | | **Signatures** | `sign.Ed25519`, `sign.ecdsa.*` | | **Key Exchange** | `dh.X25519` | | **KEM** | `kem.ml_kem.*` (post-quantum) | | **Password** | `pwhash.argon2`, `pwhash.scrypt`, `pwhash.bcrypt`, `pwhash.pbkdf2` | | **KDF** | `kdf.hkdf.HkdfSha256`, `kdf.hkdf.HkdfSha512` | | **Random** | Use `io.random`, `io.randomSecure`, or `std.Random.IoSource` | | **Utilities** | `secureZero`, `timing_safe.*`, `codecs.*` | ## Choosing Algorithms ``` Need encryption? ├─ With authentication → AEAD (Aes256Gcm, ChaCha20Poly1305) └─ Stream only → stream.chacha.* (usually want AEAD instead) Need hashing? ├─ General purpose → Sha256, Sha512, Blake3 ├─ Password storage → argon2, scrypt, bcrypt └─ Legacy compatibility → Md5, Sha1 (NOT secure for new designs) Need signatures? ├─ Standard choice → Ed25519 └─ ECDSA compatibility → ecdsa.EcdsaP256Sha256 Need key exchange? ├─ Standard choice → X25519 └─ Post-quantum → ml_kem.* (Kyber) Need MAC? ├─ With key → HmacSha256, HmacSha512 └─ Hash table keying → siphash ``` ## Hashing ### SHA-2 Family ```zig const std = @import("std"); const sha2 = std.crypto.hash.sha2; // One-shot hashing var digest: [sha2.Sha256.digest_length]u8 = undefined; sha2.Sha256.hash("hello world", &digest, .{}); // Streaming (incremental) var hasher = sha2.Sha256.init(.{}); hasher.update("hello "); hasher.update("world"); hasher.final(&digest); // Peek at intermediate digest without consuming state const intermediate = hasher.peek(); ``` Available: `Sha224`, `Sha256`, `Sha384`, `Sha512`, `Sha512_224`, `Sha512_256` ### SHA-3 Family ```zig const sha3 = std.crypto.hash.sha3; var digest: [sha3.Sha3_256.digest_length]u8 = undefined; sha3.Sha3_256.hash("data", &digest, .{}); // SHAKE (extendable output) var shake = sha3.Shake128.init(.{}); shake.update("data"); var output: [64]u8 = undefined; shake.squeeze(&output); ``` Available: `Sha3_224`, `Sha3_256`, `Sha3_384`, `Sha3_512`, `Shake128`, `Shake256`, `Keccak256`, `Keccak512` ### Blake3 ```zig const Blake3 = std.crypto.hash.Blake3; // Standard hashing var digest: [Blake3.digest_length]u8 = undefined; Blake3.hash("data", &digest, .{}); // Keyed hashing (MAC) var keyed: [Blake3.digest_length]u8 = undefined; Blake3.hash("data", &keyed, .{ .key = key }); // Key derivation uses the dedicated KDF mode, not keyed-hash options. var derived: [32]u8 = undefined; var kdf = Blake3.initKdf("my app v1 key derivation", .{}); kdf.update("material"); kdf.final(&derived); ``` ### Blake2 ```zig const blake2 = std.crypto.hash.blake2; // Blake2b (64-byte output) var digest: [blake2.Blake2b256.digest_length]u8 = undefined; blake2.Blake2b256.hash("data", &digest, .{}); // With key blake2.Blake2b256.hash("data", &digest, .{ .key = key }); ``` Available: `Blake2b128`, `Blake2b256`, `Blake2b384`, `Blake2b512`, `Blake2s128`, `Blake2s224`, `Blake2s256` ## AEAD (Authenticated Encryption) ### AES-GCM ```zig const Aes256Gcm = std.crypto.aead.aes_gcm.Aes256Gcm; // Encryption var ciphertext: [plaintext.len]u8 = undefined; var tag: [Aes256Gcm.tag_length]u8 = undefined; Aes256Gcm.encrypt(&ciphertext, &tag, plaintext, associated_data, nonce, key); // Decryption var decrypted: [ciphertext.len]u8 = undefined; try Aes256Gcm.decrypt(&decrypted, &ciphertext, tag, associated_data, nonce, key); // Returns error.AuthenticationFailed if the tag doesn't verify. Treat the // contents of `decrypted` as invalid and discard them on any failure. ``` Key constants: - `key_length`: 32 bytes (256 bits) - `nonce_length`: 12 bytes - `tag_length`: 16 bytes ### ChaCha20-Poly1305 ```zig const ChaCha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305; var ciphertext: [msg.len]u8 = undefined; var tag: [ChaCha20Poly1305.tag_length]u8 = undefined; ChaCha20Poly1305.encrypt(&ciphertext, &tag, msg, ad, nonce, key); try ChaCha20Poly1305.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key); ``` Key constants: - `key_length`: 32 bytes - `nonce_length`: 12 bytes (IETF) or 24 bytes (XChaCha) - `tag_length`: 16 bytes Available variants: - `ChaCha20Poly1305` - Standard IETF - `XChaCha20Poly1305` - Extended nonce (24 bytes, better for random nonces) - `ChaCha12Poly1305`, `ChaCha8Poly1305` - Reduced rounds (faster, lower security margin) ### AEGIS High-performance AEAD designed for modern CPUs with AES-NI: ```zig const Aegis256 = std.crypto.aead.aegis.Aegis256; var ciphertext: [msg.len]u8 = undefined; var tag: [Aegis256.tag_length]u8 = undefined; Aegis256.encrypt(&ciphertext, &tag, msg, ad, nonce, key); try Aegis256.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key); ``` ## Message Authentication (MAC) ### HMAC ```zig const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256; // One-shot var mac: [HmacSha256.mac_length]u8 = undefined; HmacSha256.create(&mac, message, key); // Streaming var hmac = HmacSha256.init(key); hmac.update(data1); hmac.update(data2); hmac.final(&mac); ``` Available: `HmacMd5`, `HmacSha1`, `HmacSha224`, `HmacSha256`, `HmacSha384`, `HmacSha512` ### SipHash Fast MAC for hash table keying (not for general authentication): ```zig const SipHash = std.crypto.auth.siphash.SipHash64(2, 4); const hash = SipHash.hash(key, data); ``` ## Digital Signatures ### Ed25519 ```zig const Ed25519 = std.crypto.sign.Ed25519; // Generate key pair const kp = Ed25519.KeyPair.generate(io); // Sign message const sig = kp.sign(message, null); // Verify signature try sig.verify(message, kp.public_key); // Returns error.SignatureVerificationFailed on failure // Incremental signing (large messages) var signer = try kp.signer(null); signer.update(chunk1); signer.update(chunk2); const sig2 = signer.finalize(); ``` Key lengths: - Secret key: 64 bytes - Public key: 32 bytes - Signature: 64 bytes ### ECDSA ```zig const EcdsaP256Sha256 = std.crypto.sign.ecdsa.EcdsaP256Sha256; // Generate key pair const kp = EcdsaP256Sha256.KeyPair.generate(io); // Sign const sig = try kp.sign(message, null); // Verify try sig.verify(message, kp.public_key); ``` Available: `EcdsaP256Sha256`, `EcdsaP256Sha3_256`, `EcdsaP384Sha384`, `EcdsaP384Sha3_384`, `EcdsaSecp256k1Sha256` ## Key Exchange ### X25519 (Diffie-Hellman) ```zig const X25519 = std.crypto.dh.X25519; // Generate key pairs for Alice and Bob const alice = X25519.KeyPair.generate(io); const bob = X25519.KeyPair.generate(io); // Compute shared secret const alice_shared = try X25519.scalarmult(alice.secret_key, bob.public_key); const bob_shared = try X25519.scalarmult(bob.secret_key, alice.public_key); // alice_shared == bob_shared // IMPORTANT: Hash the shared secret before use var key: [32]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(&alice_shared, &key, .{}); ``` ### ML-KEM (Post-Quantum) ```zig const MLKem768 = std.crypto.kem.ml_kem.MLKem768; // Key generation const kp = MLKem768.KeyPair.generate(io); // Encapsulation (sender) const encaps = kp.public_key.encaps(io); const shared_secret = encaps.shared_secret; const ciphertext = encaps.ciphertext; // Decapsulation (receiver) const decaps_secret = try kp.secret_key.decaps(ciphertext); // shared_secret == decaps_secret ``` Available: `MLKem512`, `MLKem768`, `MLKem1024` ## Key Derivation ### HKDF ```zig const HkdfSha256 = std.crypto.kdf.hkdf.HkdfSha256; // Extract: derive pseudorandom key from input keying material const prk = HkdfSha256.extract(salt, input_key_material); // Expand: derive output key from PRK var output_key: [32]u8 = undefined; HkdfSha256.expand(&output_key, context_info, prk); // Streaming extract (large IKM) var hkdf = HkdfSha256.extractInit(salt); hkdf.update(ikm_part1); hkdf.update(ikm_part2); var prk2: [HkdfSha256.prk_length]u8 = undefined; hkdf.final(&prk2); ``` ## Password Hashing ### Argon2 Memory-hard password hashing (recommended for new applications): ```zig const argon2 = std.crypto.pwhash.argon2; // Hash password var hash: [32]u8 = undefined; try argon2.kdf( allocator, &hash, password, salt, .{ .t = 3, // time cost (iterations) .m = 65536, // memory cost (KiB) .p = 4, // parallelism }, .argon2id, // mode: argon2i, argon2d, or argon2id io, ); // Use preset parameters try argon2.kdf(allocator, &hash, password, salt, argon2.Params.interactive_2id, .argon2id, io); // PHC string format (for storage) var buf: [128]u8 = undefined; const encoded = try argon2.strHash(password, .{ .allocator = allocator, .params = .interactive_2id, .mode = .argon2id, }, &buf, io); // Returns: "$argon2id$v=19$m=65536,t=3,p=4$..." // Verify PHC-encoded hash try argon2.strVerify(encoded, password, .{ .allocator = allocator }, io); ``` Parameter presets: - `interactive_2id`: Fast verification (login forms) - `moderate_2id`: Balanced - `sensitive_2id`: High security (key derivation) - `owasp_2id`: OWASP recommended ### Scrypt Memory-hard KDF: ```zig const scrypt = std.crypto.pwhash.scrypt; var hash: [32]u8 = undefined; try scrypt.kdf( allocator, &hash, password, salt, .{ .ln = 17, .r = 8, .p = 1 }, // N=2^17, r=8, p=1 ); // Presets try scrypt.kdf(allocator, &hash, password, salt, scrypt.Params.interactive); ``` ### bcrypt ```zig const bcrypt = std.crypto.pwhash.bcrypt; // Hash password var hash: [128]u8 = undefined; const hash_str = try bcrypt.strHash(password, .{ .params = .owasp, .encoding = .phc, }, &hash, io); // Verify try bcrypt.strVerify(hash_str, password, .{ .silently_truncate_password = false }); ``` ### PBKDF2 ```zig const pbkdf2 = std.crypto.pwhash.pbkdf2; const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256; var key: [32]u8 = undefined; try pbkdf2(&key, password, salt, 100000, HmacSha256); // 100k iterations ``` ## Secure Random Use the caller-provided `std.Io` entropy interface. `io.random` is the process CSPRNG and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable; use `io.randomSecure` when that fallback is unacceptable. ```zig // Random bytes var key: [32]u8 = undefined; io.random(&key); // Adapt std.Io to the general std.Random convenience API. var source: std.Random.IoSource = .{ .io = io }; const random = source.interface(); const n = random.int(u64); const bounded = random.intRangeLessThan(u32, 0, 100); // [0, 100) // Random float [0, 1) const f = random.float(f64); // Shuffle random.shuffle(u32, &items); ``` ## Secure Utilities ### secureZero Securely erase sensitive data (prevents optimizer from removing): ```zig var secret: [32]u8 = undefined; // ... use secret ... std.crypto.secureZero(u8, &secret); // guaranteed to zero ``` ### Timing-Safe Operations ```zig const timing_safe = std.crypto.timing_safe; // Constant-time equality (for MACs, signatures) const equal = timing_safe.eql([32]u8, mac1, mac2); // Constant-time comparison const order = timing_safe.compare(u8, &a, &b, .big); // .lt, .eq, .gt // Constant-time arithmetic const overflow = timing_safe.add(u8, &a, &b, &result, .big); const underflow = timing_safe.sub(u8, &a, &b, &result, .big); ``` ### Codecs (Constant-Time) ```zig const codecs = std.crypto.codecs; // Hex encoding (constant-time) var hex: [64]u8 = undefined; try codecs.hex.encode(&hex, &binary, .lower); // Hex decoding var decoded: [32]u8 = undefined; try codecs.hex.decode(&decoded, &hex); // Base64 const base64 = codecs.base64; // Similar API to hex ``` ## Elliptic Curve Primitives Low-level curve operations (usually use higher-level APIs): ```zig const ecc = std.crypto.ecc; // Edwards25519 const point = ecc.Edwards25519.basePoint; const result = try point.mul(scalar); // P-256 (NIST) const p256_point = ecc.P256.basePoint; // Ristretto255 (prime-order group) const ristretto = ecc.Ristretto255.basePoint; ``` Available: `Curve25519`, `Edwards25519`, `Ristretto255`, `P256`, `P384`, `Secp256k1` ## Error Handling ```zig const errors = std.crypto.errors; // Common errors error.AuthenticationFailed // MAC/tag verification failed error.SignatureVerificationFailed error.IdentityElement // Degenerate point in ECC error.NonCanonical // Input not in canonical form error.InvalidEncoding // Malformed input error.WeakPublicKey // Unsafe public key error.PasswordVerificationFailed ``` ## Common Patterns ### Encrypt-then-MAC ```zig // Use AEAD instead - it handles this correctly const Aes256Gcm = std.crypto.aead.aes_gcm.Aes256Gcm; Aes256Gcm.encrypt(&ct, &tag, pt, ad, nonce, key); ``` ### Key Generation ```zig // For symmetric keys var key: [32]u8 = undefined; io.random(&key); // For asymmetric keys const kp = std.crypto.sign.Ed25519.KeyPair.generate(io); ``` ### Nonce Management ```zig // Option 1: Counter (deterministic, never reuse) var nonce: [12]u8 = undefined; std.mem.writeInt(u64, nonce[0..8], counter, .big); @memset(nonce[8..], 0); counter += 1; // Option 2: Random (safe with XChaCha's 24-byte nonce) const XChaCha = std.crypto.aead.chacha_poly.XChaCha20Poly1305; var nonce: [XChaCha.nonce_length]u8 = undefined; io.random(&nonce); ``` ### Secure Password Storage ```zig const argon2 = std.crypto.pwhash.argon2; // Registration: hash and store var buf: [128]u8 = undefined; const hash_str = try argon2.strHash(password, .{ .allocator = allocator, .params = .interactive_2id, .mode = .argon2id, }, &buf, io); // Store hash_str in database // Login: verify argon2.strVerify(stored_hash, password, .{ .allocator = allocator }, io) catch |err| { if (err == error.PasswordVerificationFailed) { // Invalid password } }; ``` ## Side-Channel Protection Configure side-channel mitigations: ```zig const SideChannelsMitigations = std.crypto.SideChannelsMitigations; // Available levels: // .none - Fastest, no mitigations // .basic - Protects against most practical attacks // .medium - Default, good balance (increased resistance) // .full - Highest protection, significant performance impact // Default is .medium const default = std.crypto.default_side_channels_mitigations; ``` ## Notes - **Never use MD5 or SHA1 for security** - only for legacy compatibility - **AEAD over separate encrypt+MAC** - AES-GCM or ChaCha20-Poly1305 handle this correctly - **Hash shared secrets** - X25519 output should be passed through a KDF before use - **Use argon2id for passwords** - it's the current best practice - **XChaCha for random nonces** - 24-byte nonce has negligible collision probability - **Timing attacks** - use `timing_safe.eql` for comparing secrets, not `==` or `std.mem.eql` - **Zero secrets** - always `secureZero` sensitive data when done