跳转到主要内容

密钥管理基础设施

项目描述

本软件包提供符合NIST SP 800-57标准的密钥管理基础设施(KMI)。

要开始使用,请

$ python bootstrap.py # Must be Python 2.7 or higher
$ ./bin/buildout     # Depends on successful compilation of M2Crypto
$ ./bin/runserver    # or ./bin/gunicorn --paste server.ini

服务器将在端口8080上启动。您可以使用以下命令创建一个新的加密密钥

$ wget https://127.0.0.1:8080/new -O kek.dat --ca-certificate sample.crt \
       --post-data=""

或者,如果您需要一个更方便的工具

$ ./bin/testclient https://127.0.0.1:8080 -n > kek.dat

现在可以通过将KEK发布到另一个URL来检索数据加密密钥

$ wget https://127.0.0.1:8080/key --header 'Content-Type: text/plain' \
       --post-file kek.dat -O datakey.dat --ca-certificate sample.crt

或者

$ ./bin/testclient https://127.0.0.1:8080 -g kek.dat > datakey.dat

注意:要符合标准,服务器必须当然使用加密通信通道。 --ca-certificate 告诉wget信任keas.kmi发行版中包含的示例自签名证书;您需要为生产使用生成一个新的SSL证书。

密钥管理基础设施

本软件包提供符合NIST SP 800-57标准的密钥管理基础设施。该基础设施的一部分是密钥管理设施,它提供与密钥相关的多项服务。所有密钥都存储在指定的存储目录中。

>>> from __future__ import print_function
>>> import tempfile
>>> storage_dir = tempfile.mkdtemp()
>>> from keas.kmi import facility
>>> keys = facility.KeyManagementFacility(storage_dir)
>>> keys
<KeyManagementFacility (0)>
>>> from zope.interface import verify
>>> from keas.kmi import interfaces
>>> verify.verifyObject(interfaces.IKeyManagementFacility, keys)
True

设施提供的一项服务是新密钥的生成。

>>> verify.verifyObject(interfaces.IKeyGenerationService, keys)
True

生成新密钥对算法相当复杂。以下功能是必需的

  1. 数据本地的密钥不能直接用作加密密钥。

  2. 加密密钥必须使用至少与密钥本身一样安全的加密方式存储。

  3. 存储数据的计算机不能同时存储密钥。

这表明以下算法可以用于生成和存储新的加密密钥

  1. 创建密钥加密密钥(私钥和公钥)。

  2. 创建加密密钥。

  3. 使用公钥加密密钥加密两个加密密钥。

  4. 丢弃公钥加密密钥。非常重要,这个密钥永远不能存储在任何地方。

  5. 将加密的加密密钥存储在密钥管理设施中。

  6. 返回私钥加密密钥。

现在让我们使用密钥生成服务的API生成一个密钥。

>>> key = keys.generate()
>>> print(key.decode())
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

默认情况下,系统使用AES 256加密算法,因为公开评论建议AES 196或AES 256加密算法足以满足PCI、HIPAA和NIST密钥强度要求。

现在您可以使用这个密钥加密密钥提取加密密钥

>>> from hashlib import md5
>>> hash_key = md5(key).hexdigest()
>>> len(keys.get(hash_key))
256

我们的密钥管理设施还支持加密服务,允许您在给定密钥加密密钥的情况下加密和解密字符串。

>>> verify.verifyObject(interfaces.IEncryptionService, keys)
True

现在让我们加密一些数据

>>> encrypted = keys.encrypt(key, b'Stephan Richter')
>>> len(encrypted)
16

我们也可以解密数据。

>>> keys.decrypt(key, encrypted) == b'Stephan Richter'
True

我们也可以使用文件描述符加密数据

>>> import tempfile
>>> tmp_file = tempfile.TemporaryFile()
>>> data = b"encryptioniscool"*24*1024
>>> pos = tmp_file.write(data)
>>> pos = tmp_file.seek(0)
>>> encrypted_file = tempfile.TemporaryFile()
>>> keys.encrypt_file(key, tmp_file, encrypted_file)
>>> tmp_file.close()

并解密文件

>>> decrypted_file = tempfile.TemporaryFile()
>>> pos = encrypted_file.seek(0)
>>> keys.decrypt_file(key, encrypted_file, decrypted_file)
>>> encrypted_file.close()
>>> pos = decrypted_file.seek(0)
>>> decrypted_data = decrypted_file.read()
>>> decrypted_file.close()
>>> decrypted_data == data
True

这就是全部内容。大多数复杂的加密相关工作都在幕后进行,对用户是透明的。

最后一点。一旦查找并解密了数据加密密钥,它将被缓存,因为不断地解密DEK是非常昂贵的。

>>> hash_key in keys._KeyManagementFacility__dek_cache
True

