• Stars
    star
    231
  • Rank 173,434 (Top 4 %)
  • Language
    Python
  • License
    BSD 2-Clause "Sim...
  • Created about 10 years ago
  • Updated 27 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Transparent field level encryption for Django using the pgcrypto postgresql extension.

django-pgcrypto-fields

Latest Release Python Versions Build Status Requirements Status Updates Coverage Status

django-pgcrypto-fields is a Django extension which relies upon pgcrypto to encrypt and decrypt data for fields.

Requirements

  • postgres with pgcrypto
  • Supports Django 2.2.x, 3.0.x, 3.1.x and 3.2.x
  • Compatible with Python 3 only

Last version of this library that supports Django 1.8.x, 1.9.x, 1.10.x was django-pgcrypto-fields 2.2.0.

Last version of this library that supports Django 2.0.x and 2.1.x was was django-pgcrypto-fields 2.5.2.

Installation

Install package

pip install django-pgcrypto-fields

Django settings

Our library support different crypto keys for multiple databases by defining the keys in your DATABASES settings.

In settings.py:

import os
BASEDIR = os.path.dirname(os.path.dirname(__file__))
PUBLIC_PGP_KEY_PATH = os.path.abspath(os.path.join(BASEDIR, 'public.key'))
PRIVATE_PGP_KEY_PATH = os.path.abspath(os.path.join(BASEDIR, 'private.key'))

# Used by PGPPublicKeyField used by default if not specified by the db
PUBLIC_PGP_KEY = open(PUBLIC_PGP_KEY_PATH).read()
PRIVATE_PGP_KEY = open(PRIVATE_PGP_KEY_PATH).read()

# Used by TextHMACField and PGPSymmetricKeyField if not specified by the db
PGCRYPTO_KEY='ultrasecret'

DIFF_PUBLIC_PGP_KEY_PATH = os.path.abspath(
    os.path.join(BASEDIR, 'tests/keys/public_diff.key')
)
DIFF_PRIVATE_PGP_KEY_PATH = os.path.abspath(
    os.path.join(BASEDIR, 'tests/keys/private_diff.key')
)

# And add 'pgcrypto' to `INSTALLED_APPS` to create the extension for
# pgcrypto (in a migration).
INSTALLED_APPS = (
    'pgcrypto',
    # Other installed apps
)

DATABASES = {
    # This db will use the default keys above
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'pgcryto_fields',
        'USER': 'pgcryto_fields',
        'PASSWORD': 'xxxx',
        'HOST': 'psql.test.com',
        'PORT': 5432,
        'OPTIONS': {
            'sslmode': 'require',
        }
    },
    'diff_keys': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'pgcryto_fields_diff',
        'USER': 'pgcryto_fields_diff',
        'PASSWORD': 'xxxx',
        'HOST': 'psqldiff.test.com',
        'PORT': 5432,
        'OPTIONS': {
            'sslmode': 'require',
        },
        'PGCRYPTO_KEY': 'djangorocks',
        'PUBLIC_PGP_KEY': open(DIFF_PUBLIC_PGP_KEY_PATH, 'r').read(),
        'PRIVATE_PGP_KEY': open(DIFF_PRIVATE_PGP_KEY_PATH, 'r').read(),
    },
}

Generate GPG keys if using Public Key Encryption

The public key is going to encrypt the message and the private key will be needed to decrypt the content. The following commands have been taken from the pgcrypto documentation (see Generating PGP Keys with GnuPG).

Generating a public and a private key (The preferred key type is "DSA and Elgamal".):

$ gpg --gen-key
$ gpg --list-secret-keys

/home/bob/.gnupg/secring.gpg
---------------------------
sec   2048R/21 2014-10-23
uid                  Test Key <[email protected]>
ssb   2048R/42 2014-10-23


$ gpg -a --export 42 > public.key
$ gpg -a --export-secret-keys 21 > private.key

Limitations

This library currently does not support Public Key Encryption private keys that are password protected yet. See Issue #89 to help implement it.

Upgrading to 2.4.0 from previous versions

The 2.4.0 version of this library received a large rewrite in order to support auto-decryption when getting encrypted field data as well as the ability to filter on encrypted fields without using the old PGPCrypto aggregate functions available in previous versions.

