1# sqlite/pysqlcipher.py
2# Copyright (C) 2005-2021 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"""
9.. dialect:: sqlite+pysqlcipher
10 :name: pysqlcipher
11 :dbapi: pysqlcipher
12 :connectstring: sqlite+pysqlcipher://:passphrase/file_path[?kdf_iter=<iter>]
13 :url: https://pypi.python.org/pypi/pysqlcipher
14
15 ``pysqlcipher`` is a fork of the standard ``pysqlite`` driver to make
16 use of the `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.
17
18 ``pysqlcipher3`` is a fork of ``pysqlcipher`` for Python 3. This dialect
19 will attempt to import it if ``pysqlcipher`` is non-present.
20
21 .. versionadded:: 1.1.4 - added fallback import for pysqlcipher3
22
23 .. versionadded:: 0.9.9 - added pysqlcipher dialect
24
25Driver
26------
27
28The driver here is the
29`pysqlcipher <https://pypi.python.org/pypi/pysqlcipher>`_
30driver, which makes use of the SQLCipher engine. This system essentially
31introduces new PRAGMA commands to SQLite which allows the setting of a
32passphrase and other encryption parameters, allowing the database
33file to be encrypted.
34
35`pysqlcipher3` is a fork of `pysqlcipher` with support for Python 3,
36the driver is the same.
37
38Connect Strings
39---------------
40
41The format of the connect string is in every way the same as that
42of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
43"password" field is now accepted, which should contain a passphrase::
44
45 e = create_engine('sqlite+pysqlcipher://:testing@/foo.db')
46
47For an absolute file path, two leading slashes should be used for the
48database name::
49
50 e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db')
51
52Additional encryption-related pragmas must be executed manually,
53using the ``first_connect`` pool event. A selection of the pragmas supported
54by SQLCipher is documented at
55https://www.zetetic.net/sqlcipher/sqlcipher-api/.
56
57.. warning:: Previously the documentation wrongly stated that these
58 pragma could be passed in the url string. This has never worked
59 for the 1.3 series of sqlalchemy. The 1.4 series adds proper
60 support for them when passed in the url string.
61
62
63Pooling Behavior
64----------------
65
66The driver makes a change to the default pool behavior of pysqlite
67as described in :ref:`pysqlite_threading_pooling`. The pysqlcipher driver
68has been observed to be significantly slower on connection than the
69pysqlite driver, most likely due to the encryption overhead, so the
70dialect here defaults to using the :class:`.SingletonThreadPool`
71implementation,
72instead of the :class:`.NullPool` pool used by pysqlite. As always, the pool
73implementation is entirely configurable using the
74:paramref:`_sa.create_engine.poolclass` parameter; the :class:`.StaticPool`
75may
76be more feasible for single-threaded use, or :class:`.NullPool` may be used
77to prevent unencrypted connections from being held open for long periods of
78time, at the expense of slower startup time for new connections.
79
80
81""" # noqa
82
83from __future__ import absolute_import
84
85from .pysqlite import SQLiteDialect_pysqlite
86from ... import pool
87from ...engine import url as _url
88
89
90class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
91 driver = "pysqlcipher"
92
93 @classmethod
94 def dbapi(cls):
95 try:
96 from pysqlcipher import dbapi2 as sqlcipher
97 except ImportError as e:
98 try:
99 from pysqlcipher3 import dbapi2 as sqlcipher
100 except ImportError:
101 raise e
102 return sqlcipher
103
104 @classmethod
105 def get_pool_class(cls, url):
106 return pool.SingletonThreadPool
107
108 def connect(self, *cargs, **cparams):
109 passphrase = cparams.pop("passphrase", "")
110
111 conn = super(SQLiteDialect_pysqlcipher, self).connect(
112 *cargs, **cparams
113 )
114 conn.execute('pragma key="%s"' % passphrase)
115
116 return conn
117
118 def create_connect_args(self, url):
119 super_url = _url.URL(
120 url.drivername,
121 username=url.username,
122 host=url.host,
123 database=url.database,
124 query=url.query,
125 )
126 c_args, opts = super(
127 SQLiteDialect_pysqlcipher, self
128 ).create_connect_args(super_url)
129 opts["passphrase"] = url.password
130 return c_args, opts
131
132
133dialect = SQLiteDialect_pysqlcipher