超时(以秒为单位)控制何时必须查找密钥

>>> keys.timeout
3600

现在让我们通过将超时设置为零强制重新加载

>>> keys.timeout = 0

缓存是一个字典,将密钥加密密钥映射到一个包含密钥获取的日期/时间和未加密的DEK的2元组。

>>> firstTime = keys._KeyManagementFacility__dek_cache[hash_key][0]
>>> keys.decrypt(key, encrypted) == b'Stephan Richter'
True
>>> secondTime = keys._KeyManagementFacility__dek_cache[hash_key][0]
>>> firstTime < secondTime
True

本地密钥管理设施

然而,使用主密钥管理设施的加密服务是昂贵的,因为每个加密和解密请求都需要网络请求。幸运的是,我们可以

  1. 在多个设备之间传递加密密钥,并且

  2. 在内存中保持加密密钥。

只需确保数据传输通过加密通信通道完成。在这个实现中,通信协议是HTTP,因此一个足够强大的SSL连接是合适的。

现在让我们实例化本地密钥管理设施

>>> localKeys = facility.LocalKeyManagementFacility('https://127.0.0.1/keys')
>>> localKeys
<LocalKeyManagementFacility 'https://127.0.0.1/keys'>

构造函数的参数是主密钥管理设施的URL。本地设施将使用一个小型REST API与服务器通信。

为了进行这次测试,我们将安装一个仅模拟请求的网络组件

>>> from keas.kmi import testing
>>> testing.setupRestApi(localKeys, keys)

与主设施一样,本地设施提供IEncryptionService接口

>>> verify.verifyObject(interfaces.IEncryptionService, localKeys)
True

因此加密和解密非常简单

>>> encrypted = localKeys.encrypt(key, b'Stephan Richter')
>>> len(encrypted)
16
>>> localKeys.decrypt(key, encrypted) == b'Stephan Richter'
True

本地设施不是将加密和解密请求转发到主设施,而是仅仅获取加密密钥对并在本地执行操作。这种方法有以下优点

  1. 没有一般的网络延迟对于任何加密和解密调用。

  2. 将加密和解密消息的昂贵任务委托给多个服务器,允许更好的扩展。

  3. 获取的密钥可以本地缓存,减少网络调用的次数。

在这个实现中,我们确实在私有属性中缓存了密钥

>>> key in localKeys._LocalKeyManagementFacility__cache
True

超时(以秒为单位)控制何时必须重新获取密钥

>>> localKeys.timeout
3600

现在让我们通过将超时设置为零强制重新加载

>>> localKeys.timeout = 0

缓存是一个字典,将密钥加密密钥映射到一个包含密钥获取的日期/时间、加密(公钥)密钥和解密(私钥)密钥的3元组。

>>> firstTime = localKeys._LocalKeyManagementFacility__cache[key][0]
>>> localKeys.decrypt(key, encrypted) == b'Stephan Richter'
True
>>> secondTime = localKeys._LocalKeyManagementFacility__cache[key][0]
>>> firstTime < secondTime
True

本地设施还提供IKeyGenerationService接口

>>> verify.verifyObject(interfaces.IKeyGenerationService, keys)
True

本地方法调用与主调用相同

>>> key2 = localKeys.generate()
>>> print(key2.decode())
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

操作被转发到主服务器,这样密钥也可以在那里使用

>>> hash = md5(key2)
>>> hash.hexdigest() in keys
True

REST API

主密钥管理设施的REST API定义了与本地设施的通信。当创建新的加密密钥对时,我们只需向以下URL发出一个POST请求

http://server:port/new

请求不应包含正文,响应仅是密钥加密密钥。

让我们看看这个调用

>>> from keas.kmi import rest
>>> from webob import Request
>>> request = Request({})
>>> key3 = rest.create_key(keys, request).body
>>> print(key3.decode())
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

当然,密钥在设施中可用

>>> hash = md5(key3)
>>> hash.hexdigest() in keys
True

现在我们可以使用对该URL的POST调用获取加密密钥对

http://server:port/key

请求将密钥加密密钥放在其正文中。响应是加密密钥字符串

>>> request = Request({})
>>> request.body = key3
>>> encKey = rest.get_key(keys, request)
>>> len(encKey.body)
128

如果您尝试请求不存在的密钥,您将收到一个404错误:加密密钥字符串

>>> request.body = b'xxyz'
>>> print(rest.get_key(keys, request))
Key not found

对根的GET请求显示服务器状态页面

>>> print(rest.get_status(keys, Request({})))
200 OK
Content-Type: text/plain
Content-Length: 25
<BLANKLINE>
KMS server holding 3 keys

测试密钥管理设施

测试设施仅管理一个始终恒定的单个密钥。这允许您在全局范围内安装测试设施,不将密钥存储在数据库中,并仍可在多个会话中重用ZODB。