The following items in this library have been removed and therefore references in your application to these items need to be removed as well:

  • managers.PGPManager
  • admin.PGPAdmin
  • aggregates.*

Fields

django-pgcrypto-fields has 3 kinds of fields:

  • Hash based fields
  • Public Key (PGP) fields
  • Symmetric fields

Hash Based Fields

Supported hash based fields are:

  • TextDigestField
  • TextHMACField

TextDigestField is hashed in the database using the digest pgcrypto function using the sha512 algorithm.

TextHMACField is hashed in the database using the hmac pgcrypto function using a key and the sha512 algorithm. This is similar to the digest version however the hash can only be recalculated knowing the key. This prevents someone from altering the data and also changing the hash to match.

Public Key Encryption Fields

Supported PGP public key fields are:

  • CharPGPPublicKeyField
  • EmailPGPPublicKeyField
  • TextPGPPublicKeyField
  • DatePGPPublicKeyField
  • DateTimePGPPublicKeyField
  • TimePGPPublicKeyField
  • IntegerPGPPublicKeyField
  • BigIntegerPGPPublicKeyField
  • DecimalPGPPublicKeyField
  • FloatPGPPublicKeyField
  • BooleanPGPPublicKeyField

Public key encryption creates a token generated with a public key to encrypt the data and a private key to decrypt it.

Public and private keys can be set in settings with PUBLIC_PGP_KEY and PRIVATE_PGP_KEY.

Symmetric Key Encryption Fields

Supported PGP symmetric key fields are:

  • CharPGPSymmetricKeyField
  • EmailPGPSymmetricKeyField
  • TextPGPSymmetricKeyField
  • DatePGPSymmetricKeyField
  • DateTimePGPSymmetricKeyField
  • TimePGPSymmetricKeyField
  • IntegerPGPSymmetricKeyField
  • BigIntegerPGPSymerticKeyField
  • DecimalPGPSymmetricKeyField
  • FloatPGPSymmetricKeyField
  • BooleanPGPSymmetricKeyField

Encrypt and decrypt the data with settings.PGCRYPTO_KEY which acts like a password.

Django Model Field Equivalents

Django Field Public Key Field Symmetric Key Field
CharField CharPGPPublicKeyField CharPGPSymmetricKeyField
EmailField EmailPGPPublicKeyField EmailPGPSymmetricKeyField
TextField TextPGPPublicKeyField TextPGPSymmetricKeyField
DateField DatePGPPublicKeyField DatePGPSymmetricKeyField
DateTimeField DateTimePGPPublicKeyField DateTimePGPSymmetricKeyField
TimeField TimePGPPublicKeyField TimePGPSymmetricKeyField
IntegerField IntegerPGPPublicKeyField IntegerPGPSymmetricKeyField
BigIntegerField BigIntegerPGPPublicKeyField BigIntegerPGPSymmetricKeyField
DecimalField DecimalPGPPublicKeyField DecimalPGPSymmetricKeyField
FloatField FloatPGPPublicKeyField FloatPGPSymmetricKeyField
BooleanField BooleanPGPPublicKeyField BooleanPGPSymmetricKeyField

Other Django model fields are not currently supported. Pull requests are welcomed.

Usage

Model Definition

from django.db import models

from pgcrypto import fields

class MyModel(models.Model):
    digest_field = fields.TextDigestField()
    digest_with_original_field = fields.TextDigestField(original='pgp_sym_field')
    hmac_field = fields.TextHMACField()
    hmac_with_original_field = fields.TextHMACField(original='pgp_sym_field')

    email_pgp_pub_field = fields.EmailPGPPublicKeyField()
    integer_pgp_pub_field = fields.IntegerPGPPublicKeyField()
    pgp_pub_field = fields.TextPGPPublicKeyField()
    date_pgp_pub_field = fields.DatePGPPublicKeyField()
    datetime_pgp_pub_field = fields.DateTimePGPPublicKeyField()
    time_pgp_pub_field = fields.TimePGPPublicKeyField()
    decimal_pgp_pub_field = fields.DecimalPGPPublicKeyField()
    float_pgp_pub_field = fields.FloatPGPPublicKeyField()
    boolean_pgp_pub_field = fields.BooleanPGPPublicKeyField()
    
    email_pgp_sym_field = fields.EmailPGPSymmetricKeyField()
    integer_pgp_sym_field = fields.IntegerPGPSymmetricKeyField()
    pgp_sym_field = fields.TextPGPSymmetricKeyField()
    date_pgp_sym_field = fields.DatePGPSymmetricKeyField()
    datetime_pgp_sym_field = fields.DateTimePGPSymmetricKeyField()
    time_pgp_sym_field = fields.TimePGPSymmetricKeyField()
    decimal_pgp_sym_field = fields.DecimalPGPSymmetricKeyField()
    float_pgp_sym_field = fields.FloatPGPSymmetricKeyField()
    boolean_pgp_sym_field = fields.BooleanPGPSymmetricKeyField()

