PRAGMA cipher_default_use_hmac = OFF;

2012-03-01 10:59:03 -0500

We needed a new PRAGMA! These things happen.

With the release of SQLCipher 2.0, a PRAGMA was added (cipher_use_hmac) which disabled the new per-page HMAC protection feature before opening a database, so that a developer wishing to run SQLCipher 2.0 against a 1.x database could do so. This was important to us because we wanted to provide an easy way for developers to migrate to the new database format. Generally speaking, you'd want to do something like this:

  1. Open the legacy database, set your key
  2. Call PRAGMA cipher_use_hmac = OFF; before opening the legacy database
  3. ATTACH a new, empty database, which will be encrypted by default and have HMAC on by default
  4. Copy your database into the attached database using sqlcipher_export()

That scenario looks about like this on the command line:

$> ./sqlite3 my-legacy-encrypted.db
sqlite> PRAGMA key = 's3cr37';
sqlite> PRAGMA cipher_use_hmac = OFF;
sqlite> ATTACH DATABASE 'new-sqlcipher2.db' AS newdb;
sqlite> SELECT sqlcipher_export('newdb');
sqlite> DETACH DATABASE newdb;

But there are other scenarios, ones in which we really needed to be able to ATTACH one legacy database to another (e.g. data replication). Even though we'd have called PRAGMA cipher_use_hmac = OFF; when opening the first database, the ATTACH operation assumed the new default format, and would report back "database is encrypted or is not a database."

We didn't wish to modify the ATTACH command, and we didn't want to change the behavior of cipher_use_hmac, as the latter's behavior is necessary for developers who wish to migrate and upgrade their databases. Instead, Stephen came up with a new PRAGMA, cipher_default_use_hmac, which is a globally applied setting in SQLCipher, allowing you to turn HMAC protection on and off before you open or ATTACH another encrypted database, so you only need to bother with one setting in your app. Here's an example (where custom_data_replicator() is a made-up function):

$> ./sqlite3 my-legacy-encrypted.db
sqlite> PRAGMA cipher_default_use_hmac = OFF;
sqlite> PRAGMA key = 's3cr37';
sqlite> ATTACH DATABASE 'remote.db' AS remote;
sqlite> SELECT custom_data_replicator('remote');
sqlite> DETACH DATABASE remote;

Pretty dang handy and available now on the master branch—stay tuned to the mailing list for details on new tags and release numbers. You can see the changes here along with updated unit tests.

Meanwhile, I've made some mods to SQLCipherManager to support this specific scenario. Setting the property useHMACPageProtection causes the new global PRAGMA to be called before a database is opened, rather than the older cipher_use_hmac. This allows you to attach 1.x or 2.x databases at will, by changing the setting when you need to, like so:

self.dbManager = [SQLCipherManager sharedManager];
[self.dbManager setUseHMACPageProtection:NO];
@try {
  [self.dbManager execute:
    [NSString stringWithFormat: @"ATTACH DATABASE '%@' AS remote;", [replicaURL path]];
  [self.dbManager beginTransaction];
  [self.dbManager execute:@"SELECT custom_data_replicator('remote');"];
  [self.dbManager commitTransaction];
  [self.dbManager execute:@"DETACH DATABASE remote;"];
}
@catch (NSException *exception) {
  // SQLCipher's execute: command now provides handy exceptions!
  // For traditional error handling, use execute:error: instead.
  NSLog(@"Command failed: %@", [exception reason]);
}
blog comments powered by Disqus