You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							165 lines
						
					
					
						
							5.5 KiB
						
					
					
				
			
		
		
	
	
							165 lines
						
					
					
						
							5.5 KiB
						
					
					
				# sqlite/pysqlcipher.py
 | 
						|
# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
 | 
						|
# <see AUTHORS file>
 | 
						|
#
 | 
						|
# This module is part of SQLAlchemy and is released under
 | 
						|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
 | 
						|
 | 
						|
"""
 | 
						|
.. dialect:: sqlite+pysqlcipher
 | 
						|
    :name: pysqlcipher
 | 
						|
    :dbapi: sqlcipher 3 or pysqlcipher
 | 
						|
    :connectstring: sqlite+pysqlcipher://:passphrase@/file_path[?kdf_iter=<iter>]
 | 
						|
 | 
						|
    Dialect for support of DBAPIs that make use of the
 | 
						|
    `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.
 | 
						|
 | 
						|
 | 
						|
Driver
 | 
						|
------
 | 
						|
 | 
						|
Current dialect selection logic is:
 | 
						|
 | 
						|
* If the :paramref:`_sa.create_engine.module` parameter supplies a DBAPI module,
 | 
						|
  that module is used.
 | 
						|
* Otherwise for Python 3, choose https://pypi.org/project/sqlcipher3/
 | 
						|
* If not available, fall back to https://pypi.org/project/pysqlcipher3/
 | 
						|
* For Python 2, https://pypi.org/project/pysqlcipher/ is used.
 | 
						|
 | 
						|
.. warning:: The ``pysqlcipher3`` and ``pysqlcipher`` DBAPI drivers are no
 | 
						|
   longer maintained; the ``sqlcipher3`` driver as of this writing appears
 | 
						|
   to be current.  For future compatibility, any pysqlcipher-compatible DBAPI
 | 
						|
   may be used as follows::
 | 
						|
 | 
						|
        import sqlcipher_compatible_driver
 | 
						|
 | 
						|
        from sqlalchemy import create_engine
 | 
						|
 | 
						|
        e = create_engine(
 | 
						|
            "sqlite+pysqlcipher://:password@/dbname.db",
 | 
						|
            module=sqlcipher_compatible_driver
 | 
						|
        )
 | 
						|
 | 
						|
These drivers make use of the SQLCipher engine. This system essentially
 | 
						|
introduces new PRAGMA commands to SQLite which allows the setting of a
 | 
						|
passphrase and other encryption parameters, allowing the database file to be
 | 
						|
encrypted.
 | 
						|
 | 
						|
 | 
						|
Connect Strings
 | 
						|
---------------
 | 
						|
 | 
						|
The format of the connect string is in every way the same as that
 | 
						|
of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
 | 
						|
"password" field is now accepted, which should contain a passphrase::
 | 
						|
 | 
						|
    e = create_engine('sqlite+pysqlcipher://:testing@/foo.db')
 | 
						|
 | 
						|
For an absolute file path, two leading slashes should be used for the
 | 
						|
database name::
 | 
						|
 | 
						|
    e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db')
 | 
						|
 | 
						|
A selection of additional encryption-related pragmas supported by SQLCipher
 | 
						|
as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed
 | 
						|
in the query string, and will result in that PRAGMA being called for each
 | 
						|
new connection.  Currently, ``cipher``, ``kdf_iter``
 | 
						|
``cipher_page_size`` and ``cipher_use_hmac`` are supported::
 | 
						|
 | 
						|
    e = create_engine('sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000')
 | 
						|
 | 
						|
.. warning:: Previous versions of sqlalchemy did not take into consideration
 | 
						|
   the encryption-related pragmas passed in the url string, that were silently
 | 
						|
   ignored. This may cause errors when opening files saved by a
 | 
						|
   previous sqlalchemy version if the encryption options do not match.
 | 
						|
 | 
						|
 | 
						|
Pooling Behavior
 | 
						|
----------------
 | 
						|
 | 
						|
The driver makes a change to the default pool behavior of pysqlite
 | 
						|
as described in :ref:`pysqlite_threading_pooling`.   The pysqlcipher driver
 | 
						|
has been observed to be significantly slower on connection than the
 | 
						|
pysqlite driver, most likely due to the encryption overhead, so the
 | 
						|
dialect here defaults to using the :class:`.SingletonThreadPool`
 | 
						|
implementation,
 | 
						|
instead of the :class:`.NullPool` pool used by pysqlite.  As always, the pool
 | 
						|
implementation is entirely configurable using the
 | 
						|
:paramref:`_sa.create_engine.poolclass` parameter; the :class:`.
 | 
						|
StaticPool` may
 | 
						|
be more feasible for single-threaded use, or :class:`.NullPool` may be used
 | 
						|
to prevent unencrypted connections from being held open for long periods of
 | 
						|
time, at the expense of slower startup time for new connections.
 | 
						|
 | 
						|
 | 
						|
"""  # noqa
 | 
						|
 | 
						|
from __future__ import absolute_import
 | 
						|
 | 
						|
from .pysqlite import SQLiteDialect_pysqlite
 | 
						|
from ... import pool
 | 
						|
from ... import util
 | 
						|
 | 
						|
 | 
						|
class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
 | 
						|
    driver = "pysqlcipher"
 | 
						|
    supports_statement_cache = True
 | 
						|
 | 
						|
    pragmas = ("kdf_iter", "cipher", "cipher_page_size", "cipher_use_hmac")
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def dbapi(cls):
 | 
						|
        if util.py3k:
 | 
						|
            try:
 | 
						|
                import sqlcipher3 as sqlcipher
 | 
						|
            except ImportError:
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                return sqlcipher
 | 
						|
 | 
						|
            from pysqlcipher3 import dbapi2 as sqlcipher
 | 
						|
 | 
						|
        else:
 | 
						|
            from pysqlcipher import dbapi2 as sqlcipher
 | 
						|
 | 
						|
        return sqlcipher
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_pool_class(cls, url):
 | 
						|
        return pool.SingletonThreadPool
 | 
						|
 | 
						|
    def on_connect_url(self, url):
 | 
						|
        super_on_connect = super(
 | 
						|
            SQLiteDialect_pysqlcipher, self
 | 
						|
        ).on_connect_url(url)
 | 
						|
 | 
						|
        # pull the info we need from the URL early.  Even though URL
 | 
						|
        # is immutable, we don't want any in-place changes to the URL
 | 
						|
        # to affect things
 | 
						|
        passphrase = url.password or ""
 | 
						|
        url_query = dict(url.query)
 | 
						|
 | 
						|
        def on_connect(conn):
 | 
						|
            cursor = conn.cursor()
 | 
						|
            cursor.execute('pragma key="%s"' % passphrase)
 | 
						|
            for prag in self.pragmas:
 | 
						|
                value = url_query.get(prag, None)
 | 
						|
                if value is not None:
 | 
						|
                    cursor.execute('pragma %s="%s"' % (prag, value))
 | 
						|
            cursor.close()
 | 
						|
 | 
						|
            if super_on_connect:
 | 
						|
                super_on_connect(conn)
 | 
						|
 | 
						|
        return on_connect
 | 
						|
 | 
						|
    def create_connect_args(self, url):
 | 
						|
        plain_url = url._replace(password=None)
 | 
						|
        plain_url = plain_url.difference_update_query(self.pragmas)
 | 
						|
        return super(SQLiteDialect_pysqlcipher, self).create_connect_args(
 | 
						|
            plain_url
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
dialect = SQLiteDialect_pysqlcipher
 |