pysqlcipher.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. # sqlite/pysqlcipher.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """
  8. .. dialect:: sqlite+pysqlcipher
  9. :name: pysqlcipher
  10. :dbapi: pysqlcipher
  11. :connectstring: sqlite+pysqlcipher://:passphrase/file_path[?kdf_iter=<iter>]
  12. :url: https://pypi.python.org/pypi/pysqlcipher
  13. ``pysqlcipher`` is a fork of the standard ``pysqlite`` driver to make
  14. use of the `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.
  15. ``pysqlcipher3`` is a fork of ``pysqlcipher`` for Python 3. This dialect
  16. will attempt to import it if ``pysqlcipher`` is non-present.
  17. .. versionadded:: 1.1.4 - added fallback import for pysqlcipher3
  18. .. versionadded:: 0.9.9 - added pysqlcipher dialect
  19. Driver
  20. ------
  21. The driver here is the `pysqlcipher <https://pypi.python.org/pypi/pysqlcipher>`_
  22. driver, which makes use of the SQLCipher engine. This system essentially
  23. introduces new PRAGMA commands to SQLite which allows the setting of a
  24. passphrase and other encryption parameters, allowing the database
  25. file to be encrypted.
  26. `pysqlcipher3` is a fork of `pysqlcipher` with support for Python 3,
  27. the driver is the same.
  28. Connect Strings
  29. ---------------
  30. The format of the connect string is in every way the same as that
  31. of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
  32. "password" field is now accepted, which should contain a passphrase::
  33. e = create_engine('sqlite+pysqlcipher://:testing@/foo.db')
  34. For an absolute file path, two leading slashes should be used for the
  35. database name::
  36. e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db')
  37. A selection of additional encryption-related pragmas supported by SQLCipher
  38. as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed
  39. in the query string, and will result in that PRAGMA being called for each
  40. new connection. Currently, ``cipher``, ``kdf_iter``
  41. ``cipher_page_size`` and ``cipher_use_hmac`` are supported::
  42. e = create_engine('sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000')
  43. Pooling Behavior
  44. ----------------
  45. The driver makes a change to the default pool behavior of pysqlite
  46. as described in :ref:`pysqlite_threading_pooling`. The pysqlcipher driver
  47. has been observed to be significantly slower on connection than the
  48. pysqlite driver, most likely due to the encryption overhead, so the
  49. dialect here defaults to using the :class:`.SingletonThreadPool`
  50. implementation,
  51. instead of the :class:`.NullPool` pool used by pysqlite. As always, the pool
  52. implementation is entirely configurable using the
  53. :paramref:`.create_engine.poolclass` parameter; the :class:`.StaticPool` may
  54. be more feasible for single-threaded use, or :class:`.NullPool` may be used
  55. to prevent unencrypted connections from being held open for long periods of
  56. time, at the expense of slower startup time for new connections.
  57. """
  58. from __future__ import absolute_import
  59. from .pysqlite import SQLiteDialect_pysqlite
  60. from ...engine import url as _url
  61. from ... import pool
  62. class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
  63. driver = 'pysqlcipher'
  64. pragmas = ('kdf_iter', 'cipher', 'cipher_page_size', 'cipher_use_hmac')
  65. @classmethod
  66. def dbapi(cls):
  67. try:
  68. from pysqlcipher import dbapi2 as sqlcipher
  69. except ImportError as e:
  70. try:
  71. from pysqlcipher3 import dbapi2 as sqlcipher
  72. except ImportError:
  73. raise e
  74. return sqlcipher
  75. @classmethod
  76. def get_pool_class(cls, url):
  77. return pool.SingletonThreadPool
  78. def connect(self, *cargs, **cparams):
  79. passphrase = cparams.pop('passphrase', '')
  80. pragmas = dict(
  81. (key, cparams.pop(key, None)) for key in
  82. self.pragmas
  83. )
  84. conn = super(SQLiteDialect_pysqlcipher, self).\
  85. connect(*cargs, **cparams)
  86. conn.execute('pragma key="%s"' % passphrase)
  87. for prag, value in pragmas.items():
  88. if value is not None:
  89. conn.execute('pragma %s="%s"' % (prag, value))
  90. return conn
  91. def create_connect_args(self, url):
  92. super_url = _url.URL(
  93. url.drivername, username=url.username,
  94. host=url.host, database=url.database, query=url.query)
  95. c_args, opts = super(SQLiteDialect_pysqlcipher, self).\
  96. create_connect_args(super_url)
  97. opts['passphrase'] = url.password
  98. return c_args, opts
  99. dialect = SQLiteDialect_pysqlcipher