密码是一个让不少人头疼的问题,大多数人会立即承认密码很难记住和管理,特别是当密码要求变得越来越复杂时。幸运的是,有一些很棒的软件包和浏览器插件可以帮助管理密码。但是不幸的是,密码系统更大的、根本的问题超出了软件所能解决的范围。
这个根本问题解释起来很简单,但解决起来却很困难:如果密码离开了您,那么无论其复杂性或猜测有多困难,都肯定会牺牲安全性。密码的存在本身就是不安全的。
您可能会说:“但是密码始终以加密格式存储!”或者更准确地说,它们可能存储为加盐哈希,这样很好。但是糟糕的是,用户无法验证密码的存储方式,因此我们可以假设在某些服务器上密码以明文形式存储(例如,Facebook 在 2019 年检测到它意外存储了数亿个明文用户密码)。事实上,即使是负责任地存储的密码也可能被泄露和破解,尽管(值得庆幸的是)需要付出巨大的努力。一个日益紧迫的问题源于密码系统本身的性质:时至今日,任何直接使用密码都意味着必须以明文方式处理密码。
当然,您可能会说:“但我的密码是通过 HTTPS 安全传输的!”、“服务器以散列形式存储我的密码,这是安全的,所以没有人可以访问它!”毫无疑问,这给了服务器很大的信任,而且这些也确实是保障密码安全性的手段。然而,这其中仍然存在一个风险点:一旦服务器收到密码,在安全传输和安全存储之间,必须读取和处理密码。是的,以明文方式处理密码!
为此,我们引入了密码验证密钥交换 (Password-Authenticated Key Exchange 或简称 PAKE) 协议,可同时实现密码证明与派生密钥。在说明 SRP 的具体细节之前,我想先简单介绍一下 PAKE。
# 通过 PAKE 实现密码证明
密码验证密钥交换 (Password-Authenticated Key Exchange 或简称 PAKE) 由 Bellovin 和 Merrit 于 1992 年提出,最初的动机是为了在不安全的信道上实现能够防御字典攻击的密码验证。
本质上,PAKE 是一种普通或对称的加密协议,允许仅共享密码的两方建立强大的共享密钥。PAKE 的目标是:
- 如果密码匹配,密钥将匹配,否则随机显示
- 参与者不需要信任第三方(特别是不依赖公钥基础设施)
- 不参与协议的任何人(包括知道密码的人)都不会获悉生成的密钥
- 该协议不会向对方或窃听者透露任何一方的密码(除非密码匹配)
因此,成功攻击协议的唯一方法就是在参与协议的同时正确猜测密码。(此类攻击主要可以通过速率限制来阻止,即在一定次数的错误密码尝试后阻止用户登录)。
由此观之,password-over-TLS 显然不满足 PAKE 协议的要求:
- 它依赖于 WebPKI,后者信任称为证书颁发机构的第三方
- 用户的密码会泄露给服务器
- TLS 密码无法向用户保证服务器知道他们的密码或其派生密码——服务器可以接受用户的任何输入而不进行任何检查。(换句话说,服务器可以无条件接收用户所尝试的密码,即使他们并不知道任何用户密码)
虽然看上去 PAKE 很不错,但是它仍然很糟糕,因为它要求服务器存储用户的明文密码,为了解决这个问题,我们引入了 aPAKE (非对称PAKE 或称为 asymmetric PAKE)。之所以称为 非对称PAKE 是因为只有客户端知道密码,而服务端只知道散列密码(加盐哈希值)。
因此 aPAKE 除了具有 PAKE 的四个属性之外,另外还有一个重要特性:
- 窃取服务器上存储的密码数据的攻击者必须为每个用户执行字典攻击才能在数据泄露后检索密码
当然 SRP 协议并不是完美的,因为它需要在用户登陆前将用户的盐传给用户,这也就意味着攻击者可以在窃取用户的散列密码之前计算用户的彩虹表。(OPAQUE 协议避免了这个问题,但它并不会在本文中提到)
# SAP 如何运作?
斯坦福的 SRP 文档 中列出了SRP 的 4 个不同版本,最后一个是 SRP 6。我不确定版本 4 和 5 在哪里,但版本 6 是在 TLS 中标准化和实现的版本,除此之外还有修订版 SRP 6a,Termius 似乎正在使用这个版本的协议。
下图是注册流程,这里我们以用户 Atom 作为示例。
用户 Atom 注册时,前端/客户端提前生成 salt,然后接受 Atom 输入的用户名 Atom 以及密码, 随后使用一定的哈希算法计算密码的加盐哈希值,并将用户名(在一些系统中这个可能是邮箱地址或其他能确保唯一性的字段)、盐、密码的加盐哈希一并发送给后端。
后端收到注册请求后,将通过用散列密码对预定环(具有乘法运算的加法群)的生成器求幂来算出 v,并将其与用户名、盐一并存入数据库。至此,整个注册流程结束。
从上述流程图中您可以看出,这个协议在一开始就使用了哈希函数,因此看到此消息的任何人都可以有效地暴力破解哈希密码来获取原始密码。不过,使用用户生成的盐可以成功防止影响所有用户的暴力攻击。除此之外,您会发现在不知道原始密码的情况下,任何知道 x 的人都可以冒充 Atom。
下图是登陆流程(基于github.com/1Password/srp,部分流程可合并),同样我们以用户 Atom 作为示例。
用户 Atom 登陆时,客户端向后端发起请求获取 Atom 的盐,然后客户端向服务端生成临时公钥A,后端响应临时公钥B以及ServerProof。
客户端收到 ServerProof 后检查服务端响应是否正常,如果正常则发送ClientProof,服务端若校验成功,则认为密码证明成功(登陆成功)。
# 今天应该使用 SRP 吗?
毫无疑问,SRP 方案是处理用户密码的一种更好的方法,但它仍然有许多缺陷,使得其实现的 PAKE 协议不太理想。例如,拦截注册过程的人可以轻松冒充 Atom,尽管协议中从未直接使用密码,而是在注册过程中传递的密码的加盐哈希值。
多年来,多名安全研究人员注意到了这一点。马修·格林 (Matthew Green) 在 2018 年的Should you use SRP?中写道:
为了避免您认为这些积极结果都是有意设计的,我要指出的是,SRP 协议有[五个先前版本],每个版本都包含漏洞。因此,目前的状态似乎是通过一个打磨过程而不是设计过程而达到的。
在注意到乘法和加法的组合使得它不可能在椭圆曲线群中实现后,Matthew Green 得出结论:
总而言之,SRP 很奇怪。它创建于 1998 年,具有加密货币史前时代发明的协议的所有标志。它已经以各种方式反复被破坏,尽管最新的 [v6] 修订版似乎并没有明显被破坏——只要你小心地实现它并使用正确的参数。它没有任何值得一提的安全证明,尽管有些人会说这并不重要(我不同意他们的观点。)
此外,SRP 在最新版本的 TLS (TLS 1.3) 中不可用。
此后,许多方案被提出,甚至被标准化和生产化(例如PAK在 2010 年被 Google 标准化)。
2019 年夏天,IETF 的加密货币论坛研究组 (CFRG)启动了PAKE 选择流程,目标是为每一类 PAKE(对称/平衡和非对称/增强)选择一种算法进行标准化:
2020 年 3 月 20 日 CFRG 宣布 PAKE 遴选流程结束,选出:
- CPace 作为对称/平衡 PAKE(来自 Björn Haase 和 Benoît Labrique)
- OPAQUE 作为不对称/增强 PAKE(来自 Stanislaw Jarecki、Hugo Krawczyk 和 Jiayu Xu)
综上所述,我建议您在选择 SRP 前再去了解一些其他的协议,或许会有更适合的选择。