我提前为即将到来的Wall-O-Text道歉。这是(至少,对我来说)一个相当复杂的问题,我已经考虑了很多。如果你愿意的话,你可以阅读我的问题,也可以在这个 GitHub Gist 上看到一个用 Ruby 编写的测试实现(非常仓促,没有数据库支持,而且可能非常丑陋) 。
介绍
想象一下,需要创建一个基于 Web 的密码管理系统(通过 SSL!:) 具有以下要求:
- 个人用户使用他们自己的唯一密码短语登录系统。
- 这个密码短语应该足以让用户有效地使用系统(例如,通过智能手机等)——关键是他们不应该随身携带密钥文件。
- 用户可以在系统中存储任意长度的数据位(“条目”)。
- 条目在数据库中以这样一种方式加密,即数据库或应用程序中没有足够的信息来读取加密的条目。
- 用户应该能够与系统的其他用户“共享”条目,以便其他用户可以阅读条目的内容。
我不是密码学专家。想了一会儿,我想到了以下几点。 我的问题是:这个实现安全吗?我错过了什么吗?如果是这样,上述规范甚至可以实施吗?或者这是矫枉过正?
数据库
数据库是这样设置的:
+------------------------------------------------------------------------------+
| users |
+---------+--------------+--------------+---------------+----------------------+
| salt | pub_key | enc_priv_key | priv_key_hmac | |
+---------+--------------+--------------+---------------+----------------------+
| entries |
+---------+--------------+--------------+---------------+----------+-----------+
| user_id | parent_entry | enc_sym_key | sym_key_sig | enc_data | data_hmac |
+---------+--------------+--------------+---------------+----------+-----------+
基本用例
让我们想象一下系统的两个用户,Alice 和 Bob。
Bob注册该网站:
- Bob 输入密码。此密码被发送到服务器(但不存储)。
- 服务器生成随机盐并将其存储在
salt
字段中。 - 服务器生成 Bob 的密码和 salt 的 SHA-256 哈希。
- 服务器生成一个 RSA 密钥对。公钥以纯文本形式存储在
pub_key
字段中。私钥通过 AES-256 使用 Bob 的密码和 salt 生成的哈希作为密钥进行加密,并存储在enc_priv_key
字段中。 - 服务器使用 Bob 的密码和 salt 作为密钥为 Bob 的私钥生成一个基于哈希的消息验证代码,并将其存储在
priv_key_hmac
字段中。
Bob在系统中存储一个条目:
- Bob 输入了一些数据和他的密码一起存储为条目。该数据被发送到服务器。
- 服务器生成一个密钥,用作 AES-256 加密的密钥。
- 服务器使用此密钥加密数据并将结果存储在
enc_data
字段中。 - 服务器使用生成的密钥为数据生成基于散列的消息验证码,并将其存储在
data_hmac
字段中。 - 用于加密数据的对称密钥使用 Bob 的公钥加密并存储在
enc_sym_key
字段中。 - 服务器使用 Bob 的私钥生成对称密钥的签名。
Bob检索他存储的条目:
- Bob 输入他的密码和要检索的条目的 ID。
- 服务器生成 Bob 的密码和 salt 的 SHA-256 哈希。
- Bob 的加密私钥使用哈希通过 AES-256 加密进行解密。
- 服务器通过检查 中的 HMAC 来验证 Bob 的加密私钥没有被篡改
priv_key_hmac
。 enc_sym_key
服务器使用 Bob 的私钥解密存储在该字段中的对称密钥。sym_key_sign
服务器通过使用 Bob 的公钥验证签名来验证加密的对称密钥没有被篡改。- 服务器使用对称密钥解密数据。
data_hmac
服务器通过验证存储在字段中的 HMAC 来验证加密数据没有被篡改。- 服务器将解密后的数据返回给 Bob。
Bob与 Alice 共享一个条目:
- Bob 希望 Alice 能够访问他拥有的条目。他输入他的密码和要共享的条目的 ID。
- 条目的数据使用“Bob 检索他存储的条目”中的方法解密。
- 以与“Bob 在系统中存储条目”相同的方式为 Alice 创建一个新条目,但以下情况除外:
- 条目
parent_entry
设置为 Bob 的条目。 - 对称密钥的签名是使用 Bob 的私钥计算的(因为 Alice 的私钥对 Bob 不可用)。
- 当 Alice 访问这个新条目时,非空值的存在
parent_entry
会导致系统使用 Bob 的公钥来验证签名(因为他的私钥被用来创建它)。
- 条目
Bob更改了他共享条目中的数据:
- Bob 决定更改他与 Alice 共享的条目中的数据。Bob 指出要修改的条目 ID 和它应该包含的新数据。
- 系统会覆盖在“Bob 在系统中存储条目”中创建的数据。
- 系统找到与
parent_entry
刚刚修改的条目相等的每个条目,并为每个条目覆盖在“Bob 与 Alice 共享一个条目”中创建的数据。
分析
优点:
- 如果没有拥有数据的用户的密码,就不可能从数据库中解密任何数据,因为解密数据所需的私钥是用用户的密码加密的,并且该密码(及其哈希值)不存储在数据库。
- 如果用户想要更改他们的密码,只需要重新生成他们的加密私钥(用旧密码/哈希解密私钥,然后用新密码/哈希重新加密)。
- 共享条目作为实际单独的记录存储在数据库中,因此无需在多个用户/用户组之间共享密钥。
缺点/问题(我能想到的):
- 如果共享条目被修改,系统必须重新加密每个子条目;由于有大量用户共享数据,这可能在计算上很昂贵。
- 共享条目依赖于父用户的公钥进行签名验证。如果用户被删除,或者他们的密钥发生变化,则签名无效。
从介绍中重复:我的问题是:这个实现安全吗?我错过了什么吗?如果是这样,上述规范甚至可以实施吗?或者这是矫枉过正?
谢谢你坚持这么久。我对你的意见很感兴趣!我是在正确的轨道上,还是一个彻头彻尾的白痴?你决定!:)