TL;DR:生成一个数据密钥对,用所有具有写访问权限的用户的公钥加密私有部分,用所有具有读访问权限的用户的公钥加密公共部分。
让我们一一解决:
- 数据必须在数据库中安全加密
这是为了防止攻击者,主要是为了让用户知道即使是员工也无法访问他们的数据,因此技术团队不能访问密钥。
鉴于此要求,您需要考虑的最重要的属性是服务器在任何情况下都无法获取加密或解密数据所需的信息。这意味着所有加密/解密都必须发生在客户端。由于服务器能够按需注入 JavaScript 代码,因此当您需要进行端到端加密时,基于 Web 的系统本质上是不安全的;更注重安全的用户会希望控制用于访问服务的客户端软件,因此他们希望将其实现为桌面应用程序。
- 数据范围仅限于用户帐户
- 加密数据的所有者必须能够将其数据的访问权限授予其他用户
这两个限制意味着多个用户需要能够解密数据。这意味着解密数据的秘密需要与其他用户共享。
- 其他用户可以从这个给定的访问权限中撤消
这意味着,如果我们考虑私钥/公钥解决方案,我们必须能够删除提供给被撤销用户的私钥。
要撤消访问权限,您需要使用新密钥重新加密数据。正如其他答案所讨论的那样,您不能强制健忘。
描述这一点的最佳方式可能是举例。
注释:
P(x)
是名为 x 的私钥。
Q(x)
是 x 的匹配公钥。
e = E(d, Q(x))
手段是用公钥e
加密明文的结果。d
x
d = D(e, P(x))
手段是用私钥d
解密密文的结果。e
x
假设 Alice 想要与 Bob、Charlie 和 Dave 共享数据。Alice 希望让 Bob 能够读取和写入数据,Charlie 可以读取数据但不能生成有效数据,而 Dave 只能写入但不能解密其他人写入的内容(本质上它是 Dave 的放置文件夹)。
所有用户都有用户密钥对。P(Alice)
,Q(Alice)
是 Alice 的用户密钥对;P(Bob)
,Q(Bob)
是 Bob 的用户密钥对;P(Charlie)
,Q(Charlie)
是 Charlie 的用户密钥;和P(Dave)
,Q(Dave)
是 Dave 的用户密钥对。
该系统有一个用户密钥注册表,用户可以在其中共享其用户密钥的公共部分。用户如何安全地检索和验证另一个用户的用户密钥超出了此答案的范围,留给读者作为练习。大多数用户可能只是相信您在服务器上设置的访问限制,但更具安全意识的用户需要做一些类似于 GPG 密钥签名方的事情;。
所有用户都应将其用户密钥的私有部分保密。如何详细执行此操作超出了此答案的范围,但您绝对不想将私有用户密钥存储在未加密的服务器中。相反,我建议可以使用从用户密码和盐派生的对称密钥加密用户密钥,然后将加密的用户密钥和盐存储在服务器上。
为了安全地存储数据“Hello World”,Alice 首先生成一个数据密钥对:P(data)
, Q(data)
. Alice 然后使用数据密钥公钥加密数据:
plaintext = "Hello World"
ciphertext = E(plaintext, Q(data))
鉴于公钥密码学的特性,我们知道它ciphertext
只能由知道的人解密P(data)
。(请注意,数据密钥的私有和公共概念只是一个约定问题,两者都P(data)
必须Q(data)
对不需要它们的每个人保密,例如服务器)
爱丽丝希望让鲍勃和查理能够读取这些数据,所以爱丽丝检索鲍勃和查理的公钥Q(Bob)
并用它们进行Q(Charlie)
加密P(data)
,另外为了让爱丽丝在未来解密文件,可能来自不同的机器,爱丽丝做使用她自己的公钥进行相同的操作:
alice_read_key = E(P(data), Q(Alice))
bob_read_key = E(P(data), Q(Bob))
charlie_read_key = E(P(data), Q(Charlie))
Alice 希望让 Bob 和 Dave 能够写入 Alice、Bob 和 Charlie 可以读取的数据。Alice 还希望将来能够更新数据。为了能够做到这一点,AliceQ(data)
使用Q(Alice)
、Q(Bob)
和加密公共数据密钥Q(Dave)
:
alice_write_key = E(Q(data), Q(Alice))
bob_write_key = E(Q(data), Q(Bob))
charlie_write_key = E(Q(data), Q(Charlie))
Alice 然后将所有encrypted_key
, alice_read_key
, bob_read_key
, charlie_read_key
, alice_write_key
, bob_write_key
, 和charlie_write_key
发送到服务器。
由于服务器/攻击者永远不会拥有P(data)
orQ(data)
并且由于服务器也没有用于解密任何 的私钥read_keys
,因此服务器将无法解密ciphertext
。
当查理想要检索数据时,他需要下载ciphertext
和charlie_read_key
并charlie_read_key
用他的私人用户密钥解密以获得P(data)
然后使用P(data)
解密ciphertext
:
P(data) = D(charlie_read_key, P(Charlie))
plaintext = D(ciphertext, P(data))
现在查理拥有plaintext
. 然而,由于查理没有写密钥,他没有Q(data)
,因此他将无法以其他人能够成功解密的方式更新系统中的数据。
接下来,Dave 需要能够添加到数据中。他无法读取,ciphertext
但他可以通过解密他的写密钥来附加到它以获得 Q(数据):
new_plaintext = "New Data"
Q(data) = D(dave_write_key, P(Dave))
new_ciphertext = E(new_plaintext, Q(data))
updated_ciphertext = ciphertext + new_ciphertext
现在 Dave 可以将 updated_ciphertext 发送到服务器。
(请注意,在大多数非对称加密算法中,您不能简单地连接两个密文并期望能够对其进行解密,因此您可能需要存储一些元数据,以将密文块分开并分别解密)
这给我们留下的只有撤销。要撤销访问权限,您至少P(data)
需要解密ciphertext
回plaintext
,生成新的数据密钥对:P'(data)
,Q'(data)
,并使用新的数据密钥对重新加密明文:
plaintext = D(ciphertext, P(data))
new_ciphertext = E(plaintext, Q'(data))
然后你需要更新每个人的写密钥和读密钥。
要将新用户添加到现有文件中,您只需创建他们的写密钥和读密钥。只有自己可以解密其读密钥的人才能将读密钥扩展给新用户,只有自己可以解密其写密钥的人才能将写密钥扩展给新用户。
如果不需要本系统的细粒度权限权限,(IOW,如果所有能读取数据的用户也可以更新);或者如果您使用其他方式来强制执行细粒度的权限,那么您可以用对称数据密钥替换非对称数据密钥(琐事:具有对称数据密钥的系统类似于多收件人 PGP 加密的电子邮件有效;因此您可能需要对此进行调查)。