From ed257adba0d64bda7d258af7c1625c5b37516ae1 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sun, 24 Apr 2016 15:49:52 +0200 Subject: [PATCH 01/13] python setup.py install to find sub-packages --- .gitignore | 3 +++ setup.py | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5a6f88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*pyc +*egg-info +*egg diff --git a/setup.py b/setup.py index bc28ef4..96ba0ee 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,12 @@ -from distutils.core import setup -from distutils.command.install import INSTALL_SCHEMES import os +from distutils.command.install import INSTALL_SCHEMES +from setuptools import setup, find_packages + root = os.path.dirname(os.path.abspath(__file__)) os.chdir(root) -VERSION = '0.1.1' +VERSION = '0.1.2' # Make data go to the right place. # http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb @@ -23,7 +24,7 @@ url="http://github.com/codasus/django-sqlcipher", license="Creative Commons Attribution-ShareAlike 3.0 Unported License", platforms=["any"], - packages=['sqlcipher'], + packages=find_packages(), classifiers=[ "Development Status :: 3 - Alpha", "Framework :: Django", From f7c30bca55c0fed998349155c7dbe61e8e580d49 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sun, 24 Apr 2016 16:32:42 +0200 Subject: [PATCH 02/13] management commands for cli pragma key --- sqlcipher/management/__init__.py | 0 sqlcipher/management/commands/__init__.py | 0 sqlcipher/management/commands/_mixins.py | 19 +++++++++++++++++++ sqlcipher/management/commands/migrate.py | 12 ++++++++++++ sqlcipher/management/commands/runserver.py | 6 ++++++ 5 files changed, 37 insertions(+) create mode 100644 sqlcipher/management/__init__.py create mode 100644 sqlcipher/management/commands/__init__.py create mode 100644 sqlcipher/management/commands/_mixins.py create mode 100644 sqlcipher/management/commands/migrate.py create mode 100644 sqlcipher/management/commands/runserver.py diff --git a/sqlcipher/management/__init__.py b/sqlcipher/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sqlcipher/management/commands/__init__.py b/sqlcipher/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sqlcipher/management/commands/_mixins.py b/sqlcipher/management/commands/_mixins.py new file mode 100644 index 0000000..994c4e1 --- /dev/null +++ b/sqlcipher/management/commands/_mixins.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import print_function +from django.conf import settings +from getpass import getpass + + +class PromptForPragmaKeyMixin(object): + """"" + This is a universal command that you can have other management commands + inherit from in case they need database access. + """ + + def handle(self, *args, **options): + if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY: + print("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key") + key = getpass("Key: ") + settings.PRAGMA_KEY = key + super(PromptForPragmaKeyMixin, self).handle(*args, **options) diff --git a/sqlcipher/management/commands/migrate.py b/sqlcipher/management/commands/migrate.py new file mode 100644 index 0000000..51d1a5b --- /dev/null +++ b/sqlcipher/management/commands/migrate.py @@ -0,0 +1,12 @@ + +from django.core.management.commands.migrate import Command as BaseCommand + +from ._mixins import PromptForPragmaKeyMixin + + +class Command(PromptForPragmaKeyMixin, BaseCommand): + """ + Before migrating, we need to know the pragma key to access the database. If + it does not exist, retrieve it from command line input. + """ + pass diff --git a/sqlcipher/management/commands/runserver.py b/sqlcipher/management/commands/runserver.py new file mode 100644 index 0000000..080ef48 --- /dev/null +++ b/sqlcipher/management/commands/runserver.py @@ -0,0 +1,6 @@ +from django.core.management.commands.runserver import Command as RunserverCommand + +from ._mixins import PromptForPragmaKeyMixin + +class Command(PromptForPragmaKeyMixin, RunserverCommand): + pass From 058e3b8034ad4439cab376881eec7088ca8202af Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sun, 24 Apr 2016 16:44:09 +0200 Subject: [PATCH 03/13] Add readme stuff about CLI and setting key at runtime --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7a649c6..39c7373 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**django-sqlcipher** +# django-sqlcipher SQLCipher is an SQLite extension that provides transparent 256-bit AES encryption of database files. @@ -8,7 +8,7 @@ This app does it for you. You only need to specify the database key in your proj For more about SQLCipher take a look at [http://sqlcipher.net/](http://sqlcipher.net/). -**Requirements** +## Requirements * python-sqlcipher (Python compiled with SQLCipher support) @@ -16,13 +16,13 @@ For more about python-sqlcipher take a look at: [https://code.launchpad.net/~jplacerda/+junk/python-sqlcipher](https://code.launchpad.net/~jplacerda/+junk/python-sqlcipher) -**Installation** +## Installation `pip install git+http://github.com/codasus/django-sqlcipher#egg=sqlcipher` Or manually place it on your `PYTHON_PATH`. -**Configuration** +## Configuration Open your project's `settings.py` file and: @@ -30,11 +30,18 @@ Open your project's `settings.py` file and: 2. Set your database engine to `sqlcipher.backend`. -3. Put the following line where you want: +3. Optionally, type your key into your settings file (**unsafe**): `PRAGMA_KEY = "YOUR DATABASE KEY"` -**MIT License** +You may not wish to expose your encryption key in a file. +django-sqlcipher ships with custom management commands that will prompt +for the key when invoking `runserver` and `migrate`, however you +should consider how you want to set `django.conf.settings.PRAGMA_KEY` +at runtime in your production environment. + + +## MIT License
Copyright (c) 2011 Caio Ariede and Codasus Technologies.
 

