跳转到主要内容

用于处理pgcrypto postgres扩展的Django加密字段。

项目描述

django-pgcrypto-fields

Latest Release Python Versions Build Status Requirements Status Updates Coverage Status

django-pgcrypto-fields是一个依赖于pgcryptoDjango扩展,用于加密和解密字段的 数据。

需求

  • 带有pgcrypto的postgres
  • 支持Django 2.2.x, 3.0.x, 3.1.x和3.2.x
  • 仅兼容Python 3

此库支持Django 1.8.x, 1.9.x, 1.10.x的最后一个版本是django-pgcrypto-fields 2.2.0。

此库支持Django 2.0.x和2.1.x的最后一个版本是django-pgcrypto-fields 2.5.2。

安装

安装包

pip install django-pgcrypto-fields

Django设置

我们的库通过在DATABASES设置中定义密钥来支持多个数据库的不同加密密钥。

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

如果使用公钥加密,则生成GPG密钥

公钥将用于加密消息,而私钥将用于解密内容。以下命令取自pgcrypto 文档(参见使用GnuPG生成PGP密钥)。

生成一个公钥和一个私钥(推荐的关键类型是“DSA和Elgamal”。)

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

/home/bob/.gnupg/secring.gpg
---------------------------
sec   2048R/21 2014-10-23
uid                  Test Key <example@example.com>
ssb   2048R/42 2014-10-23


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

限制

此库目前不支持密码保护的公钥加密私钥。有关实现它的信息,请参阅问题#89。

从先前版本升级到2.4.0

此库的2.4.0版本进行了大量重写,以支持在获取加密字段数据时自动解密,以及在不使用先前版本中可用的旧PGPCrypto聚合函数的情况下过滤加密字段的能力。

以下库中的项目已被移除,因此您需要从应用程序中删除对这些项目的引用

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

字段

django-pgcrypto-fields 有 3 种字段类型

  • 基于哈希的字段
  • 公钥(PGP)字段
  • 对称字段

基于哈希的字段

支持的基于哈希的字段有

  • TextDigestField
  • TextHMACField

TextDigestField 使用 digest pgcrypto 函数和 sha512 算法在数据库中哈希。

TextHMACField 使用 hmac pgcrypto 函数和一个密钥以及 sha512 算法在数据库中哈希。这与摘要版本类似,但只能知道密钥才能重新计算哈希。这可以防止有人更改数据并更改哈希以匹配。

公钥加密字段

支持的PGP公钥字段有

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

公钥加密使用公钥生成令牌以加密数据,并使用私钥进行解密。

公钥和私钥可以使用 PUBLIC_PGP_KEYPRIVATE_PGP_KEY 在设置中设置。

对称密钥加密字段

支持的PGP对称密钥字段有

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

使用 settings.PGCRYPTO_KEY 加密和解密数据,它充当密码。

Django 模型字段等效

Django 字段 公钥字段 对称密钥字段
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

其他Django模型字段目前不受支持。欢迎提交拉取请求。

用法

模型定义

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

加密

数据在插入数据库时会自动加密。

示例

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

如果使用 original 属性,基于哈希的字段可以自动更新哈希。此属性允许您指定其他字段名称,以便基于该字段名称的哈希值。

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

在上面的示例中,如果指定可选的 original 属性,则将使用第一个_name 模型字段的无加密值作为输入值来创建哈希。如果没有指定 original 属性,则字段将像现在一样工作,并保持向后兼容。

PGP 字段

当访问模型实例的字段名称属性时,我们得到解密值。

示例

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

从 2.4.0 版本开始,自动解密处理加密值过滤,并且不再支持 aggregate 方法,并已从库中删除。

此外,自动解密也支持 select_related 模型。

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_of 查找来实现的。

示例

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

限制

.distinct('encrypted_field_name')

由于Django ORM中缺少一个功能,在Django 2.0.x及以下版本中,对加密字段使用distinct()不工作。

在Django 2.1.x及更高版本中,正常的distinct功能可以工作。

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

