密钥管理基础设施
项目描述
本软件包提供符合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
生成新密钥对算法相当复杂。以下功能是必需的
数据本地的密钥不能直接用作加密密钥。
加密密钥必须使用至少与密钥本身一样安全的加密方式存储。
存储数据的计算机不能同时存储密钥。
这表明以下算法可以用于生成和存储新的加密密钥
创建密钥加密密钥(私钥和公钥)。
创建加密密钥。
使用公钥加密密钥加密两个加密密钥。
丢弃公钥加密密钥。非常重要,这个密钥永远不能存储在任何地方。
将加密的加密密钥存储在密钥管理设施中。
返回私钥加密密钥。
现在让我们使用密钥生成服务的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
本地密钥管理设施
然而,使用主密钥管理设施的加密服务是昂贵的,因为每个加密和解密请求都需要网络请求。幸运的是,我们可以
在多个设备之间传递加密密钥,并且
在内存中保持加密密钥。
只需确保数据传输通过加密通信通道完成。在这个实现中,通信协议是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
本地设施不是将加密和解密请求转发到主设施,而是仅仅获取加密密钥对并在本地执行操作。这种方法有以下优点
没有一般的网络延迟对于任何加密和解密调用。
将加密和解密消息的昂贵任务委托给多个服务器,允许更好的扩展。
获取的密钥可以本地缓存,减少网络调用的次数。
在这个实现中,我们确实在私有属性中缓存了密钥
>>> 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 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 1898bd6edd72922787fe0c886aff67287b7343c520479691b7f3eeb98eddb6c4 |
|
MD5 | 6ed359a8f7a09193246fb70ebe757cdf |
|
BLAKE2b-256 | 82dbe03623733fc7c9c45ed913fed7dfdc2c13e9a9ac53489bde986e3efa9dea |