From baeafc5043120c904f30d25226db0afdcf2e8d26 Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 16:53:28 +0200
Subject: [PATCH 04/13] add loaddata and dumpdata support

---
 sqlcipher/management/commands/dumpdata.py | 6 ++++++
 sqlcipher/management/commands/loaddata.py | 6 ++++++
 2 files changed, 12 insertions(+)
 create mode 100644 sqlcipher/management/commands/dumpdata.py
 create mode 100644 sqlcipher/management/commands/loaddata.py

diff --git a/sqlcipher/management/commands/dumpdata.py b/sqlcipher/management/commands/dumpdata.py
new file mode 100644
index 0000000..fe675b0
--- /dev/null
+++ b/sqlcipher/management/commands/dumpdata.py
@@ -0,0 +1,6 @@
+from django.core.management.commands.dumpdata import Command as DumpdataCommand
+
+from ._mixins import PromptForPragmaKeyMixin
+
+class Command(PromptForPragmaKeyMixin, DumpdataCommand):
+    pass
diff --git a/sqlcipher/management/commands/loaddata.py b/sqlcipher/management/commands/loaddata.py
new file mode 100644
index 0000000..31ad8b8
--- /dev/null
+++ b/sqlcipher/management/commands/loaddata.py
@@ -0,0 +1,6 @@
+from django.core.management.commands.loaddata import Command as LoaddataCommand
+
+from ._mixins import PromptForPragmaKeyMixin
+
+class Command(PromptForPragmaKeyMixin, LoaddataCommand):
+    pass

From 40e4810048ab3d31f619946c195d985ce7098be5 Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 17:06:00 +0200
Subject: [PATCH 05/13] Only prompt once during first runserver (auto-reload of
 runserver will also prompt still)

---
 sqlcipher/management/commands/_mixins.py   | 12 ++++++++----
 sqlcipher/management/commands/runserver.py | 10 +++++++---
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/sqlcipher/management/commands/_mixins.py b/sqlcipher/management/commands/_mixins.py
index 994c4e1..a99f189 100644
--- a/sqlcipher/management/commands/_mixins.py
+++ b/sqlcipher/management/commands/_mixins.py
@@ -5,6 +5,13 @@
 from getpass import getpass
 
 
+def ensure_pragma_key():
+    if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
+        print("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key")
+        key = getpass("Key: ")
+        settings.PRAGMA_KEY = key
+
+
 class PromptForPragmaKeyMixin(object):
     """""
     This is a universal command that you can have other management commands
@@ -12,8 +19,5 @@ class PromptForPragmaKeyMixin(object):
     """
 
     def handle(self, *args, **options):
-        if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
-            print("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key")
-            key = getpass("Key: ")
-            settings.PRAGMA_KEY = key
+        ensure_pragma_key()
         super(PromptForPragmaKeyMixin, self).handle(*args, **options)
diff --git a/sqlcipher/management/commands/runserver.py b/sqlcipher/management/commands/runserver.py
index 080ef48..7f20613 100644
--- a/sqlcipher/management/commands/runserver.py
+++ b/sqlcipher/management/commands/runserver.py
@@ -1,6 +1,10 @@
 from django.core.management.commands.runserver import Command as RunserverCommand
 
