2017 年浏览器内密码学有什么问题?

信息安全 Web应用程序 密码学 javascript 网络加密 API
2021-08-27 15:03:51

互联网上有很多文章批评浏览器中的 JavaScript 密码学:

他们提出了一些公平的观点,但我想从 2017 年的角度分析它们并了解您的意见。为此,我将为加密笔记站点定义一个可能的架构,以便您可以提出有关安全方面的可能问题和解决方案:

  1. 当然,我们将使用 SSL。

  2. 当用户第一次登录我们的笔记应用程序时,我们会向他们发送我们的公钥。该密钥将用于验证我们crypto.js脚本的真实性。公钥将存储在用户的浏览器中。

  3. checker.js脚本也被下载和存储这个脚本永远不会改变,它将负责检查crypto.js和 (2) 的完整性。

  4. 在 (2) 和 (3) 中,我们在用户和我们的网站之间建立了首次使用信任 (TOFU) 关系。公钥和checker.js使用服务工作者或类似的缓存。我们也将使用SRI来尽量提高完整性。

  5. 即使我们使用 SSL,在下载 (2) 和 (3) 时也可能发生 MITM 攻击,因此我们可以提供一种方法来检查公钥checker.js是否被泄露。例如,通过将 (2) 和 (3) 的本地副本的哈希值与我们网站或第三方网站中的真实哈希值进行比较。这个解决方案并不完美,这可能是薄弱环节,但我相信可以在桌面应用程序上执行类似的攻击。

  6. 首次登录时,我们还将其私钥发送给用户。此私钥将用于加密和签署笔记。此私钥将被加密。

  7. 解密所需的密钥 (6) 通过电子邮件发送给用户。这样我们就建立了双通道认证。

  8. 使用Web Crypto,我们用 (7) 解密 (6)。通过这种方式,(6)永远不会存储在浏览器中被解密,并且由于 Web Crypto API 的存在,JavaScript 无法访问它。

  9. 现在我们可以从 Web 应用程序的功能开始:创建加密笔记。为此,用户写一个便条并单击保存按钮。服务器发送crypto.js用服务器的私钥签名(见 2)。

  10. 使用在 (2) 和 (3) 中下载的公钥验证签名,如果正确,则对票据进行加密。如果checker.js被修改,SRI 应该停止这个过程。

  11. 笔记被发送回服务器并存储。

  12. 根据所需的功能,服务器应删除用户的私钥并仅保留或不保留公共密钥。

你觉得这个设置怎么样?

我对 (5) 不满意,但这是原生软件可能发生的事情。这不是我们第一次看到安装人员受到威胁下载安装程序时也可能发生 MITM。而且本机代码签名并不完美

你认为网络加密仍然比本地加密更糟糕吗?

有什么可以改进浏览器加密的建议吗?

3个回答

网页中密码学的主要问题是,由于您正在执行的代码是从 Web 服务器加载的,因此该服务器可以完全控制该代码是什么,并且可以在您每次刷新页面时更改它。除非您每次在该站点上加载新页面时手动检查正在运行的代码(最好在实际执行该代码之前),否则您无法知道该代码实际上会做什么。

Web Cryptography API 可以通过以页面上运行的脚本无法访问的方式安全地存储加密密钥,从而在一定程度上减轻这种风险,但是可以使用这些密钥执行的所有操作(解密、签名等)仍然是可用于那些(可能是恶意的)脚本。

只要您确实信任服务器不会在浏览器中进行恶意行为,加密就会非常有用,但在许多使用加密的应用程序中,对您无法控制的远程服务器的信任级别是不可接受的。

特别是对于您的计划:

  1. 当然,我们将使用 SSL

这很好。如果没有 SSL,所有以后的安全措施都将毫无意义,因为攻击者可以简单地用他们自己的代码替换您的代码,并对用户的数据做任何他们想做的事情。

  1. 当用户第一次登录我们的笔记应用程序时,我们会向他们发送我们的公钥。该密钥将用于验证我们的“crypto.js”脚本的真实性。公钥将存储在用户的浏览器中。

这似乎毫无意义。TLS已经向客户端发送了您服务器的公钥,并使用它来验证您通过该连接加载的所有脚本的真实性。没有理由在 JavaScript 中重新做同样的事情。

  1. “checker.js”脚本也被下载和存储。这个脚本永远不会改变,它将负责检查“crypto.js”和(2)的完整性。

这也是没有意义的,因为无法强制执行“此脚本永远不会更改”的要求。您可以发送一个具有较长 max-age 的 Cache-Control 标头,但不能保证用户代理始终尊重该值;不打算依赖缓存来确保安全性。

  1. 在 (2) 和 (3) 中,我们在用户和我们的网站之间建立了首次使用信任 (TOFU) 关系。公钥和“checker.js”都使用 service worker 或类似的缓存。

需要明确的是:使用服务人员缓存这些文件对整个系统的安全性没有影响。当用户稍后返回您的站点时,浏览器将检查服务器以查看 service worker 是否已更新并安装新版本(如果有)。所以服务器仍然可以完全控制在用户浏览器中运行的代码。这里没有“首次使用信任 (TOFU) 关系”。

  1. 即使我们使用 SSL,在下载 (2) 和 (3) 时可能会发生 MITM 攻击,因此我们可以提供一种方法来检查公钥和“checker.js”是否受到损害。