Django 2.0.x及以下版本的解决方案

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

这是因为注释的字段被Django自动解密为F字段,并在distinct()中使用该字段。

将现有字段迁移到PGCrypto字段

此库不执行将现有字段迁移到PGCrypto字段的操作。您需要通过前向迁移或其他方式进行数据迁移。除在Postgres中创建/激活pgcrypto扩展之外,还支持迁移。

迁移数据很复杂,可能需要考虑以下因素

  • 数据形状
  • 在表/模型/表单以及任何其他地方执行的验证/约束

库无法做所有这些猜测或做出所有这些决定。

如果您需要将数据从未加密字段迁移到加密字段,有三种解决方法

  1. 当数据库中没有数据时,应该可以从从头开始重建数据库。
  2. 当表中没有数据时,应该可以重建表。
  3. 当有数据或项目是共享的时,应该可以以非破坏性的方式进行迁移。

选项1:数据库中没有数据

  1. 删除数据库
  2. 压缩迁移
  3. 重新创建数据库

选项2:表中没有数据

  1. 创建一个迁移以删除表
  2. 为具有加密字段的表创建一个新的迁移
  3. 可选地压缩迁移

选项3:以非破坏性的方式进行迁移

这里的目的是在出现问题的情况下能够使用旧字段。

第一部分

  1. 创建新字段
  2. 当数据保存时,将数据写入旧字段和新字段
  3. 创建一个数据迁移来将数据从旧字段转换为新字段
  4. 如果可能,检查旧字段和新字段中的现有数据是否相同

第二部分

  1. 重命名字段并删除旧字段
  2. 更新代码以只使用新字段

安全限制

参考PostgreSQL文档中的说明

https://postgresql.ac.cn/docs/9.6/static/pgcrypto.html#AEN187024

所有pgcrypto函数都在数据库服务器内部运行。这意味着所有数据和密码都在pgcrypto和客户端应用程序之间以明文形式传输。因此,您必须

  1. 本地连接或使用SSL连接。
  2. 信任系统和数据库管理员。

如果您无法这样做,那么在客户端应用程序内部进行加密会更好。

该实现无法抵抗侧信道攻击。例如,给定大小的密文对pgcrypto解密函数完成所需的时间可能会有所不同。

变更日志

主分支(未发布)

