Textrun will ship with post-quantum encryption using TextrunSQL. To protect personal secrets but also to provide credentials to agentic researchers safely.
Prepare your encrypted SQLite databases for the post-quantum future
TextrunSQL helps defend against “harvest now, decrypt later” attacks by adding post-quantum protection to SQLCipher database keys.
Attackers are stealing encrypted databases today and storing them for later. Their hope is that future quantum computers will let them read what is protected now.
TextrunSQL adds post-quantum protection around the key that opens the database. SQLCipher handles the encryption. SQLite stores and queries the data.
How TextrunSQL works
TextrunSQL protects the database key with ML-KEM-768, which is a standard from NIST FIPS 203 and implemented in OpenSSL. SQLCipher still encrypts and checks the database pages. Your app still decides where the database, envelope, private key, and recovery information live.
Seal protects the database key inside a 1236-byte Envelope. Bytes 0..1187 are authenticated but not encrypted. The final 48 bytes contain the wrapped key and its tag.
Open validates the envelope and returns the key only when all checks succeed. Validation fails if the envelope has changed or the recipient or context does not match. A Handoff passes the key to SQLCipher.
What the labels mean:
AAD(Additional Authenticated Data): The envelope bytes0..1187whichAES-GCMauthenticates.AES-GCM/GCM: Short forms forAES-256-GCM, which TextrunSQL uses to wrap theDEKand authenticate theAAD.- Bind: Computes
rid = SHA-256(pk)andchash = SHA-256(context)so Open can reject a different recipient or context. chash(context hash): The 32-byteSHA-256hash of the caller context, stored in the envelope and used as theHKDFsalt.context: A nonempty database-context identifier with up to 1024 bytes. Use the same value for Seal & Open.- Decapsulate: Computes the shared secret using the ciphertext and the recipient’s private key.
DEK(Database Encryption Key): The 32-byte encryption key supplied to SQLCipher in raw-key form.- Derive: Uses
HKDF-SHA-256with the shared secret, context hash, and fixed protocol label to produce theAES-GCMwrapping key. - Encapsulate: Generates a 1088-byte ciphertext and a 32-byte shared secret using the recipient’s public key.
- Envelope: The fixed 1236-byte record containing the header, recipient and context bindings,
KEMciphertext,nonce, wrappedDEK, and tag. - Format: Converts the recovered
DEKinto SQLCipher’s lowercase raw-key syntax. That syntax uses the prefixx', exactly 64 hex digits, and a closing'. GCMoutput: Envelope bytes1188..1235contain the 32-byte wrappedDEKfollowed by its 16-byte authentication tag.- Handoff: The separate caller-controlled step that formats the recovered
DEKand applies it to an already-open SQLCipher database. hdr(header): The fixed 24-byte envelope header containing the magic, version, suite, flags, reserved bytes, and declared lengths.HKDF(HMAC-based Key Derivation Function): The key-derivation function that combines the shared secret, context hash, and fixed protocol label to produce the wrapping key.- Input: Open receives the
envelope, matching recipientkey, and unchangedcontext. Handoff receives the opendb, selectedschema, and recoveredDEK. KEMciphertext: The 1088-byteML-KEMoutput that the matching decapsulation key uses to produce the same shared secret.ML-KEM-768(Module-Lattice-Based Key-Encapsulation Mechanism): The FIPS 203 parameter set used by TextrunSQL. NIST classifies it in security category 3.768is a parameter-set name, not a key size.nonce: A fresh random 12-byteAES-GCMinput stored in the envelope. It must not repeat with the same wrapping key.- Open: Authenticates an envelope for the matching recipient and context and returns the
DEK. It does not itself apply theDEKto SQLCipher. pk(public key): The recipient’s 1184-byteML-KEMencapsulation key, also called the public key in this API.rid(recipient identifier): The 32-byteSHA-256digest ofpk, stored in the envelope and checked during Open.- Seal: Creates the envelope that protects the database key.
- Set key: Calls
sqlite3_key_v2()with the formatted raw key for the selected schema. SHA-256: The hash function used to computeridfrompkandchashfrom the context.- SQLCipher: The SQLite-compatible library that encrypts and authenticates database pages after the
DEKis applied. sqlite3_key_v2(): The SQLCipher API that applies the formatted key to the selected schema. Success means the key was accepted, not that existing database content was authenticated.tag: The 16-byteAES-GCMauthentication tag covering the wrappedDEKand the AAD.textrunsql_pq_key_sqlcipher(): Formats the 32-byteDEKas a SQLCipher raw key and callssqlite3_key_v2(). Success sets the key but does not authenticate existing database content. The caller must read protected content next.textrunsql_pq_open_dek(): Validates and authenticates the envelope and returns the 32-byteDEKonly on success. The output remains zero on failure.textrunsql_pq_seal_dek(): Seals a supplied 32-byteDEKinto a 1236-byte envelope for one recipient and context.- Unwrap: Uses
AES-GCMto authenticate the AAD and wrappedDEK, then releases the recoveredDEKonly if authentication succeeds. - Validate: Checks the exact envelope format, version, suite, recipient binding, and context binding before decapsulation.
wDEK(wrappedDEK): The 32-byteAES-GCMciphertext of theDEKstored in the envelope.- Wrap: Uses the derived key and random
noncetoAES-GCM-encrypt theDEKand authenticate the AAD, producingwDEKand the tag.
Why not encrypt the entire envelope?
Open needs the envelope’s header, recipient and context bindings, ML-KEM ciphertext, and nonce before it can derive the key that unwraps the database key. Encrypting those fields with that same key would create a circle: Open would need the key to read the data required to derive it.
Those fields are not secret. They remain readable, but AES-GCM authenticates them as AAD, so Open rejects the envelope if any of them change. The database key is the part that needs to stay hidden, so TextrunSQL encrypts it and uses the authentication tag to protect both it and the visible fields.
Why not use something that already exists?
SQLCipher currently does not provide post-quantum protection for the key. There is also no fork that does. TextrunSQL adds that missing layer and relies on existing cryptographic code:
- OpenSSL: TextrunSQL calls OpenSSL for
ML-KEMinstead of implementing the algorithm itself. - A current standard: OpenSSL’s
ML-KEMconforms to FIPS 203, and its 3.5 release has long-term support through April 2030. - Used elsewhere: NGINX and HAProxy also use OpenSSL’s post-quantum support for TLS. Their protocols are different, but the
ML-KEMcode comes from the same maintained library.
Are SHA-256, HKDF-SHA-256, and AES-256-GCM post-quantum-safe?
Post-quantum protection does not mean replacing every cryptographic algorithm. The quantum attacks that threaten many existing public-key systems do not work against SHA-256, HKDF-SHA-256, or AES-256-GCM in the same way. Quantum computers can speed up some attacks against these algorithms, but that does not make them easy to break.
In TextrunSQL, SHA-256 identifies the recipient and context. HKDF-SHA-256 turns the shared secret into a wrapping key. AES-256-GCM protects the database key and authenticates the envelope. These algorithms are still suitable for these tasks when used correctly.
How to test this
The tests follow the Seal & Open paths in the diagrams above. They check that Seal creates an envelope that Open can read, that the recovered DEK opens the SQLCipher database, and that Open refuses changed data, the wrong recipient, or the wrong context.
Start with the regular checks. After you have configured the repo with OpenSSL 3.5 or newer, run these commands from its root:
make libsqlite3.a testfixture
./testfixture test/sqlcipher.test
make -C textrunsql check
The first two commands build SQLCipher and run its own checks. The last command tests TextrunSQL, including NIST examples, the Seal & Open behavior above, and the handoff of the recovered key to SQLCipher. The full setup and exact coverage are in textrunsql/TESTING.md.
For deeper checks, run two more commands that look for memory errors or parser crashes:
make -C textrunsql asan
make -C textrunsql fuzz-smoke
asan reruns the focused C tests with checks for memory errors and undefined behavior. fuzz-smoke sends 5,000 generated inputs through the envelope parser and looks for crashes, memory errors, or undefined behavior.
Passing these tests shows that the code worked in the setup you used: this commit, compiler, operating system, OpenSSL provider, architecture, and build settings. It is not a security review or certification, and it cannot cover every way an app or its environment might expose the database or its keys.
How to learn more
These talks explain the ideas behind post-quantum cryptography, why the change matters now, and how ML-KEM is being used:
-
Tomas Gustavsson @ OpenSSL Conference 2025
Tomas from Refactor.com shows hybrid
ML-KEMTLS working out of the box with OpenSSL 3.5, then examines the remaining gaps across certificates, browsers, and hardware security modules. -
Sophie Schmieg & Bas Westerbaan @ RWPQC 2026
Sophie from Google and Bas from Cloudflare describe how hybrid
ML-KEMreached production TLS. They explain why store-now, decrypt-later makes key exchange urgent. Signatures, certificates, and legacy systems make the rest of the migration much harder. -
Jeff Crume @ IBM Technology 2026
Jeff uses a chessboard analogy to explain how high-dimensional lattices and added noise create problems that neither classical nor quantum computers can solve efficiently. He then connects the idea to harvest-now, decrypt-later and why encrypted data with a long useful life needs protection now.