这是一个很好的姿态,但正如我之前所说,即使这些文件当前没有受到破坏,服务器或 MITM(他们以某种方式设法破坏了您的 TLS 连接)可以随时轻松更新这些文件以破坏它们而不会引起用户注意,所以我真的不明白这个功能的意义。

  1. 首次登录时,我们还将其私钥发送给用户。此私钥将用于加密和签署笔记。此私钥将被加密。

  2. 解密所需的密钥 (6) 通过电子邮件发送给用户。这样我们就建立了双通道认证。

  3. 使用 Web Crypto ( https://www.w3.org/TR/WebCryptoAPI/ ) 我们用 (7) 解密 (6)。通过这种方式,(6)永远不会存储在浏览器中被解密,并且由于 Web Crypto API 的存在,JavaScript 无法访问它。

实现这一点需要服务器有权访问用户私钥的明文版本。根据您使用这些密钥的确切用途,如果服务器遭到入侵,这可能会出现问题。相反,您应该考虑使用 Web Crypto API 在用户设备上生成私钥-公钥对,并让浏览器将该密钥的公钥部分发送到服务器。这样服务器就永远无法访问用户的私钥。

  1. 现在我们可以从 Web 应用程序的功能开始:创建加密笔记。为此,用户写一个便条并单击保存按钮。服务器发送使用服务器私钥签名的“crypto.js”(参见 2)。

  2. 使用在 (2) 和 (3) 中下载的公钥验证签名,如果正确,则对票据进行加密。如果“checker.js”被修改,SRI 应该停止这个过程。

除非您checker.js从不受信任的第三方服务器加载,否则在这种情况下不需要子资源完整性。任何可以破坏您的服务器或其与客户端的连接进行修改checker.js的人也可以修改子资源完整性哈希的值,以便浏览器可以毫无怨言地接受修改后的脚本。或者他们可以只修改页面以完全不加载checker.js,并使用他们自己制作的完全不同的脚本。无论哪种方式,子资源完整性都无济于事。

  1. 笔记被发送回服务器并存储。

只要您解决了我在 6、7 和 8 中提到的问题,就可以了,这样服务器就没有解密用户文件所需的密钥。如果您对拥有访问用户文件的密钥的服务器感到满意,则根本不需要客户端加密;只需让服务器处理加密即可。

  1. 根据所需的功能,服务器应删除用户的私钥并仅保留或不保留公共密钥。

或者,正如我所建议的,首先不要给服务器用户的密钥。除此之外,这部分在安全方面很好,因为它可以防止服务器在用户不使用站点时访问用户的文件。

但是,一旦用户访问该站点,用户的浏览器将从该服务器加载代码,该服务器将能够使用用户的密钥来解密用户的笔记。所以对于普通用户来说,访问他们的笔记而不让你的服务器能够阅读它们是不可能的。

此实现还存在一些可用性问题,因为这意味着用户将无法从新浏览器登录他们的帐户,并且仍然可以访问他们的笔记。更好的实现是使用具有高工作因子的密钥派生算法(如 PBKDF2(可通过 Web Cryptography API 获得))从用户的密码中派生出用户的加密密钥。这将允许他们从任何浏览器访问他们的笔记。(但仍然会有我上面评论中提到的所有相同的安全缺点。)

对我来说真正突出的是 6 和 7。这是迄今为止最让我对这个描述感到畏缩的事情。

设置 TOFU 的全部要点是有两种方式的信任。当然,首次使用信任有其自身的问题,我相信您已经概述了大多数情况,虽然不太可能发生,但都是可能的。

但是你告诉我,网站会为我生成一个私钥然后把加密的密钥交给我,然后给我一个通过电子邮件解密私钥的方法?基本上向我发送了一种解密方式,我通过电子邮件解密的方式。

我的意思是当我使用一项服务时,我通常会寻找平等的曝光率。我不希望网站成为我所做的任何事情的单点故障。它还造成了这样一种情况,即任何对我而言具有系统管理员访问权限的用户生成的私钥都可以解密对我来说意味着的消息。这意味着我不能相信它。

它完全削弱了非对称密码学的全部意义。特别是因为创建我自己的私钥并向服务器发送公钥是一件简单的事情。即使对于技术上不喜欢的用户,它也可以包含在客户端中。绝对没有理由让另一方为我创建私钥 IMO 或让该密钥接触互联网。

其他点我让其他人回答,我认为6和7是最危险的。除非您在 OP 中已经提到过 MITM。

我进行客户端加密的唯一上下文是散列客户端凭据(密码、信用卡信息等),以便服务器不知道它们。是的,服务器不需要知道它必须检查有效性的纯密码。服务器对其接收的密码字符串应用自己的散列。只要客户端发送相同的哈希,该方案就可以正常工作。当服务器不知道客户端明文密码/客户端散列算法时,有效密码得到验证。过去,当我们的一些客户不遵守我们在 https/tls 上实施系统的建议时,我曾经加密/解密用户名。加密/解密用户名对 SSL 通信毫无意义,因为服务器知道/存储用户名。我是什么意思?只要上下文不会使服务器对客户端的秘密数据不可知,我不推荐客户端加密。它是为了保护客户端的秘密免受服务器的攻击,或者保护服务器免受神圣规模的漏洞。在所有其他情况下,不要偏离主干道(https、tls 等)

PS 当涉及到记录管理、文档管理上下文时,有些用户在密码中使用用户名中的单词是愚蠢的。我最喜欢的攻击是对用户名的定时攻击,然后如果对密码的定时攻击直接失败,则使用用户名作为对密码的字典攻击的宝贵资源。有趣的是,文档/记录管理软件开发世界中很少有人关心对用户名的定时攻击。