>>> storage_dir = tempfile.mkdtemp()
>>> testingKeys = testing.TestingKeyManagementFacility(storage_dir)

当然,支持密钥生成服务

>>> verify.verifyObject(interfaces.IKeyGenerationService, keys)
True

但是,您总是会收到相同的密钥

>>> def getKeySegment(key):
...     return str(key.decode().split('\n')[1])
>>> getKeySegment(testingKeys.generate())
'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
>>> getKeySegment(testingKeys.generate())
'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
>>> storage_dir = tempfile.mkdtemp()
>>> testingKeys = testing.TestingKeyManagementFacility(storage_dir)
>>> getKeySegment(testingKeys.generate())
'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'

所有其他方法保持不变

>>> key = testingKeys.generate()
>>> testingKeys.getEncryptionKey(key) == b'_\xc4\x04\xbe5B\x7f\xaf\xd6\x92\xbd\xa0\xcf\x156\x1d\x88=p9{\xaal\xb4\x84M\x1d\xfd\xb2z\xae\x1a'
True

我们还可以安全地进行加密和解密

>>> encrypted = testingKeys.encrypt(key, b'Stephan Richter')
>>> testingKeys.decrypt(key, encrypted) == b'Stephan Richter'
True

密钥持有者

密钥持有者是一个简单的类,用于在RAM中存储密钥

>>> from keas.kmi import keyholder
>>> holder = keyholder.KeyHolder(__file__)
>>> verify.verifyObject(interfaces.IKeyHolder, holder)
True

加密持久对象

此包提供了一个EncryptedPersistent类,它负责存储中的数据加密。使用很简单:不需要从子类化,而是从子类化。

>>> from keas.kmi.persistent import EncryptedPersistent
>>> class UserPrivateData(EncryptedPersistent):
...     def __init__(self, name, ssn):
...         self.name = name
...         self.ssn = ssn
...     def __repr__(self):
...         return '<UserPrivateData %s %s>' % (self.name, self.ssn)
>>> userdata = UserPrivateData('Stephan Richter', '123456789')
>>> userdata
<UserPrivateData Stephan Richter 123456789>

用于加密和解密的密钥来自IKeyHolder实用程序,您应该在您的应用程序中提供。

>>> from keas.kmi.testing import TestingKeyHolder
>>> from zope.component import provideUtility
>>> provideUtility(TestingKeyHolder())

原始数据没有出现在pickle中

>>> from zodbpickle import pickle
>>> pickled_data = pickle.dumps(userdata)
>>> b'Stephan' in pickled_data
False
>>> b'123456789' in pickled_data
False

我们可以成功地加载它

>>> pickle.loads(pickled_data)
<UserPrivateData Stephan Richter 123456789>

每个持久对象都单独存储。只有从继承的对象才会被加密。

>>> import persistent.dict
>>> users = persistent.dict.PersistentDict()
>>> users['stephan'] = UserPrivateData('Stephan Richter', '123456789')
>>> users['mgedmin'] = UserPrivateData('Marius Gedminas', '987654321')
>>> pickled_data = pickle.dumps(users)
>>> b'stephan' in pickled_data
True
>>> b'123456789' in pickled_data
False

持久引用

我们已经足够pickle了;我们真的应该确保我们的魔法不会干扰ZODB跟踪持久对象引用。

首先让我们让我们的对象有一些其他(加密和非加密)持久对象的引用

>>> users['stephan'].__parent__ = users
>>> users['mgedmin'].__parent__ = users
>>> users['stephan'].friend = users['mgedmin']
>>> users['mgedmin'].friend = users['stephan']

现在让我们创建一个数据库

>>> import ZODB.DB
>>> import ZODB.MappingStorage
>>> db = ZODB.DB(ZODB.MappingStorage.MappingStorage())
>>> conn = db.open()
>>> conn.root()['users'] = users
>>> import transaction
>>> transaction.commit()

然后我们可以打开第二个连接(同时仔细保持第一个连接打开,以确保它不会被重用,我们实际上加载pickle而不是从缓存中接收持久对象)并加载整个对象图

>>> conn2 = db.open()
>>> users2 = conn2.root()['users']
>>> users2['stephan']
<UserPrivateData Stephan Richter 123456789>
>>> users2['mgedmin']
<UserPrivateData Marius Gedminas 987654321>

持久对象和加密持久对象之间的所有对象引用都正确保留

>>> users2['stephan'].friend
<UserPrivateData Marius Gedminas 987654321>
>>> users2['mgedmin'].friend
<UserPrivateData Stephan Richter 123456789>
>>> users2['stephan'].__parent__ is users2
True
>>> users2['mgedmin'].__parent__ is users2
True
>>> users2['stephan'].friend is users2['mgedmin']
True
>>> users2['mgedmin'].friend is users2['stephan']
True

