diff --git a/backuptest.c b/backuptest.c index caacbbc..4629d0d 100644 --- a/backuptest.c +++ b/backuptest.c @@ -9,35 +9,85 @@ #define ERROR(X) {printf("[ERROR] "); printf X;fflush(stdout); exit(1);} -int main(int argc, char **argv) { - sqlite3 *indb, *edb, *outdb; +const char *usage = + "Usage: backuptest [options]\n" + "\n" + "Options :\n" + "\n" + "\t-i filename input filename or :memory: (default sqlcipher.db).\n" + "\t-I input key (default no key).\n" + "\t-o filename output filename or :memory: (default backup.db).\n" + "\t-O output key (default no key).\n" + "\t-h help (this text)\n" + "\n" + "\n" + ; +const char *optstring = "hi:o:I:O:"; + +int parse_args(int argc, char **argv, char **input, char **output, char **inkey, char **outkey) +{ + int c; - const char* infile = "plain.db"; - const char* efile= "sqlcipher.db"; - const char* outfile= "plain2.db"; + while((c = getopt(argc, argv, optstring)) != -1) { + switch (c) { + case 'i': + *input = optarg; + break; + case 'o': + *output = optarg; + break; + case 'I': + *inkey = optarg; + break; + case 'O': + *outkey = optarg; + break; + case 'h': + default: + fputs(usage, stderr); + return 0; + } + } - const char* key = "test123"; + return 1; +} +int main(int argc, char **argv) { + sqlite3 *indb, *outdb; + sqlite3_stmt *stmt; + char* infile = "sqlcipher.db"; + char* outfile= "backup.db"; + char* inkey = NULL; + char* outkey = NULL; + int count; int rc; + if (!parse_args(argc, argv, &infile, &outfile, &inkey, &outkey)) return 1; + if (sqlite3_open(infile, &indb) != SQLITE_OK) { ERROR(("sqlite3_open failed for %s: %s\n", infile, sqlite3_errmsg(indb))) } - sqlite3_exec(indb, "PRAGMA cache_size = 0;", NULL, 0, NULL); - if (sqlite3_open(efile, &edb) != SQLITE_OK) { - ERROR(("sqlite3_open failed for %s: %s\n", efile, sqlite3_errmsg(edb))) - } - sqlite3_key(edb, key, strlen(key)); - sqlite3_exec(edb, "PRAGMA cache_size = 0;", NULL, 0, NULL); - sqlite3_exec(edb, "SELECT count(*) FROM sqlite_master;", NULL, 0, NULL); + if(inkey != NULL) sqlite3_key(indb, inkey, strlen(inkey)); + if(strcmp(infile, ":memory:") == 0) { + sqlite3_exec(indb, "CREATE TABLE t1(a,b);", NULL, 0, NULL); + sqlite3_exec(indb, "BEGIN;", NULL, 0, NULL); + for(int i = 0; i < 524288; i++) { + sqlite3_exec(indb, "INSERT INTO t1(a,b) VALUES ('0123456789abcdefghijklmnopqrstuvwxyz', ')!@#$%^&*(ABCDEFGHIJKLMNOPQRSTUVWXYZ');", NULL, 0, NULL); + } + sqlite3_exec(indb, "COMMIT;", NULL, 0, NULL); + } + if (sqlite3_open(outfile, &outdb) != SQLITE_OK) { ERROR(("sqlite3_open failed for %s: %s\n", outfile, sqlite3_errmsg(outdb))) } - sqlite3_exec(outdb, "PRAGMA cache_size = 0;", NULL, 0, NULL); - /* + if(outkey != NULL) { + sqlite3_key(outdb, outkey, strlen(outkey)); + } + sqlite3_exec(outdb, "SELECT count(*) FROM sqlite_master;", NULL, 0, NULL); + sqlite3_backup *backup = sqlite3_backup_init(outdb, "main", indb, "main"); if(backup == NULL) { ERROR(("sqlite3_backup failed %s\n", sqlite3_errmsg(outdb))) @@ -46,30 +96,52 @@ int main(int argc, char **argv) { ERROR(("sqlite3_backup_step from indb to outdb failed %d, %s, %s\n", rc, sqlite3_errmsg(indb), sqlite3_errmsg(outdb))) } sqlite3_backup_finish(backup); - */ - /* - */ - sqlite3_backup *backup = sqlite3_backup_init(edb, "main", indb, "main"); - if(backup == NULL) { - ERROR(("sqlite3_backup failed %s\n", sqlite3_errmsg(indb))) + if((rc = sqlite3_prepare_v2(outdb, "PRAGMA integrity_check;", -1, &stmt, NULL)) == SQLITE_OK) { + if ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + printf("%s\n", sqlite3_column_text(stmt, 0)); + } else { + ERROR(("error stepping integrity statement %d: %s\n", rc, sqlite3_errmsg(outdb))) + } + } else { + ERROR(("error preparing integrity statement %d: %s\n", rc, sqlite3_errmsg(outdb))) } - if((rc = sqlite3_backup_step(backup, -1)) != SQLITE_DONE) { - ERROR(("sqlite3_backup_step from indb to edb failed %d, %s, %s\n", rc, sqlite3_errmsg(indb), sqlite3_errmsg(edb))) - } - sqlite3_backup_finish(backup); + sqlite3_finalize(stmt); + if((rc = sqlite3_prepare_v2(outdb, "PRAGMA page_size;", -1, &stmt, NULL)) == SQLITE_OK) { + if ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + printf("%d\n", sqlite3_column_int(stmt, 0)); + } else { + ERROR(("error stepping page statement %d: %s\n", rc, sqlite3_errmsg(outdb))) + } + } else { + ERROR(("error preparing page statement %d: %s\n", rc, sqlite3_errmsg(outdb))) + } + sqlite3_finalize(stmt); - backup = sqlite3_backup_init(outdb, "main", edb, "main"); - if(backup == NULL) { - ERROR(("sqlite3_backup failed %s\n", sqlite3_errmsg(edb))) + if((rc = sqlite3_prepare_v2(outdb, "SELECT count(*) FROM t1;", -1, &stmt, NULL)) == SQLITE_OK) { + if ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + printf("%d\n", sqlite3_column_int(stmt, 0)); + } else { + ERROR(("error stepping count statement %d: %s\n", rc, sqlite3_errmsg(outdb))) + } + } else { + ERROR(("error preparing count statement %d: %s\n", rc, sqlite3_errmsg(outdb))) } - if((rc = sqlite3_backup_step(backup, -1)) != SQLITE_DONE) { - ERROR(("sqlite3_backup_step failed from edb to outdb %d, %s, %s\n", rc, sqlite3_errmsg(edb), sqlite3_errmsg(outdb))) + sqlite3_finalize(stmt); + + if((rc = sqlite3_prepare_v2(outdb, "select max(length(a)), min(length(a)), min(length(b)), min(length(b)) from t1;", -1, &stmt, NULL)) == SQLITE_OK) { + if ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + printf("%d %d %d %d\n", sqlite3_column_int(stmt, 0), sqlite3_column_int(stmt, 1), sqlite3_column_int(stmt, 2), sqlite3_column_int(stmt, 3)); + } else { + ERROR(("error stepping max statement %d: %s\n", rc, sqlite3_errmsg(outdb))) + } + } else { + ERROR(("error preparing max statement %d: %s\n", rc, sqlite3_errmsg(outdb))) } - sqlite3_backup_finish(backup); + sqlite3_finalize(stmt); + sqlite3_close(indb); - sqlite3_close(edb); sqlite3_close(outdb); } diff --git a/decrypt.c b/decrypt.c index 3aa1e3c..b22ce56 100644 --- a/decrypt.c +++ b/decrypt.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -12,20 +13,62 @@ #define ERROR(X) {printf("[ERROR] iteration %d: ", i); printf X;fflush(stdout);} #define PAGESIZE 1024 -#define PBKDF2_ITER 4000 +#define PBKDF2_ITER 64000 #define FILE_HEADER_SZ 16 +const char *usage = + "Usage: decrypt [options]\n" + "\n" + "Options :\n" + "\n" + "\t-f filename input filename (default sqlcipher.db).\n" + "\t-o filename output filename (default sqlite.db).\n" + "\t-k key key (default testkey).\n" + "\t-h help (this text)\n" + "\n" + "\n" + ; +const char *optstring = "hi:o:k:"; + +int parse_args(int argc, char **argv, char **key, char **input, char **output) +{ + int c; + + while((c = getopt(argc, argv, optstring)) != -1) { + switch (c) { + case 'i': + *input = optarg; + break; + case 'o': + *output = optarg; + break; + case 'k': + *key = optarg; + break; + case 'h': + default: + fputs(usage, stderr); + return 0; + } + } + + return 1; +} + + int main(int argc, char **argv) { - const char* infile = "sqlcipher.db"; - const char* outfile = "sqlite.db"; - char *pass= "test123"; - int i, csz, tmp_csz, key_sz, iv_sz; + char* infile = "sqlcipher.db"; + char* outfile = "sqlite.db"; + char *pass= "testkey"; + int i, csz, tmp_csz, key_sz, iv_sz, block_sz, hmac_sz, reserve_sz; FILE *infh, *outfh; int read, written; unsigned char *inbuffer, *outbuffer, *salt, *out, *key, *iv; EVP_CIPHER *evp_cipher; EVP_CIPHER_CTX ectx; + if (!parse_args(argc, argv, &pass, &infile, &outfile)) return 1; + OpenSSL_add_all_algorithms(); evp_cipher = (EVP_CIPHER *) EVP_get_cipherbyname("aes-256-cbc"); @@ -35,14 +78,31 @@ int main(int argc, char **argv) { iv_sz = EVP_CIPHER_iv_length(evp_cipher); iv = malloc(iv_sz); + + hmac_sz = EVP_MD_size(EVP_sha1()); + + block_sz = EVP_CIPHER_block_size(evp_cipher); + reserve_sz = iv_sz + hmac_sz; + reserve_sz = ((reserve_sz % block_sz) == 0) ? reserve_sz : ((reserve_sz / block_sz) + 1) * block_sz; + inbuffer = (unsigned char*) malloc(PAGESIZE); outbuffer = (unsigned char*) malloc(PAGESIZE); salt = malloc(FILE_HEADER_SZ); - infh = fopen(infile, "r"); - outfh = fopen(outfile, "w"); - + infh = fopen(infile, "rb"); + + if(infh == NULL) { + printf("unable to open input file %s\n", infile); + exit(1); + } + + outfh = fopen(outfile, "wb"); + if(outfh == NULL) { + printf("unable to open output file %s\n", outfile); + exit(1); + } + read = fread(inbuffer, 1, PAGESIZE, infh); /* read the first page */ memcpy(salt, inbuffer, FILE_HEADER_SZ); /* first 16 bytes are the random database salt */ @@ -51,12 +111,12 @@ int main(int argc, char **argv) { memset(outbuffer, 0, PAGESIZE); out = outbuffer; - memcpy(iv, inbuffer + PAGESIZE - iv_sz, iv_sz); /* last iv_sz bytes are the initialization vector */ + memcpy(iv, inbuffer + PAGESIZE - reserve_sz, iv_sz); /* last iv_sz bytes are the initialization vector */ EVP_CipherInit(&ectx, evp_cipher, NULL, NULL, 0); EVP_CIPHER_CTX_set_padding(&ectx, 0); EVP_CipherInit(&ectx, NULL, key, iv, 0); - EVP_CipherUpdate(&ectx, out, &tmp_csz, inbuffer + FILE_HEADER_SZ, PAGESIZE - iv_sz - FILE_HEADER_SZ); + EVP_CipherUpdate(&ectx, out, &tmp_csz, inbuffer + FILE_HEADER_SZ, PAGESIZE - reserve_sz - FILE_HEADER_SZ); csz = tmp_csz; out += tmp_csz; EVP_CipherFinal(&ectx, out, &tmp_csz); @@ -69,14 +129,14 @@ int main(int argc, char **argv) { printf("wrote page %d\n", 0); for(i = 1; (read = fread(inbuffer, 1, PAGESIZE, infh)) > 0 ;i++) { - memcpy(iv, inbuffer + PAGESIZE - iv_sz, iv_sz); /* last iv_sz bytes are the initialization vector */ + memcpy(iv, inbuffer + PAGESIZE - reserve_sz, iv_sz); /* last iv_sz bytes are the initialization vector */ memset(outbuffer, 0, PAGESIZE); out = outbuffer; EVP_CipherInit(&ectx, evp_cipher, NULL, NULL, 0); EVP_CIPHER_CTX_set_padding(&ectx, 0); EVP_CipherInit(&ectx, NULL, key, iv, 0); - EVP_CipherUpdate(&ectx, out, &tmp_csz, inbuffer, PAGESIZE - iv_sz); + EVP_CipherUpdate(&ectx, out, &tmp_csz, inbuffer, PAGESIZE - reserve_sz); csz = tmp_csz; out += tmp_csz; EVP_CipherFinal(&ectx, out, &tmp_csz); diff --git a/loadtest.c b/loadtest.c new file mode 100644 index 0000000..358e6cc --- /dev/null +++ b/loadtest.c @@ -0,0 +1,110 @@ +/* + gcc loadtest.c -I../sqlcipher-internal -DSQLITE_HAS_CODEC -DSQLCIPHER_CRYPTO_CC -o loadtest -framework Security -framework Foundation +*/ + +#include +#include +#include + +#define ERROR(X) {printf("[ERROR] iteration %d: ", i); printf X;fflush(stdout);} + +#define LOOPS 1500 +int main(int argc, char **argv) { + sqlite3 *db; + const char *file= "loadtest.db"; + struct timeval stop, start; + int insert_rows = 10000; + int row, rc; + sqlite3_stmt *stmt; + int i; + + const char *key = "test"; + char *buffer = malloc(LOOPS); + sqlite3_int64 current, highwater; + + for(i = 0; i < LOOPS; i++) { + *(buffer+i) = 'a'; + } + + if (sqlite3_open(file, &db) != SQLITE_OK) { + ERROR(("error opening database %s\n", sqlite3_errmsg(db))) + exit(1); + } + + if(sqlite3_key(db, key, strlen(key)) != SQLITE_OK) { + ERROR(("error setting key %s\n", sqlite3_errmsg(db))) + exit(1); + } + + if(sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1(data);", NULL, NULL, NULL) != SQLITE_OK) { + ERROR(("error creating table %s\n", sqlite3_errmsg(db))) + exit(1); + } + +/* + if(sqlite3_exec(db, "PRAGMA cipher_memory_security = OFF;", NULL, NULL, NULL) != SQLITE_OK) { + exit(1); + } +*/ + + for(i = 0; i < LOOPS; i++) { + +/* + if(sqlite3_exec(db, "DELETE FROM t1;", NULL, NULL, NULL) != SQLITE_OK) { + exit(1); + } + + if(sqlite3_exec(db, "VACUUM;", NULL, NULL, NULL) != SQLITE_OK) { + exit(1); + } +*/ + + gettimeofday(&start, NULL); + + if(sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK) { + ERROR(("error starting transaction %s\n", sqlite3_errmsg(db))) + exit(1); + } + + if(sqlite3_prepare_v2(db, "INSERT INTO t1(data) VALUES (?);", -1, &stmt, NULL) != SQLITE_OK) { + ERROR(("error preparing insert %s\n", sqlite3_errmsg(db))) + exit(1); + } + + for(row = 0; row < insert_rows; row++) { + //sqlite3_bind_text(stmt, 1, buffer, i, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 1, buffer, i, SQLITE_STATIC); + if (sqlite3_step(stmt) != SQLITE_DONE) { + ERROR(("error inserting row %s\n", sqlite3_errmsg(db))) + exit(1); + } + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + + sqlite3_status64(SQLITE_STATUS_MEMORY_USED, ¤t, &highwater, 0); + printf("SQLITE_STATUS_MEMORY_USED current=%lld, highwater=%lld\n", current, highwater); + sqlite3_status64(SQLITE_STATUS_MALLOC_SIZE, ¤t, &highwater, 0); + printf("SQLITE_STATUS_MALLOC_SIZE current=%lld, highwater=%lld\n", current, highwater); + sqlite3_status64(SQLITE_STATUS_MALLOC_COUNT, ¤t, &highwater, 0); + printf("SQLITE_STATUS_MALLOC_COUNT current=%lld, highwater=%lld\n", current, highwater); + sqlite3_status64(SQLITE_STATUS_PAGECACHE_USED, ¤t, &highwater, 0); + printf("SQLITE_STATUS_PAGECACHE_USED current=%lld, highwater=%lld\n", current, highwater); + sqlite3_status64(SQLITE_STATUS_PAGECACHE_OVERFLOW, ¤t, &highwater, 0); + printf("SQLITE_STATUS_PAGECACHE_OVERFLOW current=%lld, highwater=%lld\n", current, highwater); + sqlite3_status64(SQLITE_STATUS_PAGECACHE_SIZE, ¤t, &highwater, 0); + printf("SQLITE_STATUS_PAGECACHE_SIZE current=%lld, highwater=%lld\n", current, highwater); + + if(sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) { + ERROR(("error committing transaction %s\n", sqlite3_errmsg(db))) + exit(1); + } + gettimeofday(&stop, NULL); + + //printf("Inserted %d records with each containing %d chars in %lu ms\n", insert_rows, i, ((stop.tv_sec - start.tv_sec) * 1000) + ((stop.tv_usec - start.tv_usec) / 1000) ); + printf("%d\t%lu\n", i, ((stop.tv_sec - start.tv_sec) * 1000) + ((stop.tv_usec - start.tv_usec) / 1000) ); + } + + sqlite3_close(db); + free((void *)buffer); +} diff --git a/verify.c b/verify.c new file mode 100644 index 0000000..46812ae --- /dev/null +++ b/verify.c @@ -0,0 +1,230 @@ +/* + verifies per-page HMAC on a SQLCipher 3 database at the page level, bypassing sqlite internals + + SQLCipher 3: + clang verify.c -DPAGESIZE=1024 -DPBKDF2_ITER=64000 -DHMAC_SHA1 -I /usr/local/opt/openssl/include -L /usr/local/opt/openssl/lib -l crypto -o verify + + SQLCipher 4: + clang verify.c -DPAGESIZE=4096 -DPBKDF2_ITER=256000 -DHMAC_SHA512 -I /usr/local/opt/openssl/include -L /usr/local/opt/openssl/lib -l crypto -o verify +*/ + +#include +#include +#include +#include +#include +#include + +#define FILE_HEADER_SZ 16 + +#ifndef PAGESIZE +#define PAGESIZE 1024 +#endif + +#ifndef PBKDF2_ITER +#define PBKDF2_ITER 64000 +#endif + +#ifndef HMAC_SALT_MAS +#define HMAC_SALT_MASK 0x3a +#endif + +#ifndef FAST_PBKDF2_ITER +#define FAST_PBKDF2_ITER 2 +#endif + +#if defined(HMAC_SHA512) +#define HMAC_FUNC EVP_sha512() +#elif defined(HMAC_SHA256) +#define HMAC_FUNC EVP_sha256() +#else +#define HMAC_FUNC EVP_sha1() +#endif + +static int cipher_hex2int(char c) { + return (c>='0' && c<='9') ? (c)-'0' : + (c>='A' && c<='F') ? (c)-'A'+10 : + (c>='a' && c<='f') ? (c)-'a'+10 : 0; +} + +static void cipher_hex2bin(const unsigned char *hex, int sz, unsigned char *out){ + int i; + for(i = 0; i < sz; i += 2){ + out[i/2] = (cipher_hex2int(hex[i])<<4) | cipher_hex2int(hex[i+1]); + } +} + +static int cipher_isHex(const unsigned char *hex, int sz){ + int i; + for(i = 0; i < sz; i++) { + unsigned char c = hex[i]; + if ((c < '0' || c > '9') && + (c < 'A' || c > 'F') && + (c < 'a' || c > 'f')) { + return 0; + } + } + return 1; +} + +#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) +static HMAC_CTX *HMAC_CTX_new(void) +{ + HMAC_CTX *ctx = OPENSSL_malloc(sizeof(*ctx)); + if (ctx != NULL) { + HMAC_CTX_init(ctx); + } + return ctx; +} + +static void HMAC_CTX_free(HMAC_CTX *ctx) +{ + if (ctx != NULL) { + HMAC_CTX_cleanup(ctx); + OPENSSL_free(ctx); + } +} +#endif + +const char *usage = + "Usage: verify [options]\n" + "\n" + "Options :\n" + "\n" + "\t-f filename input filename (default sqlcipher.db).\n" + "\t-k key key (default testkey).\n" + "\t-h help (this text)\n" + "\n" + "\n" + ; +const char *optstring = "hf:k:"; + +int parse_args(int argc, char **argv, char **key, char **file) +{ + int c; + + while((c = getopt(argc, argv, optstring)) != -1) { + switch (c) { + case 'f': + *file = optarg; + break; + case 'k': + *key = optarg; + break; + case 'h': + default: + fputs(usage, stderr); + return 0; + } + } + + return 1; +} + + +int main(int argc, char **argv) { + char* file = "sqlcipher.db"; + char *pass= "testkey"; + int i, rc, key_sz, iv_sz, block_sz, hmac_sz, reserve_sz, read, problems = 0; + unsigned int outlen; + FILE *infh; + unsigned char *buffer, *salt, *hmac_salt, *out, *key, *hmac_key; + EVP_CIPHER *evp_cipher; + HMAC_CTX* hctx = NULL; + + if (!parse_args(argc, argv, &pass, &file)) return 1; + +#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) + OpenSSL_add_all_algorithms(); +#endif + + evp_cipher = (EVP_CIPHER *) EVP_get_cipherbyname("aes-256-cbc"); + + key_sz = EVP_CIPHER_key_length(evp_cipher); + key = malloc(key_sz); + hmac_key = malloc(key_sz); + + iv_sz = EVP_CIPHER_iv_length(evp_cipher); + hmac_sz = EVP_MD_size(HMAC_FUNC); + block_sz = EVP_CIPHER_block_size(evp_cipher); + reserve_sz = iv_sz + hmac_sz; + reserve_sz = ((reserve_sz % block_sz) == 0) ? reserve_sz : ((reserve_sz / block_sz) + 1) * block_sz; + + buffer = (unsigned char*) malloc(PAGESIZE); + out = (unsigned char*) malloc(hmac_sz); + salt = (unsigned char*) malloc(FILE_HEADER_SZ); + hmac_salt = (unsigned char*) malloc(FILE_HEADER_SZ); + + infh = fopen(file, "rb"); + + if(infh == NULL) { + printf("unable to open input file %s\n", file); + goto error; + } + + read = fread(buffer, 1, PAGESIZE, infh); + memcpy(salt, buffer, FILE_HEADER_SZ); + memcpy(hmac_salt, buffer, FILE_HEADER_SZ); + + for(i = 0; i < FILE_HEADER_SZ; i++) { + hmac_salt[i] ^= HMAC_SALT_MASK; + } + + if (strlen(pass) == ((key_sz * 2) + 3) && strncmp(pass ,"x'", 2) == 0 && cipher_isHex((const unsigned char*) pass + 2, key_sz * 2)) { + cipher_hex2bin((const unsigned char *) pass + 2, strlen(pass) - 2, key); + } else { + if(!PKCS5_PBKDF2_HMAC((const char *)pass, strlen(pass), salt, FILE_HEADER_SZ, PBKDF2_ITER, HMAC_FUNC, key_sz, key)) goto error; + } + if(!PKCS5_PBKDF2_HMAC((const char *)key, key_sz, hmac_salt, FILE_HEADER_SZ, FAST_PBKDF2_ITER, HMAC_FUNC, key_sz, hmac_key)) goto error; + + i = 1; + if((hctx = HMAC_CTX_new()) == NULL) goto error; + if(!HMAC_Init_ex(hctx, hmac_key, key_sz, HMAC_FUNC, NULL)) goto error; + if(!HMAC_Update(hctx, buffer + FILE_HEADER_SZ, PAGESIZE - FILE_HEADER_SZ - reserve_sz + iv_sz)) goto error; + if(!HMAC_Update(hctx, (const unsigned char *) &i, sizeof(int))) goto error; + if(!HMAC_Final(hctx, out, &outlen)) goto error; + HMAC_CTX_free(hctx); + + if(memcmp(out, buffer + PAGESIZE - reserve_sz + iv_sz, outlen) != 0) { + problems++; + printf("page %d is invalid\n", i); + } + + for(i = 2; (read = fread(buffer, 1, PAGESIZE, infh)) > 0; i++) { + if((hctx = HMAC_CTX_new()) == NULL) goto error; + if(!HMAC_Init_ex(hctx, hmac_key, key_sz, HMAC_FUNC, NULL)) goto error; + if(!HMAC_Update(hctx, buffer, PAGESIZE - reserve_sz + iv_sz)) goto error; + if(!HMAC_Update(hctx, (const unsigned char *) &i, sizeof(int))) goto error; + if(!HMAC_Final(hctx, out, &outlen)) goto error; + HMAC_CTX_free(hctx); + + if(memcmp(out, buffer + PAGESIZE - reserve_sz + iv_sz, outlen) != 0) { + problems++; + printf("page %d is invalid\n", i); + } + } + +rc = 0; + +if(problems > 0) { + printf("scanned %d pages and found %d invalid, database is corrupt\n", i-1, problems); +} else { + printf("scanned %d pages and 0 are invalid, database is intact\n", i-1); +} +goto close; + +error: + printf("an error occurred\n"); + rc = 1; + +close: + if(infh != NULL) fclose(infh); + if(buffer != NULL) free(buffer); + if(key != NULL) free(key); + if(hmac_key != NULL) free(hmac_key); + if(salt != NULL) free(salt); + if(hmac_salt != NULL) free(hmac_salt); + + return rc; +} +