Encrypting

Data is automatically encrypted when inserted into the database.

Example:

>>> MyModel.objects.create(value='Value to be encrypted...')

Hash fields can have hashes auto updated if you use the original attribute. This attribute allows you to indicate another field name to base the hash value on.

from django.db import models

from pgcrypto import fields

class User(models.Model):
    first_name = fields.TextPGPSymmetricKeyField(max_length=20, verbose_name='First Name')
    first_name_hashed = fields.TextHMACField(original='first_name') 

In the above example, if you specify the optional original attribute it would take the unencrypted value from the first_name model field as the input value to create the hash. If you did not specify an original attribute, the field would work as it does now and would remain backwards compatible.

PGP fields

When accessing the field name attribute on a model instance we are getting the decrypted value.

Example:

>>> # When using a PGP public key based encryption
>>> my_model = MyModel.objects.get()
>>> my_model.value
'Value decrypted'

Filtering encrypted values is now handled automatically as of 2.4.0. And aggregate methods are not longer supported and have been removed from the library.

Also, auto-decryption is support for select_related() models.

from django.db import models

from pgcrypto import fields


class EncryptedFKModel(models.Model):
    fk_pgp_sym_field = fields.TextPGPSymmetricKeyField(blank=True, null=True)


class EncryptedModel(models.Model):
    pgp_sym_field = fields.TextPGPSymmetricKeyField(blank=True, null=True)
    fk_model = models.ForeignKey(
        EncryptedFKModel, blank=True, null=True, on_delete=models.CASCADE
    )

Example:

>>> import EncryptedModel
>>> my_model = EncryptedModel.objects.get().select_releated('fk_model')
>>> my_model.pgp_sym_field
'Value decrypted'
>>> my_model.fk_model.fk_pgp_sym_field
'Value decrypted'
Hash fields

To filter hash based values we need to compare hashes. This is achieved by using a __hash_of lookup.

Example:

>>> my_model = MyModel.objects.filter(digest_field__hash_of='value')
[<MyModel: MyModel object>]
>>> my_model = MyModel.objects.filter(hmac_field__hash_of='value')
[<MyModel: MyModel object>]

Limitations

Unique Indexes

It is usually not possible to index a bytea column in the database as the value in the index exceeds the the pgsql's maximum length allowed for an index (8192 bytes). One solution is to create a digest message of the value that you want unique and apply the unique constraint to the digest.

You can use the hash field ability to auto-create digest on the value of another field in the same model using the original argument. In the example below, a digest is created for unencrypted value that is in the name field when the model is saved or updated. A unique constraint exists on the name_digest so no two digests are allowed. Note well that bulk updates do NOT cause hashes to be updated.

from django.db import models
from pgcrypto import fields

class Product(models.Model):
    name_digest = fields.TextDigestField(original='name')
    name = fields.TextPGPSymmetricKeyField()

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['name_digest', ],
                name='name_digest_unique'
            )
       ]

.distinct('encrypted_field_name')

Due to a missing feature in the Django ORM, using distinct() on an encrypted field does not work for Django 2.0.x and lower.

The normal distinct works on Django 2.1.x and higher:

items = EncryptedFKModel.objects.filter(
    pgp_sym_field__startswith='P'
).only(
    'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'
).distinct(
    'pgp_sym_field'
)

Workaround for Django 2.0.x and lower:

from django.db import models