-from ._mixins import PromptForPragmaKeyMixin
+from ._mixins import ensure_pragma_key
 
-class Command(PromptForPragmaKeyMixin, RunserverCommand):
-    pass
+
+class Command(RunserverCommand):
+
+    def inner_run(self, *args, **options):
+        ensure_pragma_key()
+        RunserverCommand.inner_run(self, *args, **options)

From b2b726243416e1bdf106bbbd86c4d1d90486fda5 Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 17:49:23 +0200
Subject: [PATCH 06/13] do not pollute stdout (dumpdata produces illegal output
 then)

---
 sqlcipher/management/commands/_mixins.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/sqlcipher/management/commands/_mixins.py b/sqlcipher/management/commands/_mixins.py
index a99f189..3d0333c 100644
--- a/sqlcipher/management/commands/_mixins.py
+++ b/sqlcipher/management/commands/_mixins.py
@@ -1,13 +1,16 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import print_function
+
+import sys
+
 from django.conf import settings
 from getpass import getpass
 
 
 def ensure_pragma_key():
     if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
-        print("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key")
+        sys.stderr.write("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key.\n\n")
         key = getpass("Key: ")
         settings.PRAGMA_KEY = key
 

From ca57916e837faf88ed820b80cbc676689bb12d5b Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 18:49:54 +0200
Subject: [PATCH 07/13] Make compatible with recent Django versions

---
 sqlcipher/backend/base.py | 106 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 105 insertions(+), 1 deletion(-)

diff --git a/sqlcipher/backend/base.py b/sqlcipher/backend/base.py
index c0b2b38..6bebf87 100644
--- a/sqlcipher/backend/base.py
+++ b/sqlcipher/backend/base.py
@@ -1,10 +1,114 @@
-from django.db.backends.sqlite3.base import DatabaseWrapper as BaseDatabaseWrapper
+from __future__ import unicode_literals
+
+from django.db.backends.sqlite3.base import DatabaseWrapper as BaseDatabaseWrapper, \
+    _sqlite_date_extract, _sqlite_date_trunc, _sqlite_datetime_cast_date, \
+    _sqlite_datetime_extract, _sqlite_datetime_trunc, _sqlite_time_extract, \
+    _sqlite_regexp, _sqlite_format_dtdelta, _sqlite_power, FORMAT_QMARK_REGEX
 
 from ..signals import setup
 
+from pysqlcipher import dbapi2 as Database
+
+
+import datetime
+import decimal
+import warnings
+
+from django.conf import settings
+from django.db.backends import utils as backend_utils
+from django.utils import six, timezone
+from django.utils.dateparse import (
+    parse_date, parse_datetime, parse_time,
+)
+from django.utils.deprecation import RemovedInDjango20Warning
+from django.utils.safestring import SafeBytes
+
+try:
+    import pytz
+except ImportError:
+    pytz = None
+
+DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
+
+
+def adapt_datetime_warn_on_aware_datetime(value):
+    # Remove this function and rely on the default adapter in Django 2.0.
+    if settings.USE_TZ and timezone.is_aware(value):
+        warnings.warn(
+            "The SQLite database adapter received an aware datetime (%s), "
+            "probably from cursor.execute(). Update your code to pass a "
+            "naive datetime in the database connection's time zone (UTC by "
+            "default).", RemovedInDjango20Warning)
+        # This doesn't account for the database connection's timezone,
+        # which isn't known. (That's why this adapter is deprecated.)
+        value = value.astimezone(timezone.utc).replace(tzinfo=None)
+    return value.isoformat(str(" "))
+
+
+def decoder(conv_func):
+    """ The Python sqlite3 interface returns always byte strings.
+        This function converts the received value to a regular string before
+        passing it to the receiver function.
+    """
+    return lambda s: conv_func(s.decode('utf-8'))
+
+
+Database.register_converter(str("bool"), decoder(lambda s: s == '1'))
+Database.register_converter(str("time"), decoder(parse_time))
+Database.register_converter(str("date"), decoder(parse_date))
+Database.register_converter(str("datetime"), decoder(parse_datetime))
+Database.register_converter(str("timestamp"), decoder(parse_datetime))
+Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime))
+Database.register_converter(str("decimal"), decoder(backend_utils.typecast_decimal))
+
+Database.register_adapter(datetime.datetime, adapt_datetime_warn_on_aware_datetime)
+Database.register_adapter(decimal.Decimal, backend_utils.rev_typecast_decimal)
+if six.PY2:
+    Database.register_adapter(str, lambda s: s.decode('utf-8'))
+    Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8'))
+
 
 class DatabaseWrapper(BaseDatabaseWrapper):
