Skip to content

Changing cipher_hmac_pgno after key use corrupts an encrypted database #613

Description

@DBFuzz

Version and environment

Reproduced from SQLCipher commit 810db22f575ee7cf94ea96a3e91622b5fcece3dc on Linux amd64. Runtime: SQLite 3.53.3 / SQLCipher 4.17.0 Community.

Describe the bug

PRAGMA cipher_hmac_pgno=be directly switches the page-number byte order used for page HMACs even after the key has been used and pages have been written. SQLCipher accepts the change, allows another successful write, and closes normally. Reopening with the same correct key and default format then fails HMAC authentication on page 1.

Minimal reproduction

bash sqlcipher-014-post-key-cipher-hmac-pgno-be-corrupts-an-encrypted-dat-a505e9e3/poc/run_revalidation.sh

The trigger SQL is:

PRAGMA key='testkey';
CREATE TABLE t(v);
INSERT INTO t VALUES('before');
SELECT count(*) FROM t;
PRAGMA cipher_hmac_pgno=be;
INSERT INTO t VALUES('after');
SELECT count(*) FROM t;

Close and reopen with the same key and default settings, then query the table and run PRAGMA integrity_check. The included control performs the same writes without changing cipher_hmac_pgno.

Actual behavior/output

control_create_rc=0
control_reopen_rc=0
PRAGMA cipher_hmac_pgno is deprecated, please remove from use
be
candidate_first_rc=0
candidate_reopen_rc=1

The failed reopen emits sqlcipher_page_cipher: hmac check failed for pgno=1 and file is not a database (26).

Expected behavior

Page-HMAC byte order should remain immutable once the key is used. A late change should be rejected or treated as a no-op; it must not create a file containing pages authenticated under inconsistent format assumptions.

Impact and scope

Although the compatibility PRAGMA is deprecated, applications can still invoke it and receive successful writes that persistently corrupt the encrypted database.

Relevant source/root cause

DEFAULT_CIPHER_FLAGS selects little-endian page numbers. sqlcipher_page_hmac() uses the LE/BE flags. The PRAGMA branch at src/sqlcipher.c:2959-2976 directly clears/sets those flags without checking CIPHER_FLAG_KEY_USED, unlike other cryptographic-format setters.

Suggested regression test

After creating and reading/writing a default-format encrypted database, execute PRAGMA cipher_hmac_pgno=be. Assert rejection or no state change, then write, close, reopen with the correct key, and verify all rows plus integrity_check.

Reproduction bundle

Attached: sqlcipher-014-post-key-cipher-hmac-pgno-be-corrupts-an-encrypted-dat-a505e9e3_attachment.zip. It contains the focused PoC, validation evidence, and candidate metadata.

sqlcipher-014-post-key-cipher-hmac-pgno-be-corrupts-an-encrypted-dat-a505e9e3_attachment.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions