Sqlcipher backend

  • Although this extention’s code is short, it has not been properly peer-reviewed yet and may have introduced vulnerabilities.
  • The code contains minimum values for passphrase length and kdf_iter, as well as a default value for the later. Do not regard these numbers as advice. Consult the docs at http://sqlcipher.net/sqlcipher-api/ and security experts.

Also note that this code relies on pysqlcipher and sqlcipher, and the code there might have vulnerabilities as well, but since these are widely used crypto modules, we can expect “short zero days” there.

sqlcipher_ext API notes

class SqlCipherDatabase(database, passphrase, kdf_iter=64000, \*kwargs*)

Subclass of SqliteDatabase that stores the database encrypted. Instead of the standard sqlite3 backend, it uses pysqlcipher: a python wrapper for sqlcipher, which – in turn – is an encrypted wrapper around sqlite3, so the API is identical to SqliteDatabase’s, except for object construction parameters:

Parameters:
  • database – Path to encrypted database filename to open [or create].
  • passphrase – Database encryption passphrase: should be at least 8 character long (or an error is raised), but it is strongly advised to enforce better passphrase strength criteria in your implementation.
  • kdf_iter – [Optional] number of PBKDF2 iterations.
  • If the database file doesn’t exist, it will be created with encryption by a key derived from passhprase with kdf_iter PBKDF2 iterations.
  • When trying to open an existing database, passhprase and kdf_iter should be identical to the ones used when it was created.

  • rekey(passphrase)

    Parameters:passphrase (str) – New passphrase for database.

    Change the passphrase for database.

Notes:

  • [Hopefully] there’s no way to tell whether the passphrase is wrong or the file is corrupt. In both cases – the first time we try to acces the database – a DatabaseError error is raised, with the exact message: "file is encrypted or is not a database".

    As mentioned above, this only happens when you access the databse, so if you need to know right away whether the passphrase was correct, you can trigger this check by calling [e.g.] get_tables() (see example below).

  • Most applications can expect failed attempts to open the database (common case: prompting the user for passphrase), so the database can’t be hardwired into the Meta of model classes. To defer initialization, pass None in to the database.

Example:

  1. db = SqlCipherDatabase(None)
  2. class BaseModel(Model):
  3. """Parent for all app's models"""
  4. class Meta:
  5. # We won't have a valid db until user enters passhrase.
  6. database = db
  7. # Derive our model subclasses
  8. class Person(BaseModel):
  9. name = TextField(primary_key=True)
  10. right_passphrase = False
  11. while not right_passphrase:
  12. db.init(
  13. 'testsqlcipher.db',
  14. passphrase=get_passphrase_from_user())
  15. try: # Actually execute a query against the db to test passphrase.
  16. db.get_tables()
  17. except DatabaseError as exc:
  18. # This error indicates the password was wrong.
  19. if exc.args[0] == 'file is encrypted or is not a database':
  20. tell_user_the_passphrase_was_wrong()
  21. db.init(None) # Reset the db.
  22. else:
  23. raise exc
  24. else:
  25. # The password was correct.
  26. right_passphrase = True

See also: a slightly more elaborate example.