2.6.0

  • 添加了对Django 3.1.x的支持
  • 更新了requirements_dev.txt
  • 删除了对Python 3.5的支持
  • 删除了对Django 2.2.x LTS以下版本的支持
  • 添加了对BigIntegerFields的支持 (#169)
  • 添加了现有数据迁移的文档 (#246)

2.5.2

  • 添加了对Django 3.x的支持
  • 更新了requirements_dev.txt

2.5.1

  • 修复了EmailPGPPublicKeyField定义中的回归 (#77)
  • 删除了死代码(remove_validators和RemoveMaxLengthValidatorMixin)
  • 更新了requirements_dev.txt

2.5.0

  • 为公钥和对称密钥添加了新的DecimalFields (#64)
  • 为公钥和对称密钥添加了新的FloatFields (#64)
  • 为公钥和对称密钥添加了新的TimeFields (#64)
  • 根据数据库添加了对不同密钥的支持 (#67)

2.4.0

  • 自动解密所有加密字段,包括FK表
  • 删除了django-pgcrypto-fields的aggregatesPGPManagerPGPAdmin,因为它们不再需要
  • 添加了对get_or_create()update_or_create()的支持 (#27)
  • 添加了对get_by_natural_key()的支持 (#23)
  • 添加了对only()defer()的支持,因为它们在PGPManager中不受支持
  • 添加了对 distinct() 的支持(Django 2.1+,并为 2.0 及以下版本提供了解决方案)
  • 将开发要求从 setup.py 要求中分离出来
  • 更新了打包/设置.py,包括长描述
  • 添加了作者信息和更新了贡献指南
  • 更新了 TravisCI 以使用 Xenial,以在矩阵中获得 Python 3.7

2.3.1

  • 为日期/日期时间字段添加了 __range 查询(#59)
  • 移除对 Django 1.8, 1.9 和 1.10 的兼容性(#62)
  • 改进了 setup.py
    • 检查 Python 3.5+
    • 更新了分类器
  • 改进了用于发布的 make 文件,以使用 twine
  • README 添加了额外的徽章
  • 更新 Travis 配置以包括 Python 3.5 和 3.6
  • 重构了查询和混合

2.3.0

  • 无效的发布,升级到 2.3.1

2.2.0

  • .coveragerc 合并到 setup.cfg
  • 添加了 .gitignore 文件
  • 更新了过时的要求(最新版本的 Flake8pycodestyle 互不兼容)
  • 更新了 README,以更好地解释字段
  • 实现了 DatePGPPublicKeyField 和 DateTimePGPPublicKeyField

2.1.1

  • 添加了对 Django 2.x+ 的支持
  • 更新了测试要求
  • 更新 Travis 配置以包括 Python 3.6 和其他环境

2.1.0

感谢 @peterfarrell

  • 添加了对 DatePGPSymmetricKeyFieldDateTimePGPSymmetricKeyField 的支持,包括对序列化和反序列化 django 表单字段的兼容性。
  • 通过 PGPManager 添加了对对称密钥和公钥字段的自动解密支持(并通过 PGPAdmin 在 Django Admin 中禁用支持)

2.0.0

  • 移除对 Django 1.7 的兼容性。
  • 添加对 Django 1.10 的兼容性。
  • Django 1.9 添加到 Travis 矩阵中。

v1.0.1

  • 从分发包中排除测试应用程序。

v1.0.0

  • 将包名从 pgcrypto_fields 更改为 pgcrypto

v0.7.0

  • 使 get_placeholder 接受新的参数 compiler
  • 修复了 Aggregate 的错误导入

注意:这些更改已针对 django > 1.8.0 执行。

v0.6.4

  • 从电子邮件字段中删除 MaxLengthValidator

v0.6.3

  • 避免在 PGP 字段上设置 max_length

v0.6.2

  • 允许/检查以下字段的 NULL 值:TextDigestFieldTextHMACFieldEmailPGPPublicKeyFieldIntegerPGPPublicKeyFieldTextPGPPublicKeyFieldEmailPGPSymmetricKeyFieldIntegerPGPSymmetricKeyFieldTextPGPSymmetricKeyField

v0.6.1

  • 修复了向整数字段发送负值时的 cast 错误。

v0.6.0

  • 添加了 EmailPGPPublicKeyFieldEmailPGPSymmetricKeyField

v0.5.0

  • 重命名以下字段:PGPPublicKeyFieldTextPGPPublicKeyFieldPGPSymmetricKeyFieldTextPGPSymmetricKeyFieldDigestFieldTextDigestFieldHMACFieldTextHMACField
  • 添加了新的整数字段:IntegerPGPPublicKeyFieldIntegerPGPSymmetricKeyField

v0.4.0

  • 使访问解密值透明。修复了当字段具有 PGP 和基于密钥的哈希字段的字符串表示为 memoryview 时的错误。

v0.3.1

  • 修复了 EncryptedProxyField 以选择正确的项。

v0.3.0

  • 使用字段的代理 _decrypted 访问 PGPPublicKeyFieldPGPSymmetricKeySQL 的解密值。
  • 删除字段名称和原始值的描述符。

v0.2.0

  • DigestFieldHMACField 添加基于哈希的查找。
  • 添加 DigestFieldHMACFieldPGPPublicKeyAggregatePGPSymmetricKeyAggregate

v0.1.0

  • 通过聚合类添加了解密。
  • 在向数据库插入数据时添加了加密。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分发

django-pgcrypto-fields-2.6.0.tar.gz (15.9 kB 查看哈希值)

上传时间

构建分发

django_pgcrypto_fields-2.6.0-py3-none-any.whl (13.5 kB 查看哈希值)

上传时间 Python 3

由支持