对于即将到来的Wall-O-Text,我提前表示歉意。这是(至少对我来说)一个相当复杂的问题,我对此进行了很多思考。您可以阅读我的问题,并在 测试实现(非常仓促构建,没有数据库支持,并且可能非常丑陋) /888975/cbccec57b74ad8d6bad14b41f70d3c784c33811a" rel="nofollow">这个 GitHub Gist 如果你愿意的话。
简介
假设需要创建一个基于 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 密码和盐的 SHA-256 哈希值。
- 服务器生成 RSA 密钥对。公钥以明文形式存储
pub_key
字段中的文本。私钥通过 AES-256 加密
使用从 Bob 的密码和盐生成的哈希值作为密钥
存储在 enc_priv_key
字段中。
- 服务器为 Bob 生成基于哈希的消息验证码
使用 Bob 的密码和盐作为密钥的私钥并将其存储在
priv_key_hmac
字段。
Bob 在系统中存储一个条目:
- Bob 输入一些要与他的密码一起存储为条目的数据。
该数据被发送到服务器。
- 服务器生成一个密钥,用作 AES-256 加密的密钥。
- 服务器使用该密钥加密数据并将结果存储在
enc_data
字段。
- 服务器生成一个基于哈希的消息认证码
使用生成的密钥获取数据并将其存储在
data_hmac
字段中。
- 用于加密数据的对称密钥是用 Bob 的公钥加密的
密钥并存储在
enc_sym_key
字段中。
- 服务器使用 Bob 的私钥生成签名
对称密钥。
Bob 检索他存储的条目:
- Bob 输入他的密码和要检索的条目的 ID。
- 服务器生成 Bob 密码和盐的 SHA-256 哈希值。
- Bob 的加密私钥通过 AES-256 加密进行解密,使用
哈希。
- 服务器验证Bob的加密私钥未被
通过检查
priv_key_hmac
中的 HMAC 被篡改。
- 服务器解密存储在
enc_sym_key
字段中的对称密钥
使用鲍勃的私钥。
- 服务器验证加密的对称密钥没有被篡改
通过使用 Bob 的公钥验证
sym_key_sign
中的签名。
- 服务器使用对称密钥解密数据。
- 服务器验证加密数据未被篡改
通过验证存储在
data_hmac
字段中的 HMAC。
- 服务器将解密后的数据返回给Bob。
鲍勃与爱丽丝共享条目:
- 鲍勃希望爱丽丝能够访问他拥有的条目。他进入他的
密码和要共享的条目的 ID。
- 使用“Bob 检索
他存储的条目。”
- 以与“Bob 存储”中相同的方式为 Alice 创建一个新条目
系统中的一个条目”,但以下情况除外:
- 条目的
parent_entry
设置为 Bob 的条目。
- 对称密钥的签名是使用 Bob 的私钥计算得出的
密钥(因为 Bob 无法使用 Alice 的私钥)。
- 当 Alice 访问这个新条目时,存在非空
parent_entry
使系统使用Bob的公钥进行验证
签名(因为他的私钥是用来创建签名的)。
Bob 更改其共享条目中的数据:
- Bob 决定更改他与 Alice 共享的条目中的数据。鲍勃
指示要修改的条目 ID 及其应包含的新数据。
- 系统会覆盖“Bob 将条目存储在
system."
- 系统会查找
parent_entry
等于该条目的每个条目
刚刚修改过,并且每一项都会覆盖在中创建的数据
“鲍勃与爱丽丝共享一个条目。”
分析
优点:
- 如果没有密码,就不可能解密数据库中的任何数据。
拥有数据的用户的密码,作为必要的私钥
解密数据是用用户的密码加密的,并且该密码
(及其哈希)不存储在数据库中。
- 如果用户想要更改其密码,则只能更改其加密的私人密码
需要重新生成密钥(用旧的私钥解密私钥)
密码/哈希,然后使用新密码/哈希重新加密)。
- 共享条目作为实际单独的记录存储在数据库中,
因此无需在多个用户/用户组之间共享密钥。
缺点/问题(我能想到的):
- 如果共享条目被修改,系统必须重新加密每个子项
入口;由于大量用户共享数据,这可能会
计算成本昂贵。
- 共享条目依赖于父用户的公钥进行签名
确认。如果用户被删除,或者他们的密钥发生变化,签名
均无效。
重复介绍:我的问题是:这个实现是
安全的?我错过了什么吗?如果是这样,上述规范是否可以实现?
或者这太过分了?
谢谢你坚持这么久。我对你的意见很感兴趣!我是在正确的道路上,还是一个彻头彻尾的白痴?你决定! :)
I apologize in advance for the incoming Wall-O-Text. This is (at least, to me) a fairly complex issue that I've put quite a bit of thought into. You can read my question and also see a test implementation in Ruby (very hastily built, not database-backed, and probably very ugly) at this GitHub Gist if you are so inclined.
Introduction
Imagine one was required to create a web-based password management system
(over SSL! :) with the following requirements:
- Individual users sign in to the system using their own unique pass
phrase.
- This pass phrase should be enough to allow the user to use the system
effectively (e.g. from a smartphone, etc.)--the point being that they
should not have to keep a key file with them.
- Users can store arbitrary-length bits of data in the system ("entries").
- Entries are encrypted in the database in such a way that there is not
enough information in the database or application alone to read the
encrypted entries.
- Users should be able to "share" entries with other users of the system
so that the other user(s) can read the contents of the entry.
I'm no expert in cryptography. After thinking about it for a while, I came up
with the following. My question is: is this implementation secure? Am I
missing something? If so, is the above spec even implementable? Or is this
overkill?
Database
The database is set up as such:
+------------------------------------------------------------------------------+
| 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 |
+---------+--------------+--------------+---------------+----------+-----------+
Basic Use Cases
Let's imagine two users of the system, Alice and Bob.
Bob signs up for the site:
- Bob enters a password. This password is sent to the server (but not
stored).
- The server generates a random salt and stores it in the
salt
field.
- The server generates the SHA-256 hash of Bob's password and salt.
- The server generates an RSA key pair. The public key is stored as plain
text in the pub_key
field. The private key is encrypted via AES-256
using the hash generated from Bob's password and salt as the key and
stored in the enc_priv_key
field.
- The server generates a hash-based message authentication code for Bob's
private key using Bob's password and salt as the key and stores this in
the priv_key_hmac
field.
Bob stores an entry in the system:
- Bob enters some data to be stored as an entry along with his password.
This data is sent to the server.
- The server generates a key to be used as a key for AES-256 encryption.
- The server uses this key to encrypt the data and stores the result in
the enc_data
field.
- The server generates a hash-based message authentication code for the
data using the generated key and stores this in the data_hmac
field.
- The symmetric key used to encrypt the data is encrypted with Bob's public
key and stored in the enc_sym_key
field.
- The server uses Bob's private key to generate a signature for the
symmetric key.
Bob retrieves his stored entry:
- Bob enters his password and the ID of the entry to retrieve.
- The server generates the SHA-256 hash of Bob's password and salt.
- Bob's encrypted private key is decrypted via AES-256 encryption using the
hash.
- The server verifies that Bob's encrypted private key has not been
tampered with by checking the HMAC in priv_key_hmac
.
- The server decrypts the symmetric key stored in the
enc_sym_key
field
using Bob's private key.
- The server verifies that the encrypted symmetric key has not been tampered
with by verifying the signature in sym_key_sign
using Bob's public key.
- The server decrypts the data using the symmetric key.
- The server verifies that the encrypted data has not been tampered with
by verifying the HMAC stored in the data_hmac
field.
- The server returns the decrypted data to Bob.
Bob shares an entry with Alice:
- Bob wants Alice to have access to an entry he owns. He enters his
password and the ID of the entry to share.
- The data for the entry is decrypted using the method in "Bob retrieves
his stored entry."
- A new entry is created for Alice in the same fashion as in "Bob stores
an entry in the system," with the following exceptions:
- The entry's
parent_entry
is set to Bob's entry.
- The signature for the symmetric key is calculated using Bob's private
key (since Alice's private key is not available to Bob).
- When Alice accesses this new entry, the existence of a non-null
parent_entry
causes the system to use Bob's public key to verify
the signature (since his private key was used to create it).
Bob changes the data in his shared entry:
- Bob decides to change the data in the entry he shared with Alice. Bob
indicates the entry ID to modify and the new data it should contain.
- The system overwrites the data created in "Bob stores an entry in the
system."
- The system finds every entry with a
parent_entry
equal to the entry
that was just modified, and for each one overwrites the data created in
"Bob shares an entry with Alice."
Analysis
Advantages:
- It is impossible to decrypt any data from the database without the
password of the user that owns the data, as the private key necessary to
decrypt the data is encrypted with the user's password, and that password
(and it's hash) is not stored in the database.
- If a user wants to change their password, only their encrypted private
key needs to be regenerated (decrypt the private key with the old
password/hash, then re-encrypt it with the new password/hash).
- Shared entries are stored as actual separate records in the database,
so there is no need to share a key between multiple users/groups of users.
Disadvantages/Problems (that I can think of):
- If a shared entry is modified, the system must re-encrypt every child
entry; with a large number of users sharing data, this could potentially
be computationally expensive.
- Shared entries depend on the parent user's public key for signature
verification. If the user is deleted, or their key changes, the signatures
are invalid.
Repeated from the introduction: my question is: is this implementation
secure? Am I missing something? If so, is the above spec even implementable?
Or is this overkill?
Thanks for sticking it out this long. I'm interested in your opinions! Am I on the right track, or a complete moron? YOU DECIDE! :)
发布评论
评论(3)
没有静脉注射储存?我想您可以使用 AES-256-ECB,但这只能让用户存储 32 字节密码,并且您需要确保生成的私钥仅用于一次加密。 (您当前的设计在这方面似乎是安全的,但如果您想要允许密码超过 32 个字节,或者曾经考虑让这个密钥执行双重任务,您需要存储一个 IV 我没有
看到
priv_key_hmac
和data_hmac
的安全价值;如果私钥或加密数据被篡改,则使用私钥或对称密钥解密将产生垃圾输出。当 Bob 不知道如何键入BEL
字符时,他肯定会产生怀疑。 :)(人类会看到输出吗?人类可能会意识到返回的密码是不正确的,而无需告知。计算机无法区分其中的差异,因此,如果自动化系统将使用生成的密码,那么当然,请保留字段。)没有“我忘记了密码”的机制。确保您的用户知道,如果忘记密码,他们的数据将无法恢复。如今,用户受到了宠爱,并且可能也期望受到您的服务的宠爱。
我没有看到用户可以指定鲍勃想要解密哪个条目的机制。您应该为每个条目存储一个名称,或者像
ssh(1)
在known_hosts
中所做的那样,存储一个名称的哈希版本。直接存储名称将消除 SHA-256 操作,但报告用户拥有帐户的服务的明文名称的数据库泄露可能同样具有破坏性。 (也许是在线陪护服务,或离岸银行,或搏击俱乐部。)No IV storage? I guess you could use AES-256-ECB, but that only lets users store 32 byte passwords, and you need to make sure that the generated private key is only ever used for one encryption. (Your current design seems safe in this respect, but if you want to allow passwords longer than 32 bytes, or ever think of making this key do double-duty, you'll need to store an IV for every encryption with it.)
I don't see the security value of
priv_key_hmac
anddata_hmac
; if either the private key or the encrypted data has been tampered with, then garbage output will result from decrypting with the private key or the symmetric key. Bob will surely be suspicious when he can't figure out how to type theBEL
character. :) (Will humans ever see the output? A human will likely realize the returned password is incorrect without needing to be told. A computer couldn't tell the difference, so if automated systems will ever use the resulting passwords, then sure, keep the fields.)There is no mechanism for "I forgot my password". Make sure your users know that there is no recovering their data if they forget their password. Users are coddled these days, and might expect to be coddled with your service too.
I see no mechanism for users to specify which entry Bob wants decrypted. You should store a name, or, as
ssh(1)
does inknown_hosts
, a hashed version of a name, for each entry. Storing a name directly would remove an SHA-256 operation, but a database compromise that reports the cleartext names of services that a user has accounts with might be every bit as damaging. (Perhaps an online escort service, or off-shore bank, or fight club.)当您与 Alice 共享条目时,实际上不需要复制除
enc_sym_key
之外的任何内容 - 由于对称密钥永远不会重复用于多个条目,因此您只需要一份加密的副本数据。You don't actually need to duplicate anything other than
enc_sym_key
when you share an entry with Alice - since the symmetric key is never re-used for more than one entry, you only need one copy of the encrypted data.为什么不使用证书在用户之间共享数据?使用 PKCS#12 证书保存用户的 PEM 和私钥,每个用户或每个站点的 PEM 可以签名和加密,以实现数据验证和安全。
一个场景来说明。
鲍勃想在夏娃不阅读的情况下与爱丽丝分享。
爱丽丝将她的公钥交给了鲍勃。鲍勃将爱丽丝的公钥添加到他的可信用户钥匙串中。然后,Bob 使用 Alice 的公钥来加密消息,同时使用他自己的 PEM 对数据进行签名。当然,这种情况要求 Alice 已经拥有 Bob 公钥的副本来执行签名验证,但您明白了。
另外,为什么要储存盐或静脉注射?如果数据库遭到破坏,这两个数据都与静态数据一起存储,都可以访问。
最佳实践...
Why not use certificates for sharing data between users? The use PKCS#12 certificates for holding the PEM and Private keys of users and the PEM per user or per site can sign and encrypt for data verification and security.
A scenario to illustrate.
Bob wants to share with Alice without Eve reading.
Alice gives Bob her public key. Bob adds Alice's public key to his keychain of trusted users. Bob then uses Alice's public key to encrypt a message while using his own PEM to sign the data. Of course this scenario requires that Alice already have a copy of Bob's public key to perform verification of the signature but you get the idea.
Also, why store a salt or iv? Both of these being stored along with at rest data will be accessible in the event of a db compromise.
Best practices...