原文: OPAQUE: The Best Passwords Never Leave your Device
作者: Tatiana Bradley
更新于: 2022/01/19@2025/12/17 文章已经大体翻译完成,但是一些词句仍需斟酌
密码很麻烦,原因绝大多数读者都很清楚。对于Cloudflare而言,问题存在得更深层,分布得也更广泛。当下密码的复杂度要求变得越来越严格,相信大多数读者都会觉得密码非常难以记忆和管理。幸运的是,有一些很棒的软件和浏览器插件帮助我们管理密码。然而,某些存在于更深层次的根本问题已经超出的这些软件能解决的范围。
密码的根本问题很容易阐明,却非常难解决:无论密码多复杂,多难以猜测,一旦离开了你的掌控,密码的安全就很难保证。换而言之,密码存在的本身就是一种不安全因素。
你可能会说:“但密码都是加密储存的!”那很好了。更准确的说,密码很可能以加盐的摘要形式储存,就如同下面所说的。不幸的是,你没办法确认密码是以何种形式存储的,可以猜测某些服务商正在以明文形式存储密码。现在的现实是,即使厂商负责任的存储了密码,密码仍然可能被泄漏和爆破,不过需要付出巨大的成本(至少这一点值得庆幸)。有个日益显现的问题源于密码本身的性质:如今,任何需要使用密码的地方,都意味着需要明文处理密码。
你说:“但密码是通过HTTPS安全传输的!”这倒确实。
你说:“但我知道服务器以摘要形式存储我的密码,非常安全,没有任何人能获取他!”好吧,也许你对服务商很有信心。忽略那些不负责任的服务商,这倒也确实。
即便如此,整个过程还遗留着一个重要的漏洞:密码的使用过程中存在一个安全真空。当服务器通过加密安全地传输并收到一个密码,并在安全地存储该密码之前,密码必须被读取和处理——没错,以明文的形式。
更糟糕的是,人们更常从软件层面来考虑这些问题,很容易忽略硬件带来的漏洞。即使软件万无一失,密码也必定在某一时刻存在于内存中,也必须在某一时刻存在于共享总线以传输到CPU。这向旁路攻击者暴露了许多种形式的攻击面。当然,这些攻击面的存在可能性远远低于那些存在于传输和存储过程中的问题,但他们的严重性可一点不低(例如最近的一些CPU漏洞,幽灵(Spectre)和熔断(Meltdown),只能说警钟撅烂)。
解决这些问题的唯一办法就是把所有密码全部干掉。这确实有可能!研究和私营部门正在努力尝试去达成这一目标。新标准正在如雨后春笋般冒出,并日渐成熟。理想很丰满,但密码太常见了,需要非常长的时间才能就新标准和新技术达成共识并用于取代密码。
在Cloudflare,我们正在寻找一些能立刻、马上实现的手段。今天我们要探讨的OPAQUE是其中一种可能的解决方案。OPAQUE是一种在密码不离开你的掌控范围的情况下,确保密码有效的众多系统之一。没人喜欢密码,但密码仍然在被使用,就算如此,至少我们可以保证密码不被泄漏。
如果要说有谁要承认基于密码的认证很恼人,那我就是第一个。密码——难以记住,难以输入,难以保证安全。减少密码使用或完全替代密码的倡议未来可期。例如,WebAuthn就是一种主要使用硬件(或软件)密钥来使用公钥进行Web身份认证的标准。即便如此,密码仍然非常不幸的以一种身份验证机制存在,大概是因为密码认证很容易实现,用户学习成本低,或者只是因为密码在互联网和其他地方很常见。我们只能让基于密码的认证方式在被替代之前尽可能的安全。
我在Cloudflare的实习主要聚焦于OPAQUE,OPAQUE是一种加密协议,解决基于密码的认证系统里了一个显而易见的安全问题:尽管大多数情况下密码在传输过程中已经被HTTPS加密保护,服务器仍然需要以明文方式来校验密码的正确与否。明文处理密码很危险,若不小心被缓存,或被记录到日志中,都会导致灾难性的安全漏洞。这个项目的目标并不是推荐众多方案中的某一种,而是为了证明OPAQUE是其中可行的一种方案。我比较熟悉Web技术,而且基于Web的技术受众大概更广,接下来我会使用Web作为主要的例子。
Web认证基础课:使用TLS加密传输密码(Password-over-TLS)
当你在网页上输入密码时到底发生了什么?网站需要确认你输入的密码与你原本注册这个网站时使用的密码相同。但这一切是怎么运作的?
通常来说,你的用户名和密码被发送到服务器,服务器会检查与用户名关联的密码是否与你提供的密码相同。当然,为了防止攻击者窃听你的网络流量来窃取你的密码,你与服务器之间的连接会使用HTTPS加密(HTTP-over-TLS)。
就算使用了HTTPS,这个过程中仍然存在一个显而易见的问题:服务器必须在某处以某种形式保存你的密码。服务器很难保证完全安全,被黑入是常有的事情。泄漏保存的内容会导致灾难性的安全问题。(要想了解最近的服务器入侵事件,可以看看https://haveibeenpwned.com/。)
为了降低泄漏事件的严重性,服务器通常会使用哈希(Hash)函数来保护用户的密码。哈希函数会将每个密码映射到独一无二的、看起来完全随机的值。对密码做哈希很简单,但想要反过来,从哈希后的值反推原始密码几乎是不可能的事情。(这也意味着,任何人都可以猜测一个密码,对其使用哈希函数,然后检查结果是否相同。)
在密码被哈希后,服务器上不再存储明文密码。即使攻击者窃取了密码数据库,他也不能直接获取密码。攻击者需要对海量的可能使用的密码做哈希,然后再于泄露的哈希值做比较。
但很不幸,如果服务器只将密码输入哈希函数中,攻击者可以下载一份已经计算好的彩虹表,表中包含数十亿个可能的密码的哈希值,几乎可以瞬间查出原始的密码。(可以看看https://project-rainbowcrack.com/table.htm,里面有包含一些彩虹表的列表)。
考虑到这一点,使用加盐哈希是一种更进一步的防御方式,服务器在密码后增加一段每个用户都不同的随机值然后再进行哈希,这个随机值称为盐。这个盐与用户名一同存储,所以用户不会看见,也不需要提交盐。当用户提交一个密码时,服务器将密码与盐一同输入哈希函数中进行计算。即便攻击者窃取到密码数据(加盐哈希后的密码和盐本身),也需要猜测常见的密码,然后一个个输入加盐后的哈希函数中。现有的彩虹表没有任何作用,因为彩虹表并没有考虑盐的加入,所以攻击者需要为每个用户创建一个新的彩虹表!
这能有效的减缓攻击者(也许吧),让厂商有足够的时间通知用户网站遭到入侵,以便用户修改密码。此外,应该多次应用哈希函数使得加盐的哈希更难被破解,从而进一步减缓攻击速度。(可以参考https://blog.cloudflare.com/keeping-passwords-safe-by-staying-up-to-date/,里面有更详细的讨论)。
这两个缓解策略:传输过程加密和加盐反复哈希存储,是当下最佳的做法。
不过,有个很大安全漏洞仍然没有被封堵。使用TLS加密传输密码仍然需要用户在登录时以明文形式将密码发送到服务器,因为服务器必须要读取这个密码,以便与文件中保存的密码进行匹配。即使是非恶意的服务器,也可能会意外的缓存或日志记录你尝试登录时用的密码,或者在检查密码时发生意外。(例如,Facebook检测到2019年时他们意外的保存了数亿个明文用户密码)。理想状况下,服务器不应该读取到任何明文的密码。
但这听起来有点扯:如何在完全没见过密码的情况下检查密码是否正确?那就得请OPAQUE出场了,OPAQUE是一种密码认证的密钥交换(Password-Authenticated Key Exchange,PAKE)协议,能通过双方同时证明“我知道密码”来交换密钥。在详细讲解OPAQUE之前,我们先总结一下PAKE的通常的实现方法。
使用密码认证密钥交换协议证明“我知道密码”
基于密码认证的密钥交换(Password-Authenticated Key Exchange,PAKE)最初由Bellovin和Merrit[1]于1992年提出,最初的目的是在不安全的信道上传输数据时,保护密码认证不被字典爆破攻击。
本质上,普通或对称的PAKE是一种加密协议,使知道同样密码的两个人建立一个复杂的共享密钥。PAKE的目标是:
- 密码相同时,密钥就会相同,否则密钥看起来完全随机。
- 参与通信的人不需要可信的第三方担保,也就是说不需要公钥设施。
- 就算知道密码,只要没有参与通信,任何人都不知道最终的密钥。
- 协议不需要向协议中的任意一方透露密码(除非他们的密码相同),也不会向窃听者透露任何一方的密码。(译注:当通信中的双方密码相同时会得到同样的密钥,借此可以得知对方的密码就是自己的密码。)
总的来说,唯一能破解该协议的方法是在参与通信时成功猜测到正确的密码。(幸运的是,这些攻击者非常可能直接被请求限速之类的东西拒之门外。)
根据这些要求来看,password-over-TLS显然不是一种PAKE,因为:
- 他依赖WebPKI,需要信任第三者,也就是信任证书颁发机构(Certificate Authorities,CA)。(参考https://blog.cloudflare.com/introducing-certificate-transparency-and-nimbus/,深入了解WebPKI和他的不足之处。)
- 服务器需要知道用户的密码是什么
- Password-over-TLS不能向用户保证服务器知道他的密码或密码的衍生值,服务器能接受用户的任何输入,而不需要进行任何检查。
就算如此,纯PAKE也还是比Password-over-TLS更糟糕,因为它要求服务器存储明文密码。为了解决这个问题,我们需要一种新的PAKE,允许服务器存储加盐的哈希。
非对称密码认证密钥交换(asymmetric PAKE,aPAKE)是一种比纯PAKE更好的协议,因为只有客户端才知道密码,而服务端只知道经过哈希的密码。aPAKE拥有PAKE的4个基本特性,同时还额外拥有一个特性:
- 即使攻击者窃取了服务器上的密码数据,他仍然需要进行字典攻击才能获得原密码。
但现有的aPAKE协议也并非完美无缺,它们不能使用加盐的哈希(如果想使用加盐的哈希,就需要将盐告诉用户,这使得攻击者在窃取用户数据前就可以提前获取到盐,然后提前计算彩虹表)。因此,我们还需要一些额外的安全特性:
- 即使攻击者窃取到了保存在服务器上的密码数据,也需要对每一个用户的密码都做字典攻击才能获取到原本的密码。
OPAQUE是第一个具有形式化安全证明的aPAKE协议,使用完全保密的盐值。
OPAQUE - 服务器在不知道机密数据的情况下保护机密!

OPAQUE是一种强aPAKE,这个协议通过使用一个保密的加盐哈希值来防御预先计算攻击。OPAQUE由Stanislaw Jarecki,Hugo Krawcyzk和Jiayu Xu于2018年提出并进行了形式分析。(利益相关:Stanislaw Jarecki是我的学术顾问。)OPAQUE这个名字是由OPRF和PAKE两个加密协议的名字组合起来而得到的。OPRF是什么?OPRF全称是Oblivious Pseudo-Random Function,无意识伪随机函数,该协议中的双方会计算一个输出结果确定的函数F(key, x),得到一个看起来完全随机的结果。其中一方输入x,另一方输入key,输入x的一方知道F(key,x)的结果但是不知道key是什么,输入key的一方则完全无法得到任何信息。(要详细了解OPRF的数学原理,可以参考https://blog.cloudflare.com/privacy-pass-the-math/。)
OPAQUE的核心是一个在服务器不知道这些机密的情况下安全保存用户机密的方法。服务器上不再保存密码的加盐哈希,而是保存由两份信息“锁定”的机密信封:只有你才知道的密码和只有服务器才知道的随机密钥(就像盐)。登录时,客户端会发起一次加密交换,以获取信封的钥匙,而这一过程中最重要的是,服务器完全无法获取这个密钥。
接着服务器会将信封发送给用户,用户可以获取这个加密密钥。(信封里的密钥是一个客户端用的公私钥密钥对,和服务器的公钥。)这些密钥解锁后会被输入进一个认证密钥交换(Authenticated Key Exchange,AKE)协议,使客户端和服务器能协商一个用于加密之后通信的密钥。
OPAQUE有两个阶段,凭据登记和密钥交换登录。
OPAQUE:凭据注册阶段
在登记凭据前,用户会先选择用户名和密码用于注册服务。接下来登记会遵循之前所说的OPRF步骤:Alice(用户)和Bob(服务器)进行一次OPRF交换。交换完成后,Alice会有一个从OPRF结果_F(key, pwd)_派生出来的随机密钥rwd。key由服务器持有,是服务器为Alice生成的密钥,独一无二;而pwd就是Alice的密码。
在有了这个OPRF消息后,Bob发送OPAQUE标识的公钥。Alice生成一个新的公私钥密钥对,这个密钥对将作为他在Bob的服务上的永久OPAQUE标识,并使用Bob的公钥和rwd加密她的私钥(结果被称为_加密信封_)。她将这个加密信封与她的(没有加密的)公钥一同发送给Bob,Bob将她提供的数据与她专属的OPRF密钥存储到数据库中,并使用用户名作为这些数据的索引。

OPAQUE:登录阶段
登录阶段非常相似。登录阶段如同注册阶段,从一次OPRF流程开始。但不同之处在于,Bob(服务器)不再生成新的OPRF密钥,而是从数据库中查找在注册阶段保存的专属于Alice的密钥。Bob通过使用Alice在第一条信息中提供的用户名来完成查找,并获取对应的记录。记录包括Alice的公钥,她的加密信封,和Bob与Alice通信时的专用OPRF密钥。
Bob同样发送通过加密信封发送,Alice能使用OPRF输出的结果来解密这些数据。(解密失败时,Alice就会终止协议,这通常意味着Alice输入了错误的密码,或者对方并不是Bob。)如果解密成功,她就拥有了自己的公私钥密钥对和Bob的公钥。Alice将这些输入AKE协议,接着Bob就会输入他自己的私钥和Alice的公钥,这样双方就会都有了一个新的共享密钥。

将OPAQUE与AKE集成
有个重要的问题还有待解决:哪种AKE适合OPAQUE?新兴的CFRG规范概述了几种选择,例如3DH和SIGMA-I。而在网页上,我们早就有了一种选择:TLS。
TLS是一种AKE,因为它提供了单边和双向的认证,并且采用共享密钥派生机制。TLS的核心是Diffie-Hellman密钥交换,这个交换是不需要认证的,也就是说参与者无法验证彼此的身份。(这会导致一些问题,假如你要登录银行,或者什么其他存储了你的机密数据的网站,你肯定需要确保他的身份跟他声明的一致。)认证主要使用证书,证书由可信机构通过公钥基础设施(Public Key Infrastructure,PKI)颁发。每个证书都与一个私钥关联。为了表明自己的身份,服务器向客户端提供自己的证书,并使用私钥对TLS握手进行签名。
直接修改这个被广泛使用的基于证书的认证可能不大现实。我们可以退一步,在TLS握手完成后,使用OPAQUE对TLS共享密钥进行认证。换而言之,当服务器使用传统的WebPKI证书完成认证后,客户端可以接着向服务器进行身份认证。这个认证可以使用OPAQUE在TLS连接握手后完成。
Exported Authenticators是TLS握手后认证的一种机制。它们允许服务器或者客户端提供身份证明,而不用建立一个新的TLS连接。在标准的Web场景下,服务器通过证书建立它们的身份证明(例如,证明它自己是“cloudflare.com”)。但如果服务器有一个其他的身份,它必须重新建立一个TLS连接来证明他的另一个身份。(译注:Exported Authenticator 没有标准译名,按字面意翻译后容易被误解为“从验证器应用导出的TOTP验证器”,故保留原文以表明其是一个专有名词。)
最基础的Exported Authenticators流程类似于传统的“质询-回复(Challenge-Response)”协议,其工作原理如下(我们只考虑服务器验证的情况,客户端验证与其相同):

在TLS连接建立后的任意时刻,Alice(客户端)发送一个验证请求,表示他希望Bob(服务器)证明一个额外的身份。这个请求包括一个上下文(一个不可预测的字符串,可以将这个视为一个质询)和一个拓展,拓展内包括希望提供哪个身份证明的信息。例如,客户端可以包含一个SNI拓展,以在TLS连接中要求服务器提供一个不同于最初使用的域名的证书。
收到来自客户端的消息后,如果服务器有一个有效的证书来响应这个请求,服务器就会返回一个Exported Authenticator,证明服务器拥有这个证书的私钥。(这个消息与TLS 1.3 握手时使用的Auth消息的格式相同,里面包括一个Certificate,一个CertificateVerify和一个Finished消息。)如果服务器不能或者不想提供所请求的证书的认证,服务器就会返回一个空的验证器,只包括一个Finished消息。
然后客户端检查收到的Exported Authenticator的格式是否正确,接着验证其提供的证书是否有效,如果有效,就认可服务器提供的新身份。
总的来说,Exported Authenticator 利用 TLS 中经过充分验证的加密方法和消息格式,提供了一种能在七层模型中的高层(例如在应用层)安全使用的验证方法。此外,它与TLS会话绑定,验证消息无法从一个TLS会话里复制粘贴到另一个会话中。换而言之,Exported Authenticator提供了一个绝妙的接口,使得基于OPAQUE的认证能添加到TLS中。
基于Exported Authenticators的OPAQUE (OPAQUE-EA)

OPAQUE-EA使得OPAQUE在TLS连接建立后的任何时刻都可以运行。Bob(服务器)将存储他自己的OPAQUE身份信息(在OPAQUE-EA中是签名密钥和验证密钥),Alice(客户端)会她自己的身份信息加密存储在Bob的服务器上。(在OPAQUE-EA的注册流程中,Alice存储自己的加密密钥的步骤与普通的OPAQUE大致相同,唯一的不同点在于她保存的是一个签名密钥,所以在此之后会直接跳到登录流程。)Alice和Bob会执行两个“请求-验证”EA(Exported Authenticator)流程,每个人各一次,OPAQUE协议的信息通过EA的拓展部分传递。让我们看看其中的细节。
首先,Alice基于她的密码生成她的OPRF消息。她创建了一个验证请求(Authenticator Request)来获取Bob的OPAQUE身份信息,并在EA拓展部分中填入自己的用户名和OPRF消息,通过已经建立的TLS连接发送给Bob。
Bob收到消息后,在他的数据库中根据Alice的用户名查找对应的记录。Bob找到了Alice的OPAQUE记录,里面包括Alice的验证密钥和加密信封,以及Bob自己的OPRF密钥。他将OPRF密钥用于OPRF信息,创建了一个在拓展部分包括他自己的OPRF信息和加密信封的EA来证明自己拥有他的OPAQUE签名密钥。此外,他还发送了一个新的Authenticator Request,要求Alice证明她拥有她自己的OPRF签名密钥的所有权。
Alice解析了信息。并使用Bob的信息完成OPRF评估,以获得输出rwd。接着Alice使用rwd解密信封,得到她自己的签名密钥和Bob的公钥。他使用Bob的公钥去验证他的Authenticator Response证明,如果验证成功,她就会创建一个Exported Authenticator证明她持有刚刚解密的签名密钥。Bob检查她发回的Exported Authenticator的有效性,如果有效,他就会允许Alice登录。
我的项目:基于HTTPS的OPAQUE-EA
以上所有东西都有大量的理论支持,但仍缺乏实践。我的项目把理论变成了现实。我首先撰写了Exported Authenticator和OPAQUE的基本定义,和OPAQUE-in-TLS的基础草案。我的目标是把他们变成一个基本能用的原型。
我的示例项目展示了在Web上实现OPAQUE-EA的可行性,将密码(即使是加密的)彻底从传输过程中抹除。这提供了一种用于替代当前常用的password-over-TLS流程的方案,拥有更好的安全性,对用户没有可感知的变化。
一些实现细节很值得注意。在计算机科学中,抽象是一个很有用的工具。他意味着能依赖现有的工具和API以避免重复工作。在我的项目中重度依赖于mint,一个开源的Go语言TLS1.3 实现,非常适合做原型。我也使用了CIRCL的OPRF API。我构建了这些库用于Exported Authenticator,也就是OPAQUE和OPAQUE-EA的核心,用于将两者联系起来。
我制作了Web示例,通过将OPAQUE-EA的功能包装进一个简单的HTTP服务器和客户端,双方可以通过HTTPS传递消息。由于浏览器不能直接运行Go,我将Go编译成了WebAssembly(WASM)让Go在浏览器里工作起来,并写了一个简单的JavaScript脚本去调用需要的WASM函数。
由于浏览器不允许在客户端侧访问底层的TLS连接,我不得不实现一个workaround以允许客户端访问exporter密钥,具体来说,就是服务器计算密钥并通过HTTPS发送给客户端。这个workaround降低了示例的安全性,因为这意味着安全性要靠服务器提供了正确的密钥来保证。即便如此,即便恶意的服务器提供了错误的密钥,用户的密码仍然是安全的,它们无法确定它们是否之前真的在这个服务器上注册过。在未来,浏览器可以提供一个基础设施,以支持exported密钥,并允许OPAQUE-EA以完整的安全特性运行。
你可以在GitHub上浏览我的实现,甚至可以根据说明搭建你自己的OPAQUE-EA的测试服务器和客户端。不过我还是要强调,这个实现只是一个概念验证的原型,不要在经过进一步的深入审查之前用于生产环境系统。
OPAQUE-EA的局限性
尽管OPAQUE-EA有很多优点,但想要将它从一个PoC变成完善的认证机制,仍然存在一些挑战。
浏览器需要支持TLS exporter keys。在之前简短的提到过,想要在浏览器里运行OPAQUE-EA,你需要能够访问TLS连接中称为exporter keys的密钥。这在常用的浏览器中完全没有办法做到,这需要它们添加这项功能的支持。
需要对密码数据库进行重大的修改。为了适配OPAQUE-EA,服务器不仅需要更新他的密码检查逻辑,也需要对数据库进行大量的修改。因为OPAQUE并不直接保存密码,而是基于一种特殊的密码表示,只能在用户交互下生成,现有的加盐哈希密码不能自动升级为OPAQUE记录。服务器大概率需要对每个用户都运行一次特殊的OPAQUE注册流程。因为OPAQUE依赖于服务端与客户端双方的认可,故服务器需要在客户端完全支持OPAQUE只签名保留原有鉴权方法的支持。
依赖新兴标准。OPAQUE-EA依赖于正在标准化进程中的OPRF和还处于草案阶段的Exported Authenticators。这意味着这些依赖并未在大多数现有的加密库中受到支持,若想提前使用OPAQUE-EA,用户可能需要自己实现这些依赖。PAKE,特别是aPAKE,允许服务器在不知道密码的前提下实现安全登录。
总结
只要人们还在使用密码,我们就需要让密码使用的过程变得尽可能的安全。现有的方法依赖于在服务端以明文的形式检查密码的正确性,而这存在风险。
其他公司也在探索OPAQUE的使用。基于Facebook的Novi Research小组成员Kevin Lewi的说法,他们“对OPAQUE提供的强力加密保障感到兴奋,而且正在积极研究使用OPAQUE来进一步保护存储在服务器上的凭据保护的字段”。
OPAQUE是aPAKE中最出色的一种,可以完全整合进入TLS。你可以在这里查看OPAQUE的核心实现,也可以在这里查看将其整合进TLS的示例。一个可以使用的示例也可以在这里试用。一个OPAQUE的TypeScript客户端实现即将完成。如果你对实现这个协议感兴趣,或者在当前的实现中发现了任何bug,可以通过mailto:[email protected]联系我们!也请订阅IRTF CFRG邮件列表跟踪OPAQUE的规范化和标准化讨论。
[1] Bellovin, S. M., and Merritt, M. “Encrypted key exchange: Password-based protocols secure against dictionary attacks.” In Proc. IEEE Computer Society Symposium on Research in Security and Privacy (Oakland, May 1992), pp. 72–84.