客户端存储的内容和服务器存储的内容之和必须足以恢复用户特定的秘密数据(例如 Facebook 访问令牌)。客户端存储的主要是用户的密码(客户端唯一永久存储的区域是用户的大脑,如果我们想让用户随意使用几台不同的机器)。如果我理解正确,您不希望您的服务器“按原样”存储数据:如果您的服务器完全受到威胁,用户的秘密应该仍然是安全的(尽可能)。
这指向基于密码的加密。从用户的密码导出一个对称密钥K,用于对称加密包含用户秘密的包,例如 Facebook 帐户。从技术上讲,这看起来像这样:
- 当用户注册时,他选择他的密码P并将其发送到您的服务器。
- 您的服务器生成一个随机盐值S并在P和S上计算PBKDF2,以产生 256 位的输出T。我们将A称为T的前 128 位,将K称为另一 128 位的一半。
- 前半部分(A)存储在服务器的数据库中;这将用于对用户进行身份验证。
- 后半部分(K)不存储;这是该用户的对称密钥。用户的秘密将使用K加密。
- 当用户登录时,他将他的姓名和P发送到您的服务器(通过 HTTPS)。您的服务器检索该用户的盐S(存储在其数据库中)并重新计算T(将P和S与 PBKDF2 一起使用)。如果重新计算的T的前半部分与存储的 A匹配,则用户通过身份验证, T的后半部分为K。然后,服务器可以解密存储的捆绑包并访问 Facebook 访问令牌。
使用此设置,服务器永远不会存储(在其磁盘或数据库中)明文 Facebook 访问令牌,但它可以在用户登录时访问它;只要需要,服务器就可以记住(在 RAM 中)Facebook 访问令牌。
请注意,当用户更改密码时,这也会更改K(服务器必须也生成一个新的 salt S,但即使服务器错误地没有,K仍然会更改);因此,服务器必须用旧的K解密存储的包,并在更改密码时用新的K再次加密。这并不难集成,因为密码更改界面通常会要求输入旧密码和新密码,因此服务器同时拥有两者。
另请注意,忘记密码时,捆绑包将丢失。这种情况无法恢复。如果用户忘记了自己的密码,那么您将不得不进行一些密码重置,这类似于重新注册;并且必须获得一个新的 Facebook 访问令牌。
在上述所有内容中,我们看到您的服务器仍然可以看到用户的密码。如果您的服务器完全被入侵和劫持,那么攻击者将能够在攻击者控制服务器时观察所有登录用户的用户密码和用户的 Facebook 访问令牌。防止这种情况要困难得多。可以说,如果您的服务器仅提供一个 Web 界面,其中客户端的所有“智能”都是您的服务器发送的 Javascript,那么在这种情况下,不可能比上述方案做得更好。