diff --git a/sqlcipher-2.0-be-testkey.db b/sqlcipher-2.0-be-testkey.db new file mode 100644 index 0000000..f6f70f8 Binary files /dev/null and b/sqlcipher-2.0-be-testkey.db differ diff --git a/sqlcipher-2.0-testkey.db b/sqlcipher-2.0-le-testkey.db similarity index 100% rename from sqlcipher-2.0-testkey.db rename to sqlcipher-2.0-le-testkey.db diff --git a/src/crypto.c b/src/crypto.c index 556f607..ad722eb 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -129,6 +129,19 @@ int codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLeft, const c rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx); if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc); } + }else + if( sqlite3StrICmp(zLeft,"cipher_hmac_pgno")==0 ){ + // clear both pgno endian flags + if(sqlite3StrICmp(zRight, "le") == 0) { + sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO); + sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_LE_PGNO); + } else if(sqlite3StrICmp(zRight, "be") == 0) { + sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO); + sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_BE_PGNO); + } else if(sqlite3StrICmp(zRight, "native") == 0) { + sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO); + sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO); + } }else { return 0; } diff --git a/src/crypto.h b/src/crypto.h index c9ae57c..29861ba 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -59,6 +59,7 @@ /* possible flags for cipher_ctx->flags */ #define CIPHER_FLAG_HMAC 0x01 #define CIPHER_FLAG_LE_PGNO 0x02 +#define CIPHER_FLAG_BE_PGNO 0x04 #ifndef DEFAULT_CIPHER_FLAGS #define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 9c6b818..10c85f0 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -497,6 +497,7 @@ void sqlcipher_codec_ctx_free(codec_ctx **iCtx) { sqlcipher_free(ctx, sizeof(codec_ctx)); } +/** convert a 32bit unsigned integer to little endian byte ordering */ static inline void sqlcipher_put4byte_le(unsigned char *p, u32 v) { p[0] = (u8)v; p[1] = (u8)(v>>8); @@ -505,29 +506,31 @@ static inline void sqlcipher_put4byte_le(unsigned char *p, u32 v) { } int sqlcipher_page_hmac(cipher_ctx *ctx, Pgno pgno, unsigned char *in, int in_sz, unsigned char *out) { - unsigned char pgno_le[4]; - /* convert page number to consistent representation before calculating MAC for + unsigned char pgno_raw[sizeof(pgno)]; + /* we may convert page number to consistent representation before calculating MAC for compatibility across big-endian and little-endian platforms. Note: The public release of sqlcipher 2.0.0 to 2.0.6 had a bug where the bytes of pgno - were used directly in the MAC. So, we convert to little endian instead of big endian, to - preserve backwards compatibility on the most popular platform */ - sqlcipher_put4byte_le(pgno_le, pgno); + were used directly in the MAC. SQLCipher convert's to little endian by default to preserve + backwards compatibility on the most popular platforms, but can optionally be configured + to use either big endian or native byte ordering via pragma. */ + + if(ctx->flags & CIPHER_FLAG_LE_PGNO) { /* compute hmac using little endian pgno*/ + sqlcipher_put4byte_le(pgno_raw, pgno); + } else if(ctx->flags & CIPHER_FLAG_BE_PGNO) { /* compute hmac using big endian pgno */ + sqlite3Put4byte(pgno_raw, pgno); /* sqlite3Put4byte converts 32bit uint to big endian */ + } else { /* use native byte ordering */ + memcpy(pgno_raw, &pgno, sizeof(pgno)); + } HMAC_CTX_init(&ctx->hctx); - HMAC_Init_ex(&ctx->hctx, ctx->hmac_key, ctx->key_sz, EVP_sha1(), NULL); /* include the encrypted page data, initialization vector, and page number in HMAC. This will prevent both tampering with the ciphertext, manipulation of the IV, or resequencing otherwise valid pages out of order in a database */ HMAC_Update(&ctx->hctx, in, in_sz); - - if(ctx->flags & CIPHER_FLAG_LE_PGNO) /* default compute hmac using little endian */ - HMAC_Update(&ctx->hctx, (const unsigned char*) pgno_le, sizeof(pgno_le)); - else /* legacy setting - compute using native byte ordering */ - HMAC_Update(&ctx->hctx, (const unsigned char*) &pgno, sizeof(pgno)); - + HMAC_Update(&ctx->hctx, (const unsigned char*) pgno_raw, sizeof(pgno)); HMAC_Final(&ctx->hctx, out, NULL); HMAC_CTX_cleanup(&ctx->hctx); return SQLITE_OK; diff --git a/test/crypto.test b/test/crypto.test index fae5de5..9296fc9 100644 --- a/test/crypto.test +++ b/test/crypto.test @@ -1556,9 +1556,10 @@ do_test multipage-schema-autovacuum-shortread-wal { db close file delete -force test.db -# open a 2.0 database verify it can be opened -do_test open-2.0-database { - sqlite_orig db sqlcipher-2.0-testkey.db +# open a 2.0 database with little endian hmac page numbers (default) +# verify it can be opened +do_test open-2.0-le-database { + sqlite_orig db sqlcipher-2.0-le-testkey.db execsql { PRAGMA key = 'testkey'; SELECT count(*) FROM t1; @@ -1567,4 +1568,44 @@ do_test open-2.0-database { } {4 1 1 one one 1 2 one two} db close +# open a 2.0 database with big-endian hmac page numbers +# verify it can be opened +do_test open-2.0-be-database { + sqlite_orig db sqlcipher-2.0-be-testkey.db + execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_hmac_pgno = be; + SELECT count(*) FROM t1; + SELECT * FROM t1; + } +} {4 1 1 one one 1 2 one two} +db close + +# open a 2.0 database with big-endian hmac page numbers +# attach a new database with little endian page numbers (default) +# copy schema between the two, and verify the latter +# can be opened +do_test be-to-le-migration { + sqlite_orig db sqlcipher-2.0-be-testkey.db + + execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_hmac_pgno = be; + ATTACH DATABASE 'test.db' AS db2 KEY 'testkey'; + CREATE TABLE db2.t1(a,b); + INSERT INTO db2.t1 SELECT * FROM main.t1; + DETACH DATABASE db2; + } + db close + + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM t1; + SELECT * FROM t1; + } +} {4 1 1 one one 1 2 one two} +db close +file delete -force test.db + finish_test