Crypto
Cryptographic hashing, HMAC, and secure random bytes inside DuckDB.
On this page
Technical Overview
Cryptographic hashing, HMAC, and secure random β directly in SQL
DuckDB ships with hash() (non-cryptographic) and a built-in sha256(). The Crypto extension adds the full SHA-2 and SHA-3 families, BLAKE3 and BLAKE2b, HMAC, an order-deterministic aggregate hash, and a CSPRNG for keys, salts, and nonces. The scope is deliberately hashing and authentication only β and getting the hashing semantics right matters more than the algorithm list.
Type-aware, length-safe hashing
The interesting design choices here are not which algorithms ship, but how values become bytes before they're hashed. A cryptographic hash is only as trustworthy as the rule that turns a SQL value into the input bytes β and this extension makes that rule strict on purpose.
-
β’
Hashing is type-sensitive by design:
42::INTEGERand42::BIGINTproduce different digests, because their binary representations differ and the hash is computed over those bytes. That is the right behaviour for cryptographic use: a hash that silently collapses across logically distinct domains is a footgun. The flip side is that you must cast inputs deliberately β pin the type if a digest needs to be stable across schema changes. -
β’
Length-prefixed list elements: Variable-length elements (
VARCHAR,BLOB) inside a list are hashed as[8-byte length][content]. This closes a length-extension-style ambiguity: without the length prefix,['ab', 'c']and['a', 'bc']would serialize to the same byte stream and collide. With it, they produce distinct digests β and the same rule applies whether you hash a list directly or fold aVARCHAR/BLOBcolumn through the aggregate hash. -
β’
Aggregate equals list, deterministically: The aggregate hash over
col ORDER BY ordis byte-equal to a single hash ofLIST(col ORDER BY ord)β so any client (Python, Go, Rust) that can build the equivalent ordered list can verify a DuckDB digest byte-for-byte. TheORDER BYis mandatory: DuckDB's aggregate execution does not guarantee row order, so an unordered hash aggregate would be non-deterministic and worthless as a checksum. -
β’
NULL propagation and BLOB output:
crypto_hashandcrypto_hmacpropagate NULL (NULL in, NULL out); the aggregate returns NULL for an empty group. Coalesce or cast NULLs explicitly if you need a stable representation for them. All hash functions return rawBLOBβ compare with=for byte-equality, or wrap withlower(to_hex(...))only when you need a human-readable string.
Honest scope β what's not in this extension
The extension is deliberately narrow: hashing, keyed authentication, and secure random bytes. If your problem needs primitives from a wider library, this is the wrong tool and you'll need to combine it with something else β knowing the boundary up front prevents misuse.
-
β’
No symmetric encryption: There is no
crypto_encrypt/crypto_decrypt. The extension does not implement AES (see NIST FIPS 197) or any other cipher. Encrypt-at-rest belongs at the storage / file level, not in row expressions. - β’ No password-hashing KDFs: No bcrypt, scrypt, Argon2, or PBKDF2. For storing user passwords, follow the OWASP Password Storage Cheat Sheet and run a real KDF in your application layer β a fast cryptographic hash in SQL is not a password hash. You can generate salts here with the CSPRNG; the KDF itself must live elsewhere.
- β’ No public-key signatures: No RSA / ECDSA / Ed25519. Authentication is symmetric only, via HMAC with a shared secret.
-
β’
Legacy algorithms exposed for compatibility: MD4, MD5, and SHA-1 are present so you can interop with existing systems, but they are not cryptographically secure for new designs. See NIST SP 800-107 Rev. 1 for current guidance β prefer
sha2-256,sha2-512, orblake3. MD4 may be disabled outright in modern OpenSSL builds.
Where the random bytes come from
Secure random material is a small surface but an easy thing to get wrong β using a non-cryptographic RNG for a key or nonce silently undermines everything built on top of it.
-
β’
OpenSSL's CSPRNG, not a PRNG:
crypto_random_bytesdraws from OpenSSL'sRAND_bytesβ the same cryptographically secure generator TLS implementations and key-generation libraries rely on, not DuckDB's ordinaryrandom(). That distinction is the whole point: use it for HMAC keys, salts, nonces, and random IDs. -
β’
Bounded output: Length must be between 1 byte and 4 GB β 1 (DuckDB's BLOB cap); 0 or negative raises an error. Output is a raw
BLOBβ hex it for display, store it as-is for use as key material.
Deep Dive
Technical Details
What this extension adds
DuckDB ships with hash() (non-cryptographic), md5(), and sha256(). The Crypto extension adds the rest of what production cryptographic workloads need:
- More algorithms β BLAKE3, BLAKE2b, the full SHA-2 family (224/256/384/512), the SHA-3 family (224/256/384/512), and Keccak variants.
- More input types β every numeric type, VARCHAR, BLOB, BOOLEAN, DATE, TIME, TIMESTAMP, UUID, and lists of those β not just strings. Different DuckDB types hash to different digests by design.
- HMAC with the same algorithm vocabulary β for keyed message authentication.
crypto_hash_aggβ an order-deterministic aggregate hash for dataset checksums and change detection.crypto_random_bytesβ cryptographically secure random bytes from OpenSSLβs CSPRNG.
Cryptographic vs non-cryptographic hashing
Use Crypto when you need:
- Tamper resistance β adversaries canβt construct collisions cheaply (BLAKE3, SHA-2, SHA-3).
- Authentication β HMAC for verifying message integrity with a shared secret.
- Stable cross-system fingerprints β agreed-upon algorithm output that other systems can compute identically (e.g. ETags, content-addressed storage).
- Secure random β keys, salts, nonces.
Use Hashfuncs instead when you need:
- Speed β xxHash and RapidHash run at near-RAM bandwidth with no cryptographic guarantees.
- Hash partitioning, sharding, cache keys β collision risk on adversarial inputs is acceptable.
- Bloom-filter / cuckoo-filter inputs β paired with
bitfilters.
Both extensions can coexist; pick per use case.
Determinism and types
crypto_hash is deterministic for a given (algorithm, value, type) triple but is type-sensitive β 42::INTEGER and 42::BIGINT produce different hashes. This is the right behaviour for cryptographic use: it keeps the hash from collapsing across logically distinct domains.
Lists of VARCHAR or BLOB values include each elementβs length (as a 64-bit integer) before its content, defending against length-extension-style ambiguity attacks.
NULLs hash to NULL β crypto_hash('sha2-256', NULL::VARCHAR) IS NULL returns true.
Aggregate hash semantics
crypto_hash_agg('algo', col ORDER BY ord) produces the same digest as crypto_hash('algo', LIST(col ORDER BY ord)). The ORDER BY is required β DuckDBβs query planner doesnβt guarantee row order, so an unordered aggregate would be non-deterministic.
This equivalence is useful when comparing datasets across systems: anything that can produce the equivalent ordered list (Python, Go, Rust) can compute the same digest and verify a DuckDB result.
Random byte generation
crypto_random_bytes(n) returns n bytes from OpenSSLβs RAND_bytes(). Thatβs the same CSPRNG used by TLS implementations, key generation libraries, and openssl rand.
- Minimum length: 1 byte
- Maximum length: 4,294,967,295 bytes (4 GB β 1, DuckDBβs BLOB cap)
- Length 0 or negative raises
InvalidInputException
Install
INSTALL crypto FROM community;
LOAD crypto;
Quick Start
SHA-256 of a string
-- BLOB output, hex it for display
SELECT lower(to_hex(crypto_hash('sha2-256', 'hello world'))) AS sha256;
HMAC-SHA256
SELECT lower(to_hex(crypto_hmac('sha2-256', 'my-secret', 'message'))) AS hmac;
32 cryptographically secure random bytes (e.g. an AES-256 key)
SELECT crypto_random_bytes(32) AS key;
Deterministic checksum of an ordered dataset
SELECT lower(to_hex(crypto_hash_agg('sha2-256', email ORDER BY email))) AS dataset_hash
FROM users;
Reference
Extension Contents
Quick reference to all available functions and settings organized by category.
| Name | Type | Description |
|---|---|---|
|
HMAC
Keyed message authentication codes. Use for signing API requests, verifying message integrity, or building tamper-evident audit trails β anywhere a hash needs a shared secret. |
||
| crypto_hmac() | Hash-based Message Authentication Code (HMAC). | |
|
Hashing
Compute cryptographic digests of values, lists, and entire datasets. Supports BLAKE3, BLAKE2b, the SHA-2 / SHA-3 families, plus legacy MD4 / MD5 / SHA-1 for compatibility. The aggregate variant produces an order-deterministic dataset checksum. |
||
| crypto_hash() | Compute a cryptographic hash of a value with the given algorithm. | |
| crypto_hash_agg() | Order-deterministic aggregate hash. | |
|
Random
Cryptographically secure random bytes from OpenSSL's CSPRNG. Use for encryption keys, salts, nonces, and any other cryptographic material that must be unpredictable. |
||
| crypto_random_bytes() | Cryptographically secure random bytes from OpenSSL's RAND_bytes. | |
No extension contents match that search.
API Reference
Function Documentation
Practical Examples
Cookbook
Real-world recipes and patterns for common use cases.
Hash a Column
crypto_hash returns a BLOB. For human-readable output, hex-encode it:
SELECT id, lower(to_hex(crypto_hash('sha2-256', email))) AS email_hashFROM users;Different DuckDB types of the same numeric value hash differently β 42::INTEGER, 42::BIGINT, and '42'::VARCHAR all produce distinct digests. Thatβs intentional; it keeps the hash from collapsing across logically distinct values.
Sign a Message
HMAC combines a secret key with a message under a hash algorithm:
SELECT lower(to_hex(crypto_hmac('sha2-256', :secret, request_body))) AS signatureFROM api_requests;Compare signatures byte-for-byte (or in DuckDB, =) to verify authenticity. The verification side performs the same HMAC and rejects on mismatch.
Generate a Salt
-- 16-byte random salt for password hashingSELECT crypto_random_bytes(16) AS salt;The bytes come from OpenSSLβs CSPRNG β suitable for cryptographic use. Length is unbounded up to 4 GB β 1.
Dataset Checksum
crypto_hash_agg is the aggregate counterpart of crypto_hash. Hashing all values in a column in a specified order produces a single deterministic digest you can use for change detection or to verify a partition matches a known-good baseline:
-- Whole-table checksumSELECT lower(to_hex(crypto_hash_agg('sha2-256', email ORDER BY email)))FROM users;-- Per-partition checksum β matches across systems if rows are identicalSELECT day, lower(to_hex(crypto_hash_agg('sha2-256', payload ORDER BY id))) AS day_hashFROM eventsGROUP BY dayORDER BY day;The ORDER BY is required β without it, hashes would be non-deterministic across query plans.
Tamper-Evident Audit Log
Build a hash chain by hashing each rowβs content together with the previous rowβs hash:
WITH chained AS ( SELECT id, action, user_id, created_at, LAG(hash) OVER (ORDER BY created_at) AS prev_hash, crypto_hash('sha2-256', [CAST(id AS VARCHAR), action, CAST(user_id AS VARCHAR), CAST(created_at AS VARCHAR), COALESCE(LAG(hash) OVER (ORDER BY created_at)::VARCHAR, '')] ) AS hash FROM audit_log)SELECT * FROM chained ORDER BY created_at;Verify the chain later by recomputing each hash and comparing β any tampering breaks the chain at the first altered row.
Stable IDs from Multiple Columns
Generate a deterministic ID from any combination of columns:
SELECT customer_email, product_sku, purchase_date, -- stable surrogate key, idempotent across re-runs lower(to_hex(crypto_hash('sha2-256', [customer_email, product_sku, CAST(purchase_date AS VARCHAR)] ))) AS purchase_idFROM raw_purchases;Pair with hashfuncs when you want the same property but cheaper (non-cryptographic) for partitioning, sharding, or cache keys.
Algorithm Reference
| Algorithm | Output | Notes |
|---|---|---|
blake3 |
32 B | Modern, fast β recommended default |
blake2b-512 |
64 B | Strong all-rounder, no BLAKE3 hardware needed |
sha2-256 |
32 B | Industry standard SHA-2 |
sha2-512 |
64 B | Larger SHA-2 output |
sha3-256 |
32 B | Keccak-based, post-SHA-2 standard |
keccak256 |
32 B | Original Keccak (mapped to SHA3-256 here) |
md5 |
16 B | Legacy β fast but not cryptographically secure |
sha1 |
20 B | Legacy β not cryptographically secure |
Platform Support
Compatibility
Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.
Quick Facts
Platforms
- Linux x86_64 aarch64
- Linux (musl) Not available
- macOS Intel Apple Silicon
- Windows x86_64
- WASM Not available
Compiled binary sizes
| Platform | Architecture | Size |
|---|---|---|
| Linux | x86_64 | 5.44 MB |
| Linux | aarch64 | 5.39 MB |
| macOS | Intel | 3.55 MB |
| macOS | Apple Silicon | 3.57 MB |
| Windows | x86_64 | 8.90 MB |
Compressed download size from the Haybarn extension repository.
DuckDB & Haybarn
Release calendar- DuckDB v1.5.5 Haybarn 1.5.5-rc1 Supported