网页中密码学的主要问题是,由于您正在执行的代码是从 Web 服务器加载的,因此该服务器可以完全控制该代码是什么,并且可以在您每次刷新页面时更改它。除非您每次在该站点上加载新页面时手动检查正在运行的代码(最好在实际执行该代码之前),否则您无法知道该代码实际上会做什么。
Web Cryptography API 可以通过以页面上运行的脚本无法访问的方式安全地存储加密密钥,从而在一定程度上减轻这种风险,但是可以使用这些密钥执行的所有操作(解密、签名等)仍然是可用于那些(可能是恶意的)脚本。
只要您确实信任服务器不会在浏览器中进行恶意行为,加密就会非常有用,但在许多使用加密的应用程序中,对您无法控制的远程服务器的信任级别是不可接受的。
特别是对于您的计划:
- 当然,我们将使用 SSL
这很好。如果没有 SSL,所有以后的安全措施都将毫无意义,因为攻击者可以简单地用他们自己的代码替换您的代码,并对用户的数据做任何他们想做的事情。
- 当用户第一次登录我们的笔记应用程序时,我们会向他们发送我们的公钥。该密钥将用于验证我们的“crypto.js”脚本的真实性。公钥将存储在用户的浏览器中。
这似乎毫无意义。TLS已经向客户端发送了您服务器的公钥,并使用它来验证您通过该连接加载的所有脚本的真实性。没有理由在 JavaScript 中重新做同样的事情。
- “checker.js”脚本也被下载和存储。这个脚本永远不会改变,它将负责检查“crypto.js”和(2)的完整性。
这也是没有意义的,因为无法强制执行“此脚本永远不会更改”的要求。您可以发送一个具有较长 max-age 的 Cache-Control 标头,但不能保证用户代理始终尊重该值;不打算依赖缓存来确保安全性。
- 在 (2) 和 (3) 中,我们在用户和我们的网站之间建立了首次使用信任 (TOFU) 关系。公钥和“checker.js”都使用 service worker 或类似的缓存。
需要明确的是:使用服务人员缓存这些文件对整个系统的安全性没有影响。当用户稍后返回您的站点时,浏览器将检查服务器以查看 service worker 是否已更新并安装新版本(如果有)。所以服务器仍然可以完全控制在用户浏览器中运行的代码。这里没有“首次使用信任 (TOFU) 关系”。
- 即使我们使用 SSL,在下载 (2) 和 (3) 时可能会发生 MITM 攻击,因此我们可以提供一种方法来检查公钥和“checker.js”是否受到损害。
这是一个很好的姿态,但正如我之前所说,即使这些文件当前没有受到破坏,服务器或 MITM(他们以某种方式设法破坏了您的 TLS 连接)可以随时轻松更新这些文件以破坏它们而不会引起用户注意,所以我真的不明白这个功能的意义。
首次登录时,我们还将其私钥发送给用户。此私钥将用于加密和签署笔记。此私钥将被加密。
解密所需的密钥 (6) 通过电子邮件发送给用户。这样我们就建立了双通道认证。
使用 Web Crypto ( https://www.w3.org/TR/WebCryptoAPI/ ) 我们用 (7) 解密 (6)。通过这种方式,(6)永远不会存储在浏览器中被解密,并且由于 Web Crypto API 的存在,JavaScript 无法访问它。
实现这一点需要服务器有权访问用户私钥的明文版本。根据您使用这些密钥的确切用途,如果服务器遭到入侵,这可能会出现问题。相反,您应该考虑使用 Web Crypto API 在用户设备上生成私钥-公钥对,并让浏览器将该密钥的公钥部分发送到服务器。这样服务器就永远无法访问用户的私钥。
现在我们可以从 Web 应用程序的功能开始:创建加密笔记。为此,用户写一个便条并单击保存按钮。服务器发送使用服务器私钥签名的“crypto.js”(参见 2)。
使用在 (2) 和 (3) 中下载的公钥验证签名,如果正确,则对票据进行加密。如果“checker.js”被修改,SRI 应该停止这个过程。
除非您checker.js
从不受信任的第三方服务器加载,否则在这种情况下不需要子资源完整性。任何可以破坏您的服务器或其与客户端的连接进行修改checker.js
的人也可以修改子资源完整性哈希的值,以便浏览器可以毫无怨言地接受修改后的脚本。或者他们可以只修改页面以完全不加载checker.js
,并使用他们自己制作的完全不同的脚本。无论哪种方式,子资源完整性都无济于事。
- 笔记被发送回服务器并存储。
只要您解决了我在 6、7 和 8 中提到的问题,就可以了,这样服务器就没有解密用户文件所需的密钥。如果您对拥有访问用户文件的密钥的服务器感到满意,则根本不需要客户端加密;只需让服务器处理加密即可。
- 根据所需的功能,服务器应删除用户的私钥并仅保留或不保留公共密钥。
或者,正如我所建议的,首先不要给服务器用户的密钥。除此之外,这部分在安全方面很好,因为它可以防止服务器在用户不使用站点时访问用户的文件。
但是,一旦用户访问该站点,用户的浏览器将从该服务器加载代码,该服务器将能够使用用户的密钥来解密用户的笔记。所以对于普通用户来说,访问他们的笔记而不让你的服务器能够阅读它们是不可能的。
此实现还存在一些可用性问题,因为这意味着用户将无法从新浏览器登录他们的帐户,并且仍然可以访问他们的笔记。更好的实现是使用具有高工作因子的密钥派生算法(如 PBKDF2(可通过 Web Cryptography API 获得))从用户的密码中派生出用户的加密密钥。这将允许他们从任何浏览器访问他们的笔记。(但仍然会有我上面评论中提到的所有相同的安全缺点。)