A small command-line tool that decrypts (or encrypts, for test purposes) a SQLite database that uses a non-standard, per-page AES counter-mode construction — one that is sometimes labeled "CCM" by the software that produced it, but is not actually compliant RFC 3610 CCM.
SQLite's official Encryption Extension (SEE)
is commercial software sold by the SQLite maintainer (Hwaci). It documents a handful of supported
ciphers: AES-128-OFB, AES-256-OFB, AES-128-CCM, AES-256-GCM, and RC4. If you already know the raw
database key (for example, because you extracted it yourself from a device or application you have
the legal right to access), you don't need a commercial SEE license to open the file — you just
need something that reimplements the same page-cipher math against the raw key, the same way
sqlite3_key_v2() would.
SqliteSEECLI already does this well for the OFB and
RC4 modes. It explicitly does not support CCM or GCM.
This tool fills a narrower, adjacent gap: some real-world software labels its SQLite encryption as "CCM" (and lays pages out with a CCM-shaped tag+nonce region) while actually using a much simpler, non-compliant construction — a single AES-ECB block encryption of a per-page nonce with an embedded little-endian counter, XORed directly against the page content. This is not real CCM (no authentication tag is verified, and the "counter" only touches a 4-byte sub-field of the nonce, not a fully-specified CCM counter block). It looks like a from-scratch, simplified reimplementation of counter mode by someone who labeled it "CCM" out of convenience, not a licensed SEE build.
This tool implements that construction so files using it can be decrypted (or, symmetrically, re-encrypted) once you already have the key.
No vendor source code, proprietary key material, or device-identifying information is included in this repository. The construction described here was independently derived by analyzing an embedded device's proprietary backup format; this repository intentionally does not name the device or software it came from, since the algorithm itself — not its origin — is the reusable part. You must supply your own key, obtained legally from a file or device you have the right to access.
Each page is page_size bytes, laid out as:
content [0 : page_size - reserve) ciphertext, in 16-byte AES blocks
tag [page_size - reserve : page_size - nonce_size) unverified / unused by this tool
nonce [page_size - nonce_size : page_size) unique per page
Content block i (0-indexed, 16 bytes each) decrypts as:
counter_i = (nonce[counter_offset : counter_offset + counter_width] as uint, little-endian) + i
keystream_i = AES_ECB_encrypt(key, nonce with that field replaced by counter_i)
plaintext_i = keystream_i XOR ciphertext_i
Because XOR is its own inverse, the exact same routine encrypts and decrypts — see --mode below.
The default --page-size (4096), --reserve-size (32), --tag-size (16), --nonce-size (16),
--counter-offset (4), and --counter-width (4) match one real-world instance of this
construction. They are ordinary parameters, not identifying fingerprints — pass different values
on the command line if your file uses a different layout.
pip install pycryptodome
No other dependencies. Tested with Python 3.9+.
Decrypt a file once you know the raw key:
python3 sqlite_ctrsee.py encrypted.db -o decrypted.db --key "your-ascii-key-here"
Or supply the key as hex or from a file:
python3 sqlite_ctrsee.py encrypted.db -o decrypted.db --key-hex 30313233343536373839616263646566
python3 sqlite_ctrsee.py encrypted.db -o decrypted.db --key-file ./key.bin
If the source file corrupts the standard SQLite header fields at offset 16–24 (some
implementations stash proprietary metadata there instead of the real page_size/version/reserve
fields SQLite expects), add --fix-header to detect and repair just that span so the output opens
in stock sqlite3:
python3 sqlite_ctrsee.py encrypted.db -o decrypted.db --key "..." --fix-header
--fix-header only touches those bytes if they don't already look canonical — it won't silently
overwrite a file that doesn't need it.
Non-default page layout:
python3 sqlite_ctrsee.py encrypted.db -o decrypted.db --key "..." \
--page-size 4096 --reserve-size 48 --tag-size 32 --nonce-size 16 \
--counter-offset 0 --counter-width 4
--mode encrypt runs the identical transform in the other direction. This is provided to build
synthetic test vectors (see build_test_vector.py) — it will not produce a file compatible
with whatever real software you're targeting unless you also replicate its own nonce-generation
scheme (this tool does not attempt to guess or reproduce that).
python3 build_test_vector.py # writes testdata/sample.plain.db and testdata/sample.encrypted.db
python3 test_roundtrip.py # unit tests against synthetic, randomly generated page content
All test fixtures are generated synthetically at test time — nothing here is extracted from a real
proprietary file. Note the tests validate the cryptographic transform itself (encrypt/decrypt
round-trip, wrong-key rejection, reserve-region preservation); they do not attempt to fabricate a
byte-accurate "real" SQLite database with non-zero reserved bytes, since Python's standard sqlite3
module always writes reserved_bytes=0 and doesn't expose a way to override that without a real
codec. If you have legitimate access to a real file using this construction, that's the only way to
fully validate --fix-header and end-to-end openability in sqlite3.
This tool is an independent reimplementation of an observed cryptographic construction, in the
same spirit as SqliteSEECLI's reimplementation of the OFB/RC4 modes: raw-key access via something
equivalent to sqlite3_key_v2() is a documented, public API surface, and no commercial SEE license
or vendor source code is required to implement compatible page decryption once the key is known.
This repository does not include, and has never included, any proprietary key extracted from any
specific device or product — you are responsible for supplying your own key and for ensuring you
have the legal right to decrypt the file you're using it on.
MIT — see LICENSE.