写点什么

SRP (Secure Remote Password Protocol)

作者:Geek_44385e
  • 2024-02-01
    广东
  • 本文字数:7193 字

    阅读完需:约 24 分钟

SRP (Secure Remote Password Protocol)

背景知识:何为认证

认证是一方通过某种方式向另一方证明他是他说声称的实体的过程。有许多种认证协议,有的简单,有的复杂,有的能对抗攻击者的攻击,但所有的认证协议可以分为 3 大类(摘自这里):


  • Something the user knows (passwords, PINs)

  • Something the user has (ID cards, smartcards)

  • Something the user is (voiceprint identification, retinal scanners).


有的系统会通过使用其中一种方式,或组合多种方式,但目前通过密码来完成认证是最常见的方式。保护密码至关重要,那么如何才能确保密码的安全呢?


为什么需要 SRP

传统的基于用户名-密码的应答(challenge-response)协议,通常是在服务器端保存密码明文的 Hash(但很多选用的 Hash 算法并不正确),好一点的是加盐 Hash。更有甚者直接保存明文密码,或者简单使用 base64 编码,详情查看https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol


但所有这些方式都不够安全,密码明文就不用说了,直接 Hash 通过彩虹表破解难度也不高,加盐 Hash 相对来说要好一些,但仍然可被字典攻击,而且很多网站的实现并不规范,它们所使用的盐(Salt)并不是随机的,而是使用用户名或用户 ID 来作为盐(TODO 随机的盐就能更安全吗?加盐是为了防止撞库??)。而且对被动攻击毫无抵抗力,一旦服务器被拖库,或者攻击者通过中间人攻击(https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)拿到密码或其 Hash 值,攻击者就可以直接冒充用户访问用户资产。


我们所需要的是一种 Zero-knowledge proof (ZKP)协议,SRP 正是一种这样的协议。

SRP 是什么

SRP是由斯坦福大学于 1997 年发起开发的一种增强的密码认证秘钥交换(password-authenticated key exchange)协议(PAKE),用于解决在不安全网络环境中安全地交换密钥的问题(通俗地说,就是 client 向 server 证明它有密码,然后双方协商出一个用于对称加/解密的秘钥)。它是 ZKP(Zero-knowledge proof) 协议的一种,Server 不需要存储密码及其等价物(密码 hash),Client 可以安全地向 Server 完成认证,窃听者或中间人无法得到用于攻击的有效信息。


经过数学证明,SRP 的安全性等价于 Diffie-Hellman 秘钥交换协议,而 DH 通常被认为针对被动攻击是安全的,但在防御主动攻击方面的表现并不理想。AuCPace 和 OPAQUE 等较新的 PAKE 提供更强的安全保证。


SRP 具备以下优势:

  • 不需要通过网络传输密码或其等价物(比如密码 Hash),即使是 Server 也不知道密码;

  • Server 只需为每个用户保存一个验证器(verifier),其对攻击者的价值很有限,只有通过在计算量上不可行的暴力破解才能通过它得到密码明文。不过,攻击者可以利用它来冒充 Server,所以 Server 必须妥善保管 verifier。

  • 可抵抗主动 &被动攻击,如中间人攻击、暴力破解、字典攻击等;

  • 不需要引入可信的第三方(如 CA 机构)即可安全地完成认证(Kerberos需要依赖第三方),而且是 Client 和 Server 的双向认证;

  • 额外协商出一个用于加/解密后续通信数据的对称密钥 Session Key,以便让 Client 和 Server 可以在 TLS 之上传输加密数据,进一步增强安全性。


更多信息请参考官方介绍,关于 SRP 的发展史可参考这里

SRP 基础知识

SRP 总体上分为三个步骤:注册(Registration)、认证(Authentication)、验证(Verification)。关于后两者的区别,参考这里


