pysqlcipher.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # sqlite/pysqlcipher.py
  2. # Copyright (C) 2005-2022 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: https://www.opensource.org/licenses/mit-license.php
  7. """
  8. .. dialect:: sqlite+pysqlcipher
  9. :name: pysqlcipher
  10. :dbapi: sqlcipher 3 or pysqlcipher
  11. :connectstring: sqlite+pysqlcipher://:passphrase@/file_path[?kdf_iter=<iter>]
  12. Dialect for support of DBAPIs that make use of the
  13. `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.
  14. Driver
  15. ------
  16. Current dialect selection logic is:
  17. * If the :paramref:`_sa.create_engine.module` parameter supplies a DBAPI module,
  18. that module is used.
  19. * Otherwise for Python 3, choose https://pypi.org/project/sqlcipher3/
  20. * If not available, fall back to https://pypi.org/project/pysqlcipher3/
  21. * For Python 2, https://pypi.org/project/pysqlcipher/ is used.
  22. .. warning:: The ``pysqlcipher3`` and ``pysqlcipher`` DBAPI drivers are no
  23. longer maintained; the ``sqlcipher3`` driver as of this writing appears
  24. to be current. For future compatibility, any pysqlcipher-compatible DBAPI
  25. may be used as follows::
  26. import sqlcipher_compatible_driver
  27. from sqlalchemy import create_engine
  28. e = create_engine(
  29. "sqlite+pysqlcipher://:password@/dbname.db",
  30. module=sqlcipher_compatible_driver
  31. )
  32. These drivers make use of the SQLCipher engine. This system essentially
  33. introduces new PRAGMA commands to SQLite which allows the setting of a
  34. passphrase and other encryption parameters, allowing the database file to be
  35. encrypted.
  36. Connect Strings
  37. ---------------
  38. The format of the connect string is in every way the same as that
  39. of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
  40. "password" field is now accepted, which should contain a passphrase::
  41. e = create_engine('sqlite+pysqlcipher://:testing@/foo.db')
  42. For an absolute file path, two leading slashes should be used for the
  43. database name::
  44. e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db')
  45. A selection of additional encryption-related pragmas supported by SQLCipher
  46. as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed
  47. in the query string, and will result in that PRAGMA being called for each
  48. new connection. Currently, ``cipher``, ``kdf_iter``
  49. ``cipher_page_size`` and ``cipher_use_hmac`` are supported::
  50. e = create_engine('sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000')
  51. .. warning:: Previous versions of sqlalchemy did not take into consideration
  52. the encryption-related pragmas passed in the url string, that were silently
  53. ignored. This may cause errors when opening files saved by a
  54. previous sqlalchemy version if the encryption options do not match.
  55. Pooling Behavior
  56. ----------------
  57. The driver makes a change to the default pool behavior of pysqlite
  58. as described in :ref:`pysqlite_threading_pooling`. The pysqlcipher driver
  59. has been observed to be significantly slower on connection than the
  60. pysqlite driver, most likely due to the encryption overhead, so the
  61. dialect here defaults to using the :class:`.SingletonThreadPool`
  62. implementation,
  63. instead of the :class:`.NullPool` pool used by pysqlite. As always, the pool
  64. implementation is entirely configurable using the
  65. :paramref:`_sa.create_engine.poolclass` parameter; the :class:`.
  66. StaticPool` may
  67. be more feasible for single-threaded use, or :class:`.NullPool` may be used
  68. to prevent unencrypted connections from being held open for long periods of
  69. time, at the expense of slower startup time for new connections.
  70. """ # noqa
  71. from __future__ import absolute_import
  72. from .pysqlite import SQLiteDialect_pysqlite
  73. from ... import pool
  74. from ... import util
  75. class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
  76. driver = "pysqlcipher"
  77. supports_statement_cache = True
  78. pragmas = ("kdf_iter", "cipher", "cipher_page_size", "cipher_use_hmac")
  79. @classmethod
  80. def dbapi(cls):
  81. if util.py3k:
  82. try:
  83. import sqlcipher3 as sqlcipher
  84. except ImportError:
  85. pass
  86. else:
  87. return sqlcipher
  88. from pysqlcipher3 import dbapi2 as sqlcipher
  89. else:
  90. from pysqlcipher import dbapi2 as sqlcipher
  91. return sqlcipher
  92. @classmethod
  93. def get_pool_class(cls, url):
  94. return pool.SingletonThreadPool
  95. def on_connect_url(self, url):
  96. super_on_connect = super(
  97. SQLiteDialect_pysqlcipher, self
  98. ).on_connect_url(url)
  99. # pull the info we need from the URL early. Even though URL
  100. # is immutable, we don't want any in-place changes to the URL
  101. # to affect things
  102. passphrase = url.password or ""
  103. url_query = dict(url.query)
  104. def on_connect(conn):
  105. cursor = conn.cursor()
  106. cursor.execute('pragma key="%s"' % passphrase)
  107. for prag in self.pragmas:
  108. value = url_query.get(prag, None)
  109. if value is not None:
  110. cursor.execute('pragma %s="%s"' % (prag, value))
  111. cursor.close()
  112. if super_on_connect:
  113. super_on_connect(conn)
  114. return on_connect
  115. def create_connect_args(self, url):
  116. plain_url = url._replace(password=None)
  117. plain_url = plain_url.difference_update_query(self.pragmas)
  118. return super(SQLiteDialect_pysqlcipher, self).create_connect_args(
  119. plain_url
  120. )
  121. dialect = SQLiteDialect_pysqlcipher