items = EncryptedFKModel.objects.filter(
    pgp_sym_field__startswith='P'
).annotate(
    _distinct=models.F('pgp_sym_field')
).only(
    'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'
).distinct(
    '_distinct'
)

This works because the annotated field is auto-decrypted by Django as a F field and that field is used in the distinct().

Migrating existing fields into PGCrypto Fields

Migrating existing fields into PGCrypto Fields is not performed by this library. You will need to migrate the data in a forwards migration or other means. The only migration that is supported except to create/activate the pgcrypto extension in Postgres.

Migrating data is complicated as there might be a few things to consider such as:

  • the shape of the data
  • validations/constrains done on the table/model/form and anywhere else

The library has no way of doing all these guesses or to make all these decisions.

If you need to migrate data from unencrypted fields to encrypted fields, three ways to solve it:

  1. When there's no data in the db it should be possible to start from scratch by recreating the db
  2. When there's no data in the table it should be possible to recreate the table
  3. When there's data or if the project is shared it should be possible to do it in a non destructive way

Option 1: No data is in the db

  1. Drop the database
  2. Squash the migrations
  3. Recreate the db

Option 2: No data in the table

  1. Create a migration to drop the table
  2. Create a new migration for the table with the encrypted field
  3. Optionally squash the migration

Option 3: Migrating in a non-destructive way

The goal here is to be able to use to legacy field if something goes wrong.

Part 1:

  1. Create new field
  2. When data is saved write both to legacy and new field
  3. Create a data migration to cast data from legacy field to new field
  4. check existing data from legacy and new field are the same if possible

Part 2:

  1. Rename the fields and drop legacy fields
  2. Update the code to use only the new field

Common Errors

psycopg2.errors.UndefinedFunction: function pgp_sym_encrypt(numeric, unknown) does not exist

This commonly means you do not have the pgcrypto extension installed in Postgres. Run the migration available in this library or install it manually in pgsql console.

Security Limitations

Taken direction from the PostgreSQL documentation:

https://www.postgresql.org/docs/9.6/static/pgcrypto.html#AEN187024

All pgcrypto functions run inside the database server. That means that all the data and passwords move between pgcrypto and client applications in clear text. Thus you must:

  1. Connect locally or use SSL connections.
  2. Trust both system and database administrator.

If you cannot, then better do crypto inside client application.

The implementation does not resist side-channel attacks. For example, the time required for a pgcrypto decryption function to complete varies among ciphertexts of a given size.

More Repositories

1

django-wkhtmltopdf

Django Wrapper to the PDF Renderer: wkhtmltopdf
Python
325
star
2

angular-bind-html-compile

Directive that calls $compile on trusted HTML, allowing directives in an API response.
JavaScript
151
star
3

django-user-management

User management model mixins and api views.
Python
57
star
4

django-orderable

Add manual sort order to Django objects via an abstract base class and admin classes
Python
41
star
5

django-gcal

A Django application allowing developers to synchronise instances of their models with Google Calendar.
Python
25
star
6

feincms-articles

Extensible FeinCMS content article
Python
16
star
7

incuna-mail

Pythonic utility for sending template based emails with Django.
Python
13
star
8

authentic

Authentic 2 - Versatile Identity Server from http://repos.entrouvert.org/authentic.git
Python
7
star
9

django-boolean-sum

Sums of boolean fields for Django which don't die on PostgreSQL
Python
7
star
10

incuna-test-utils

TestCases and other helpers for testing Django apps.
Python
7
star
11

django-discussion

Simple django discussion application
Python
7
star
12

angular-sticky-footer

6
star
13

angular-user-management

Angular library to interface a registration/user API
JavaScript
6
star
14

django-extensible-profiles

Extensible profiles
Python
6
star
15

django-never-cache-post

Python
5
star
16

feincms-tinymce-plugins

A collection of plugins for TinyMCE which are useful which tie in nicely to FeinCMS.
Python
5
star
17

ircbot

A Python IRC Bot
Python
4
star
18

django-forum-pinax

A Pinax-aware fork of django-forum from the Googlecode svn
Python
4
star
19

django-cookie-message

Python
4
star
20

django-registration