要想深入探索 SRP 的原理,首先需要掌握一些基础知识(如已掌握可跳过),包括:

  1. 离散对数问题(Discrete Logarithm problem)

  2. 迪菲 · 赫尔曼密钥交换(Diffie–Hellman key exchange

SRP 基础数学知识

先来复习下中学的模运算和幂运算,后面会用到。

注:符号 ^ 表示幂运算。

模运算的性质

SRP 所有的运算都会模 N,所以有必要了解下模运算的性质:

  • (a + b) % N = (a%N + b%N) % N

  • (a * b) % N = (a%N * b%N) % N

  • (a ^ b) % N = ((a%N) ^ b) % N


后续所有的计算操作都要模 N,但为了简化表达,会省略模 N 操作。比如:

( (g % N) ^ a ) % N 会被简化记为 g^a。

幂运算的性质

SRP 中很多运算会用到幂运算,所以有必要了解下幂运算的性质:

  • g ^ (a + b) = g ^ (b + a) = (g^a) * (g^b)

  • g ^ (a*b) = g ^(b*a) = (g^a)^b

  • (a * b) ^ n = (a^n) * (b ^ n)

非对称加密算法

大数分解(因式分解)问题

先了解下大数分解:

  • a, b 是大素数,N = a * b;

  • 已知 a 和 b,求 N 非常简单;

  • 但如果只知道 N,求 a 和 b 就非常困难。


大数分解是 RSA 的基础。

离散对数问题

算法描述:

  • 设 a、N、g、A 为正整数:a < N,g < N;

  • 对于等式 A= (g^a) % N,如果已知 g, a, N,求 A 是很容易的;但如果已知的是 A, g, N,想求 a 的值,就非常困难;

  • 也就是说,对于函数 F(a)= (g^a) % N,是单向、不可逆的。


为了对抗字典攻击,要求:

  • N 为素数;

  • N=2q+1, q 也为素数;

  • N 的长度至少是 1024 bits;

  • g 可以选择较小的素数,比如 2 或者 5。


N 和 g 的值如何选择至关重要,RFC 5054 已经列出了一些经过证明足够安全的 N 和 g 组合了。

椭圆曲线加密

离散对数可用量子计算机快速破解,更安全的是椭圆曲线加密。

// TODO


Diffie–Hellman key exchange

参考维基百科

DH 是一种非认证密钥协商协议,用于在不安全信道上安全地协商出一个对称密钥。该协议可以保证前向安全性,为其它各种认证协议提供了算法基础。


关键点

  • a 和 b 是各自随机生成的,不能在网络上传输,不对外暴露;

  • g 和 N 是双方协商的,可对外暴露;

  • A 和 B 也可对外暴露;


D-H 密钥交换的特点

  • 通过网络窃听拿到所有传参,也无法通过算法逆向出密码明文;

  • 通过网络窃听拿到所有传参,想要暴力破解密码在计算量上也不可行;

  • 会话结束后,a 和 b 就可以丢弃,因此该算法具备前向安全性

  • 但可被中间人攻击:攻击者 C 可以拦截在 Alice 和 Bob 中间,伪装成 Bob 和 Alice 交流,再伪装成 Alice 和 Bob 交流。


扩展知识:

DH 是一种秘钥协商协议,双方协商建立一个相同的对称密钥,加密和解密都用这同一个秘钥。而 RSA 是一种秘钥分发协议,一方(通常是 Server 端)持有私钥,并将公钥分发给其它各方(通常是 Client 端),然后 Client 随机生成一个对称密钥,用公钥加密该对称密钥,对方用私钥解密出对称密钥,由于只有 Server 拥有私钥,可确保对称密钥不会泄露(不过签名场景要用私钥加密,公钥解密,因为签名是为了保证消息完整性,而只有一方拥有私钥,拥有公钥的却有很多方)。另外,RSA 不能保证前向安全性。


SRP 发展史

参考这里

SRP-1有漏洞,于是开发了 SRP-2,但 SRP-2 也存在一些问题,所以又开发了 SRP-3,其以另一种方式解决了 SRP-1 的漏洞,并避免了 SRP-2 的问题。

SRP-1

SRP 经过发展,目前最新版本是 SRP-6a。我们先来看看最早的 SRP-1 版本

协议介绍


可对抗字典攻击

SRP-1 可以让窃听者无法得知 Session Key K 的值,进而也无法伪装为 Client,原因是:

窃听者能知道:

  • A,Z,M1

  • 还能知道 M1 和 S 的算法,即:M1 = H(Z | K) = H( (v+B) | H(B^(a+x)) ) = H( (g^x + g^b) | H((g^b)^(a+x)) ) = H( (g^x + g^b) | H(g^(ab) * g^(xb)) )

但他除了不知道 x 之外,还不知道 a 和 b,也就是说他不知道的值有 4 个:g^x, g^b, g^(ab) = (g^a)^b, g^(xb),所以无法进行字典攻击。

SRP-1 的漏洞

但 srp-1 有两个漏洞,详见这里

漏洞 1:中间人冒充 Server,即可字典攻击


中间人攻击者拦截 Client 请求,就能得知 A 的值;然后再冒充 Server,就可以自己生成随机数 b,进而可以知道 g^b 和 g^(ab) 的值。

那么对于 M1 = H( (g^x + g^b) | H(g^(ab) * g^(xb)) ),唯一不知道的只有 x,所以可以进行字典攻击,就有可能计算得到 K 和 M1 的值,进而伪装为 Client。

漏洞 2:Server 被拖库,攻击者可以冒充 Server/Client 完成认证


假设 Server 被拖库,攻击者能冒充 Server 比较好理解,因为攻击者已经知道真实 Server 所知的所有信息,比如 g^x, N, g, s 的值,就可以拦截 Client 请求,冒充 Server 与 Client 通信,Client 是没有能力发现的。注:最新协议 SRP-6a 也没有解决这个问题,所以保护好 Server 数据至关重要。


但是攻击者还可以伪装成 Client 来完成认证,而 Server 发现不了,就没那么好理解了。这是因为攻击者已经知道了 v=g^x 的值,可以在不知道密码的情况下就计算出与 Server 端一样的 S 和 K,进而计算出 M1,具体过程如下(参考):

该漏洞本质上是构造出一个特殊的 A 值,使得 S 的计算过程退化为 B^a,而 a 是攻击者自己生成的,B 是 Server 发送给攻击者的,所以攻击者可以计算出与 Server 一样的 S 值。

SRP-2

协议介绍

上面分析过,攻击者拖库后,之所以能伪装为 Client 骗过 Server,是因为攻击者找到了一个特殊的 A 值,可以将计算 S 的算法退化为 B^a。既然如此,那就修改计算 S 的算法,不直接使用 A 值,而是使用 A^2 来计算,修改后协议就是 SRP-2


协议过程如下图所示:

SRP-3

协议介绍

为了解决 SRP-1 的漏洞 2,在 SRP-2 的基础上再掺入另一个随机扰乱参数(Random scrambling parameter) --- u,以减少被拖库后的危害。新的协议就是本节要介绍的 SRP-3


但 SRP-3 只能防止在 g^x 被泄露后,攻击者冒充 Client,不能防止攻击者冒充 Server。

不足之处

  1. 遭受 "two-for-one" 密码猜测攻击

  2. Server 在向 Client 发送消息前,必须先收到 Client 的消息


SRP-6a 已经解决了这两个问题。

SRP-6a

SRP-6, which fixes "two-for-one" guessing and messaging ordering attacks, was published in 2002。该版本的协议可以确保每次登录只能猜测一个密码,有效提高了攻击难度。


srp-6a 被应用于 SSL/TLS(TLS-SRP) 及其它标准(如 EAP 和 SAML)的强密码身份认证中,同时也是 IEEE 1363.2 和 ISO/IEC 11770-4 的一部分。

注册(Registration)

步骤如下:

// 一:客户端 和 服务端先协商一个 N 和 g,即 Group。后续的所有运算都是在运算空间 N 上的模运算。
// 二:用户输入 userName 和 Passwrod,分别记为 I 和 p(注意 Passwrod 不能通过网络传输给任意其它方)
// 三:客户端生成一个随机数,即 salt,记为 s(该值需要传给server永久存储)
// 四:计算 x := H( s | H(I | ":" | p) ),其中 | 表示串联操作
// 五:计算乘数因数(Multiplier Parameter) k := H( N | PAD(g) )
// 六:计算 Verifier v := g^x
// 七:client 将 I、s、v、Group 发给服务器,服务器将它们安全地存储到DB
复制代码


Group 的选择

如果攻击者可以计算运算空间 N 上的离散对数,就会对用户密码造成威胁,也会威胁到 session 会话的私密性和完整性。client 必须确保它收到的 N 值足够大,以使离散对数计算在计算量上不可行。

攻击者也可以向 client 发送一个足够确保安全的大素数 N,但其可能具有能让攻击者更容易计算的特殊格式。如果 client 使用该 N 值执行后面的计算,就会对密码安全造成威胁。由于很难实时地验证某个大素数是否足够安全,client 应该只接受 RFC 5054 附录 A 中的 Group,或者只接受提前在本地配置好的、可信任的主体发送的 N。


细节

  1. N 必须是一个大素数,且 N=2q+1, q 也为素数。g 则为一个小素数,比如 2 或者 5。RFC 5054 的附录 A 中已经列举了一些足够安全的 N 和对应的 g 值。client 和 server 从这里选就可以了,N 的值越大,越安全,但相应的计算也更耗时,需要在安全性和用户体验上做权衡取舍。如果 server 在 Server Hello 步骤返回的 Group 不在附录 A 中,但只要 client 认为该 Group 足够安全,也可以接受它。如果认为 Group 不够安全而拒绝,就返回 "insufficient_security" 以终止流程。

认证(Authentication)

流程图:


详细步骤如下:

// 一:客户端使用 userName 到服务端查询 Group(里面包含 N 和 g)以及 Salt s 和 B(计算过程在后面)//    有了这些参数,以及用户输入的 userName I 和 password p,//    客户端就可以计算出 x := H(s | H(I | ":" | p)), //    进而再计算出 v := g^x
// 二:客户端&&服务端分别计算出:// 乘数因数(Multiplier Parameter) k := H( N | PAD(g) )
// 三:客户端生成一个长度最少为 256 bits 的随机数 a,计算 A := g^a// a的长度必须至少是 256 bits,才能达到近似于 128 bits 离散对数的复杂度
// 四:服务端生成一个长度最少为 256 bits 的随机数 b, 计算 B := kv + g^b// 注:B在第一步中就已经返回给 client 了
// 五:客户端&&服务端相互交换 A 和 B (注意:a 和 b 不能传给对方),然后分别计算出:// 随机扰乱因子(Random scrambling parameter) u := H( PAD(A) | PAD(B) )
// 六:客户端&&服务端分别计算出一个对称密钥 K:// 服务端: K := H( (Av^u) ^ b )// 客户端: K := H( (B - kg^x) ^ (a + ux) )
// 客户端 和 服务端分别计算出的对称密钥 K 是相等的,证明过程如下:// 要证明 K_c == K_s,只需要证明 (B - kg^x) ^ (a + ux) == (Av^u) ^ b 即可: (B - kg^x) ^ (a + ux)= (B - kv) ^ (a + ux) // ∵ v = g^x= (g^b) ^ (a + ux) // ∵ B = kv + g^b= (g ^ (a+ux)) ^ b // 幂运算的性质= (g^a * g^(ux)) ^ b // 幂运算的性质= (A * (g^x)^u ) ^ b // ∵ A = g^a= (A * v^u) ^ b // ∵ v = g^x// 证明完成
复制代码

至此,客户端 和 服务端分别都计算出了一个 session KEY。要完成认证过程,他们必须向对方证明这两个 Key 是相等的。


细节

Client Hello:

username 不存在的情况

client 向 server 发送 username,以查询对应的 Salt 和 Group 等参数时,不能把密码相关的信息(也就是 x)传给 server。如果 server 端发现 username 不存在,可以立即返回 "unknown_psk_identity" 终止流程。如果 server 想隐藏 username 不存在的事实,也可以选择稍后在第六步中返回 "bad_record_mac",以模拟密码不正确情况。

如果 server 要模拟密码不正确的情况,需要保证对于每个 userName,每次都要返回相同的 Salt s 和 Group。server 可以存储一个密钥 "seed_key", 然后每次使用 H(seed_key, "salt" | user_name) 来生成 Salt。对于 B,server 可以返回 [1, N-1] 中的任意一个随机值。server 还要注意模拟处理延迟


Server Hello

如果 client 收到的 B % N == 0,就要立即返回 "illegal_parameter" 以终止流程。

server 需要返回加密套件对应的证书

根据所选择的加密套件,有些加密套件(以 TLS_SRP_SHA_RSA 或 TLS_SRP_SHA_DSS 开头的)必须返回相应的证书,其中包含 server 的公钥,并用对应的私钥签名该消息。详见 RFC 5054 第 2.7 小节


server 除了必须实现 TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA 加密套件,还应当实现 TLS_SRP_SHA_WITH_AES_128_CBC_SHA 和 TLS_SRP_SHA_WITH_AES_256_CBC_SHA 套件。其它的套件,也可以实现。


Client Key Exchange

client 先向 server 发送 A,server 收到后,如果判断 A % N == 0,就必须以 "illegal_parameter" 错误终止流程。

否则,client 就接着向 server 发送 M,server 验证通过后,才能向 client 发送


验证(Verification)

// 一:客户端计算: M := H( H(N) xor H(g), H(I), s, A, B, K )//    并将 M 发送给服务端
// 二:服务端用同样的方法计算出 M,并跟客户端传过来的 M 比对,// 如果一致,服务端再计算出 H(A | M | K)// 并将结果传给客户端
// 三:客户端收到后,用同样的方法计算出 H(A | M | K),并跟服务端传过来的值比对
复制代码

因为攻击者没有 K,所以无法通过客户端和服务端的验证。

注意:客户端必须先向服务端证明它拥有 K,如果服务器验证失败,就要立马以 "bad_record_mac" 终止流程。

如果 client 从 server 收到了 "bad_record_mac",它需要提示用户 username 或者 password 不正确。

另外,在 Server 完成验证 Client 的 M1 之前,Server 不能向 Client 传输任何用 Session K 加密的数据,否则攻击者可以依此暴力破解,参考这里

协议特性

如果攻击者知道了用户的 SRP verifier(比如通过某种方式窃取了 server 的 DB 文件),那么就可以伪装成真实的 server,也可以进行字典攻击破解用户密码。

攻击者也可以通过不断尝试来猜测用户密码,server 应该采取措施以避免其发生。比如限制每个 IP 或者 userName 的认证频率。

可能有多对 (userName, password) 匹配到同一个 SRP verifier,因为使用 SRP verifier 一定要小心,不要假设只会有一对 (userName, password) 与其匹配。

Hash 函数注意事项

RFC 5054 文档中使用 SHA-1 来计算几个值:

  • u: 阻止攻击者知道 SRP verifer 后伪装成真实用户去完成认证。

  • k: 阻止可以选择 Group 参数的攻击者发起 2-for-1 猜测攻击

  • x: 包含被 salt 混淆过的用户密码


对 SHA-1 进行密码学分析攻击只会影响它的碰撞抵抗,而不影响它的使用。如果发现针对 SHA-1 的攻击影响了其使用,应当选择使用了别的 Hash 算法的新的加密套件。详见 RFC 5054 第 3.4 节


其它可选的 Hash 算法在这里,更大的 hash 算法会生成更大的 session key。


Client 注意事项

  • Client 绝不能通过网络将 password 以及 x 传输给任何其它方(包括 Server),也不能在日志中打印它们。只能将它们保存在内存中,且使用完后立马销毁;

  • Client 绝不能通过网络将 s, v 传输给除 Server 之外的任何第三方,也不能在日志中打印它们。只能将它们保存在内存中,且使用完后立马销毁;

开源实现

SRP 理解起来有些成本,稍有不甚理解有误的话,开发出的系统就会有安全漏洞。幸运的是,已经有很多开源的实现了:

  1. JavaScript: https://github.com/mozilla/node-srp

  2. Go: https://github.com/1Password/srp

  3. JavaScript: https://github.com/simbo1905/thinbus-srp-npm

参考文章


发布于: 2024-02-01阅读数: 107
用户头像

Geek_44385e

关注

还未添加个人签名 2018-11-09 加入

还未添加个人简介

评论

发布
暂无评论
SRP (Secure Remote Password Protocol)_srp_Geek_44385e_InfoQ写作社区