+    Database = Database
+
     def _cursor(self):
         if self.connection is None:
             setup()
         return super(DatabaseWrapper, self)._cursor()
+
+    def get_new_connection(self, conn_params):
+        conn = Database.connect(**conn_params)
+        conn.create_function("django_date_extract", 2, _sqlite_date_extract)
+        conn.create_function("django_date_trunc", 2, _sqlite_date_trunc)
+        conn.create_function("django_datetime_cast_date", 2, _sqlite_datetime_cast_date)
+        conn.create_function("django_datetime_extract", 3, _sqlite_datetime_extract)
+        conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
+        conn.create_function("django_time_extract", 2, _sqlite_time_extract)
+        conn.create_function("regexp", 2, _sqlite_regexp)
+        conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
+        conn.create_function("django_power", 2, _sqlite_power)
+        return conn
+
+    def create_cursor(self):
+        return self.connection.cursor(factory=SQLiteCursorWrapper)
+
+
+class SQLiteCursorWrapper(Database.Cursor):
+    """
+    Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
+    This fixes it -- but note that if you want to use a literal "%s" in a query,
+    you'll need to use "%%s".
+    """
+    def execute(self, query, params=None):
+        if params is None:
+            return Database.Cursor.execute(self, query)
+        query = self.convert_query(query)
+        return Database.Cursor.execute(self, query, params)
+
+    def executemany(self, query, param_list):
+        query = self.convert_query(query)
+        return Database.Cursor.executemany(self, query, param_list)
+
+    def convert_query(self, query):
+        return FORMAT_QMARK_REGEX.sub('?', query).replace('%%', '%')

From 74599fd5254733b9a2480b4742231b0dbb1323c9 Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 21:42:42 +0200
Subject: [PATCH 08/13] locate ensure_pragman_key more conveniently

---
 sqlcipher/management/commands/_mixins.py | 11 +----------
 sqlcipher/utils.py                       | 15 +++++++++++++++
 2 files changed, 16 insertions(+), 10 deletions(-)
 create mode 100644 sqlcipher/utils.py

diff --git a/sqlcipher/management/commands/_mixins.py b/sqlcipher/management/commands/_mixins.py
index 3d0333c..f0a60ab 100644
--- a/sqlcipher/management/commands/_mixins.py
+++ b/sqlcipher/management/commands/_mixins.py
@@ -2,17 +2,8 @@
 from __future__ import unicode_literals
 from __future__ import print_function
 
-import sys
 
-from django.conf import settings
-from getpass import getpass
-
-
-def ensure_pragma_key():
-    if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
-        sys.stderr.write("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key.\n\n")
-        key = getpass("Key: ")
-        settings.PRAGMA_KEY = key
+from sqlcipher.utils import ensure_pragma_key
 
 
 class PromptForPragmaKeyMixin(object):
diff --git a/sqlcipher/utils.py b/sqlcipher/utils.py
new file mode 100644
index 0000000..a2aa74f
--- /dev/null
+++ b/sqlcipher/utils.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from __future__ import print_function
+
+import sys
+
+from django.conf import settings
+from getpass import getpass
+
+
+def ensure_pragma_key():
+    if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
+        sys.stderr.write("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key.\n\n")
+        key = getpass("Key: ")
+        settings.PRAGMA_KEY = key

From c2d094288a8a9d093f5e538a00dd0ae4651efd4b Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 21:51:47 +0200
Subject: [PATCH 09/13] keys can be utf-8

---
 sqlcipher/signals.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/sqlcipher/signals.py b/sqlcipher/signals.py
index b5087a3..ba6836d 100644
--- a/sqlcipher/signals.py
+++ b/sqlcipher/signals.py
@@ -1,9 +1,11 @@
+from __future__ import unicode_literals
+
 from django.conf import settings
 from django.db.backends.signals import connection_created
 
 
 def sqlite_set_pragma(connection, **kwargs):
-    pragma_sql = "PRAGMA key='%s';" % (settings.PRAGMA_KEY,)
+    pragma_sql = "PRAGMA key='%s';" % (settings.PRAGMA_KEY.decode("utf-8"),)
     cursor = connection.cursor()
     cursor.execute(pragma_sql)
     cursor.close()