Unofficial django-registration mirror, patched with file from bitbucket issue #26
Python
4
star
21

incuna-templates

Config template files
4
star
22

djangular-rest-framework

AngularJS interface for Django Rest Framework (DRF)
JavaScript
4
star
23

open-app-template

Open source Django application template
Python
3
star
24

incuna-bookmarks

Python
3
star
25

django-storages-cloudfront

Extend django-storages storages.backends.s3.S3Storage to use a Amazon CloudFront domain for urls.
Python
3
star
26

angular-flowplayer

Angular directive for Flowplayer
JavaScript
3
star
27

angular-reverse-url

URL reversing based on route controllers or names
JavaScript
3
star
28

dashboard

Incuna's Internal Dashboard of Awesome
Python
3
star
29

incuna-surveys

Allows the creation of custom questionnaires and surveys via the Django admin.
JavaScript
3
star
30

django-field-cryptography

Python
3
star
31

django-settingsjs

Django configurable JavaScript settings
Python
2
star
32

angular-external-link-interceptor

Angular External Link Interceptor
JavaScript
2
star
33

angular-input-fields

Angular library for common input fields
JavaScript
2
star
34

feincms-jobs

A FeinCMS plugin for job adverts
Python
2
star
35

django-rewrite-external-links

Rewrite all external (off-site) links to go via a message page, using a middleware class
Python
2
star
36

incuna-sass

Incuna's Sass Library
CSS
2
star
37

index-old

Python
2
star
38

screenshot-server

Small flask web server that calls the os x system screen capture command
2
star
39

incuna-countries

List of countries as a django application, taken from ISO 3166
Python
2
star
40

feincms-pages-api

Python
2
star
41

django-form-models

A set of models for django forms
Python
2
star
42

incuna-auth

Python
2
star
43

github_hooks

Python
2
star
44

angular-token-auth

Services and HTTP interceptor for token based HTTP authentication
JavaScript
2
star
45

django-user-deletion

Management commands to notify and delete inactive django users
Python
2
star
46

heroku-s3-pgbackups

Backs up heroku Pgbackups to external S3
1
star
47

rest-framework-push-notifications

Python
1
star
48

angular-language-select

Angular language select
JavaScript
1
star
49

django-user-mixins

Django User model mixins - successor to https://github.com/incuna/django-user-management
Python
1
star
50

solr-local

Shell
1
star
51

heroku-buildpack-python-test

Python
1
star
52

angular-articles

Articles of content for AngularJS
JavaScript
1
star
53

qanda

Q&A
Python
1
star
54

test-htmllint

Test htmllint rules on isolated files
HTML
1
star
55

pg-copy-to-dev

Copy a remote postgres database to a dev machine
1
star
56

eslint-config-incuna

Incuna eslint shared config
1
star
57

jamjs.touchSwipe

http://labs.skinkers.com/touchSwipe/ packaged for jamjs.org
JavaScript
1
star
58

incuna-pigeon

Incuna Pigeon abstracts the sending of notifications
Python
1
star
59

buildout_handlers

Wrap the django.core.handlers.modpython.handler with buildout's bin/django script
Python
1
star
60

django-cas

Python
1
star
61

zepto.splitDate

Zepto split data select widget plugin
JavaScript
1
star
62

style-guide

Incuna style guide
HTML
1
star
63

incuna-groups

Python
1
star
64

zepto.touch2mouse

Zepto plugin to map touch events to corresponding mouse events if no touch events are available.
JavaScript
1
star
65

django-image-api

Python
1
star
66

incuna-profiles

Incuna specific extensions for django-extensible-profiles
Python
1
star
67

django-fckconnector

FCKEditor connector for Django, forked from below svn
Python
1
star
68

node-yaml-merge

provides helper nodeJS module to merge multiple yaml files in order
JavaScript
1
star
69

incuna-news

Simple django news app using django.contrib.syndication framework.
Python
1
star
70

makeTextFiles

JavaScript
1
star
71

incuna-transitions

Incuna transitions library
CSS
1
star
72

grunt-gettext-checker

Grunt module which provides a task for checking differences between template.pot files and corresponding .po files
JavaScript
1
star
73

incuna-pagination

Templates and tags for easy display of paginated data.
Python
1
star