Skip to main content
Join the official 2020 Python Developers SurveyStart the survey!

Encrypted fields for Django dealing with pgcrypto postgres extension.

Project description


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.


  • postgres with pgcrypto
  • Supports Django 1.11.x, 2.2.x, and 3.0.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.


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.


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

# Used by TextHMACField and PGPSymmetricKeyField if not specified by the db

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).
    # Other installed apps

    # This db will use the default keys above
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'pgcryto_fields',
        'USER': 'pgcryto_fields',
        'PASSWORD': 'xxxx',
        'HOST': '',
        'PORT': 5432,
        'OPTIONS': {
            'sslmode': 'require',
    'diff_keys': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'pgcryto_fields_diff',
        'USER': 'pgcryto_fields_diff',
        'PASSWORD': 'xxxx',
        'HOST': '',
        '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

sec   2048R/21 2014-10-23
uid                  Test Key <>
ssb   2048R/42 2014-10-23

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


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.*


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
  • DecimalPGPPublicKeyField
  • FloatPGPPublicKeyField

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
  • DecimalPGPSymmetricKeyField
  • FloatPGPSymmetricKeyField

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
DecimalField DecimalPGPPublicKeyField DecimalPGPSymmetricKeyField
FloatField FloatPGPPublicKeyField FloatPGPSymmetricKeyField

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


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()

    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()


Data is automatically encrypted when inserted into the database.


>>> 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.


>>> # 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


>>> 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.


>>> 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>]



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(
    'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'

Workaround for Django 2.0.x and lower:

from django.db import models

items = EncryptedFKModel.objects.filter(
    'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'

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

Security Limitations

Taken direction from the PostgreSQL documentation:

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.


Master (unreleased)


  • Added support for Django 3.x
  • Updated requirements_dev.txt


  • Fixed regression in the definition of EmailPGPPublicKeyField (#77)
  • Removed dead code (remove_validators and RemoveMaxLengthValidatorMixin)
  • Updated requirements_dev.txt


  • Added new DecimalFields for both public and symmetric key (#64)
  • Added new FloatFields for both public and symmetric key (#64)
  • Added new TimeFields for both public and symmetric key (#64)
  • Added support for different keys based on database (#67)


  • Added auto-decryption of all encrypted fields including FK tables
  • Removed django-pgcrypto-fields aggregates, PGPManager and PGPAdmin as they are no longer needed
  • Added support for get_or_create() and update_or_create() (#27)
  • Added support for get_by_natural_key() (#23)
  • Added support for only() and defer() as they were not supported with PGPManager
  • Added support for distinct() (Django 2.1+ with workaround available for 2.0 and lower)
  • Separated out dev requirements from requirements
  • Updated packaging / to include long description
  • Added AUTHORS and updated CONTRIBUTING
  • Updated TravisCI to use Xenial to gain Python 3.7 in the matrix


  • Added __range lookup for Date / DateTime fields (#59)
  • Remove compatibility for Django 1.8, 1.9, and 1.10 (#62)
  • Improved
    • check for Python 3.5+
    • updated classifiers
  • Improved make file for release to use twine
  • Added additional shields to README
  • Updated Travis config to include Python 3.5 and 3.6
  • Refactored lookups and mixins


  • Invalid release, bump to 2.3.1


  • Merge .coveragerc into setup.cfg
  • Added .gitignore file
  • Updated out-dated requirements (latest versions of Flake8 and pycodestyle are incompatible with each other)
  • Updated README with better explanations of the fields
  • Implemented DatePGPPublicKeyField and DateTimePGPPublicKeyField


  • Added support for Django 2.x+
  • Updated requirements for testing
  • Updated travis config with Python 3.6 and additional environments


Thanks to @peterfarrell:

  • Add support for DatePGPSymmetricKeyField and DateTimePGPSymmetricKeyField including support for serializing / deserializing django form fields.
  • Add support for auto decryption of symmetric key and public key fields via the PGPManager (and support for disabling it in the Django Admin via the PGPAdmin)


  • Remove compatibility for Django 1.7.
  • Add compatibility for Django 1.10.
  • Add Django 1.9 to the travis matrix.


  • Exclude tests app from distributed package.


  • Rename package from pgcrypto_fields to pgcrypto.


  • Make get_placeholder accepts a new argument compiler
  • Fix buggy import to Aggregate

Note: these changes have been done for django > 1.8.0.


  • Remove MaxLengthValidator from email fields.


  • Avoid setting max_length on PGP fields.


  • Allow/check NULL values for: TextDigestField; TextHMACField; EmailPGPPublicKeyField; IntegerPGPPublicKeyField; TextPGPPublicKeyField; EmailPGPSymmetricKeyField. IntegerPGPSymmetricKeyField. TextPGPSymmetricKeyField.


  • Fix casting bug when sending negative values to integer fields.


  • Add EmailPGPPublicKeyField and EmailPGPSymmetricKeyField.


  • Rename the following fields: PGPPublicKeyField to TextPGPPublicKeyField; PGPSymmetricKeyField to TextPGPSymmetricKeyField; DigestField to TextDigestField; HMACField to TextHMACField.
  • Add new integer fields: IntegerPGPPublicKeyField; IntegerPGPSymmetricKeyField.


  • Make accessing decrypted value transparent. Fix bug when field had a string representation of memoryview for PGP and keyed hash fields.


  • Fix EncryptedProxyField to select the correct item.


  • Access PGPPublicKeyField and PGPSymmetricKeySQL decrypted values with field's proxy _decrypted.
  • Remove descriptor for field's name and raw value.


  • Add hash based lookup for DigestField and HMACField.
  • Add DigestField, HMACField, PGPPublicKeyAggregate, PGPSymmetricKeyAggregate.


  • Add decryption through an aggregate class.
  • Add encryption when inserting data to the database.

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for django-pgcrypto-fields, version 2.5.2
Filename, size File type Python version Upload date Hashes
Filename, size django_pgcrypto_fields-2.5.2-py3-none-any.whl (12.8 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size django-pgcrypto-fields-2.5.2.tar.gz (16.3 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page