From c5e0b07b96767fdd2b8d26cd38b7f53204bc2fed Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 23:49:52 +0200
Subject: [PATCH 10/13] decode cli input

---
 sqlcipher/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sqlcipher/utils.py b/sqlcipher/utils.py
index a2aa74f..5883b33 100644
--- a/sqlcipher/utils.py
+++ b/sqlcipher/utils.py
@@ -12,4 +12,4 @@ def ensure_pragma_key():
     if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
         sys.stderr.write("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key.\n\n")
         key = getpass("Key: ")
-        settings.PRAGMA_KEY = key
+        settings.PRAGMA_KEY = key.decode("utf-8")

From 38bfa68cd49652d9bf3cef03a847c55dc02c9218 Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 24 Apr 2016 23:50:30 +0200
Subject: [PATCH 11/13] do not decode here, assume decoded

---
 sqlcipher/signals.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sqlcipher/signals.py b/sqlcipher/signals.py
index ba6836d..4d9887e 100644
--- a/sqlcipher/signals.py
+++ b/sqlcipher/signals.py
@@ -5,7 +5,7 @@
 
 
 def sqlite_set_pragma(connection, **kwargs):
-    pragma_sql = "PRAGMA key='%s';" % (settings.PRAGMA_KEY.decode("utf-8"),)
+    pragma_sql = "PRAGMA key='%s';" % (settings.PRAGMA_KEY,)
     cursor = connection.cursor()
     cursor.execute(pragma_sql)
     cursor.close()

From 3aae4aa86478ed2b2ae0a37b588fde32f7bb8e9e Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Sun, 13 Aug 2017 14:13:53 +0200
Subject: [PATCH 12/13] Make some method arg patterns more compatible (fixes Dj
 1.11 issues)

---
 sqlcipher/backend/base.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/sqlcipher/backend/base.py b/sqlcipher/backend/base.py
index 6bebf87..70add9d 100644
--- a/sqlcipher/backend/base.py
+++ b/sqlcipher/backend/base.py
@@ -72,10 +72,10 @@ def decoder(conv_func):
 class DatabaseWrapper(BaseDatabaseWrapper):
     Database = Database
 
-    def _cursor(self):
+    def _cursor(self, *args, **kwargs):
         if self.connection is None:
             setup()
-        return super(DatabaseWrapper, self)._cursor()
+        return super(DatabaseWrapper, self)._cursor(*args, **kwargs)
 
     def get_new_connection(self, conn_params):
         conn = Database.connect(**conn_params)
@@ -90,7 +90,7 @@ def get_new_connection(self, conn_params):
         conn.create_function("django_power", 2, _sqlite_power)
         return conn
 
-    def create_cursor(self):
+    def create_cursor(self, *args, **kwargs):
         return self.connection.cursor(factory=SQLiteCursorWrapper)
 
 

From f39ab4762a07c0797cf885a696fa10217d535cc8 Mon Sep 17 00:00:00 2001
From: Benjamin Bach 
Date: Wed, 4 Apr 2018 22:49:10 +0200
Subject: [PATCH 13/13] Fixes for 'create_cursor() takes exactly 1 argument (2
 given)'

---
 sqlcipher/backend/base.py | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/sqlcipher/backend/base.py b/sqlcipher/backend/base.py
index 70add9d..99ba50e 100644
--- a/sqlcipher/backend/base.py
+++ b/sqlcipher/backend/base.py
@@ -72,10 +72,17 @@ def decoder(conv_func):
 class DatabaseWrapper(BaseDatabaseWrapper):
     Database = Database
 
-    def _cursor(self, *args, **kwargs):
-        if self.connection is None:
-            setup()
-        return super(DatabaseWrapper, self)._cursor(*args, **kwargs)
+    def create_cursor(self, name=None):
+        if name:
+            base_cursor = super(DatabaseWrapper, self).create_cursor(name)
+        else:
+            base_cursor = super(DatabaseWrapper, self).create_cursor()
+        return SQLiteCursorWrapper(base_cursor)
+
+    # def _cursor(self, *args, **kwargs):
+    #     if self.connection is None:
+    #         setup()
+    #     return super(DatabaseWrapper, self)._cursor(*args, **kwargs)
 
     def get_new_connection(self, conn_params):
         conn = Database.connect(**conn_params)