数据转换

如果您过去有简单的持久对象,现在想将它们转换为,请三思。这并不安全。您已经在磁盘上有了未加密的片段,而唯一消除它们的方法是物理销毁磁盘。

但是,如果您有一个仅用于测试的具有假数据的数据库,并且想通过小型转换步骤继续使用它,您可以使用convert_object_to_encrypted()函数。

>>> from keas.kmi.persistent import convert_object_to_encrypted

以下是我们要存储的旧类定义

>>> from persistent import Persistent
>>> class Password(Persistent):
...     def __init__(self, password):
...         self.password = password
>>> db = ZODB.DB(ZODB.MappingStorage.MappingStorage())
>>> conn = db.open()
>>> conn.root()['pwd'] = Password('xyzzy')
>>> transaction.commit()

现在我们重新定义类

>>> class Password(EncryptedPersistent):
...     def __init__(self, password):
...         self.password = password

我们再次必须使用不同的连接对象(同时保持第一个连接存活)以避免踩到ZODB缓存

>>> conn2 = db.open()
>>> pwd = conn2.root()['pwd']

如果您尝试使用从数据库加载的Password对象,您将收到一个错误

>>> pwd.password
Traceback (most recent call last):
  ...
ValueError: not enough values to unpack (expected 2, got 1)

但是我们可以应用转换步骤

>>> convert_object_to_encrypted(pwd)
>>> pwd.password
'xyzzy'

转换后的状态存储在DB中

>>> transaction.commit()
>>> conn3 = db.open()
>>> pwd = conn3.root()['pwd']
>>> pwd.password
'xyzzy'

变更

3.3.0 (2021-03-26)

  • 将pycryptodome替换为pycrypto

3.2.1 (2018-10-15)

  • 默认initializeVector现在是一个字节字符串。

3.2.0 (2017-05-16)

  • 添加了对Python 3.4、3.5、3.6的支持。

3.1.1 (2017-04-19)

  • 从buildout.cfg中移除了所有版本约束,因为应用程序可以很好地与所有最新版本一起工作。

3.1.0 (2016-04-22)

  • 添加了encrypt_file和decrypt_file的实现。这允许对文件进行分块编码和解码。[pcdummy]

3.0.1 (2016-04-05)

  • 在buildout.cfg中提高了setuptools的版本。

3.0.0 (2014-01-06)

  • 切换到,因为不再维护。

  • 从已弃用的切换到

注意:虽然我在网上找到了从 PyCrypto 转换到 M2Crypto 以实现向后兼容的代码,但我尚未测试该功能。请在您的数据上尝试此操作,并在遇到问题后告诉我。

注意 2:PyCrypto 不允许使用 512 位 RSA 密钥,因此我将密钥大小增加到 2048 位。旧的 512 位密钥仍然可以使用,但新的密钥现在将总是更大。

2.1.0 (2010-10-07)

  • 在密钥管理设施中添加了一个缓存,用于未加密的 DEK,就像在本地密钥管理设施中已经做的那样。这使得加密和解密性能提高了约一个数量级,从大约 2ms 提高到 0.2ms。

2.0.0 (2010-09-29)

  • 重构了 REST 服务器,使其成为一个简单的 repoze.bfg 应用程序。

  • 加密数据加密密钥 (DEK) 现在存储在一个目录中,而不是 ZODB。这提高了数据存储的透明度,并使备份更容易。

  • 对基于目录的设施添加了缓存,因此我们不需要每次都读取文件。

1.1.1 (2010-08-27)

  • 解决了关于 md5 和 zope.testing.doctest 的弃用警告。

1.1.0 (2010-08-25)

  • 特性:更新代码以与 Bluebream 1.0b3 兼容。

1.0.0 (2009-07-24)

  • 特性:更新到最新软件包版本。

0.3.1 (2008-09-11)

  • 放宽 M2Crypto 版本要求至 0.18 或更高版本。

0.3.0 (2008-09-04)

  • keas.kmi.keyholder 中提供了一个简单的 KeyHolder 工具。

0.2.0 (2008-09-04)

  • 示例服务器演示了如何启用 SSL。

  • 首页现在显示存储的密钥数量,而不是 ComponentLookupError 消息。

  • 用于测试远程密钥管理服务器的命令行客户端。

  • 修复了 LocalKeyManagementFacility 故障( AttributeError:‘RESTClient’ 对象没有 ‘POST’ 属性)

0.1.0 (2008-09-03)

  • 初始发布

    • 密钥生成服务

    • 加密服务(主和本地)

    • 用于加密服务之间密钥通信的 REST API

    • 加密持久存储

项目详情


下载文件

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

源代码分发

keas.kmi-3.3.0.tar.gz (34.1 kB 查看哈希)

上传时间 源代码

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页