什么是安全远程密码 (SRP) 以及如何使用它来保护用户的密码

介绍

安全远程密码 (SRP) 协议是一种安全身份验证方法,旨在安全地验证用户的凭据,而无需通过网络传输密码。

与传统的密码哈希不同,传统的密码哈希依赖于将密码发送到服务器,然后与存储的哈希进行比较,SRP 永远不会在服务器上传输或存储用户的密码,从而大大降低了拦截或服务器泄露的风险。好处是它可以阻止不同类型的攻击,如 MITM(中间人)攻击,如果用户数据泄露,则不需要更改密码,并让用户和服务器相互确认身份。

Apple 和 1Password 等大公司已将 SRP 作为其身份验证机制的一部分。例如,苹果公司在其iCloud钥匙串中实施了SRP,以安全地跨设备同步密码,而不会暴露密码。同样,1Password 使用用户的主密码和密钥来加密数据,密码和密钥都使用 SRP 来验证帐户并确保它们不会传输到服务器。

关于本文

本文旨在解释 SRP-6a 的每个步骤,并综合概述 RFC-5054 和 RFC-2945。它还映射了本文后面的变量简介和示例代码中的每个计算公式。这对于刚接触 SRP 并希望了解它在 RFC 中如何实现的开发人员特别有用,确保他们可以遵循提到的每个计算步骤。

并非所有开源软件包都完全遵循 RFC 标准,这意味着有些软件包可能无法与其他软件包很好地配合使用。本文严格遵循 SRP-6a,以 windwalker/srp 中的实现为模型。通过遵循此示例,您的实现应该可以轻松与完全符合 RFC 的其他包一起使用。

如果您有兴趣查找更多完全实现 SRP-6a 的软件包,请参阅此 SRP 实现列表

SRP 流程

SRP-6a 的定义和流程分散在 RFC 2945 和 RFC 5054 中;这是将它们集成起来进行概述的尝试。请严格遵守 RFC 指定的程序,不要进行自定义修改,并且不要不必要地传输任何变量,以避免安全漏洞。

定义

变量名字发送计算
I,identity主要标识(用户名或电子邮件)。C=>S
N一个大的安全素数,所有的算术都是模 N 完成的。X
g生成器模 NX
k乘数参数XSHA1(N ‖ PAD(g))
s用户 salt。C<=Srandom()
v密码验证程序Xg^x % N
x盐 + 身份 + 密码的哈希值。XSHA1(s ‖ SHA1(I ‖ ":" ‖ P))
a,b客户端和服务器密钥Xrandom()
A客户端公钥C=>Sg^a % N
B服务器公钥C<=Sk*v + g^b % N
u防止获知用户验证程序的攻击者的价值XH(PAD(A) ‖ PAD(B))
S(客户)预主密钥(安全通用会话密钥)X(B - (k * g^x)) ^ (a + (u * x)) % N
S(服务器)预主密钥(安全通用会话密钥)X(A * v^u) ^ b % N
K用于生成 M 的会话密钥哈希XH(S)
M1证据消息 1,验证双方生成了相同的会话密钥。C=>SH(H(N) XOR H(g) ‖ H(U) ‖ s ‖ A ‖ B ‖ K)
M2证据消息 2,验证双方生成了相同的会话密钥。C<=SH(A ‖ M ‖ K)

注册

当应用程序(Web/移动设备)启动注册流程时,它可能会向用户显示 ()(用户名或电子邮件)和 () 字段。他们输入用户名和密码,然后单击注册按钮。SRP 客户端将生成一个随机 () 和一个密码 (),该密码由 salt、identity 和 password 生成。identityIpasswordPsaltsverifierv

然后应用程序将只发送 ,和 发送到服务器,而不发送 。如果原始密码被意外传输到服务器,即使它被服务器忽略,这也是协议冲突和安全错误。saltverifieridentitypassword

当服务器收到注册请求时,可以将用户信息和 保存到 DB。如果要在保存之前加密 salt 和验证程序,则这是可选的,请确保使用只有服务器知道的密钥对其进行加密。saltverifier

登录

Hello 和服务器 step1

当用户开始登录过程时,他们可以在表单字段中输入其身份和密码,然后单击登录按钮。SRP 客户端将向服务器发送 Hello 请求。服务器应通过此标识检查用户是否存在,并从用户数据中获取和获取。接下来,服务器将生成一个随机的 private 和一个 public ,并通过数据库、会话或缓存存储记住它们,我们将在后续步骤中需要它们,然后,将 返回给客户端(Server Hello)。此过程类似于握手,为双方创建连接会话。identitysaltverifierbBsaltB

某些包将客户端 Hello 作为操作调用,并且是服务器质询值。challengeB

客户端步骤 1 和 2

收到 and 后,客户端运行步骤 1 生成 and,然后运行步骤 2 使用上述所有值生成客户端证明。它将通过(身份验证操作)发送到服务器。服务器端也使用所有生成的值来生成并进行比较。如果比较失败,服务器将报告错误,如果比较成功,服务器将生成服务器证明并返回给客户端。在此步骤中,身份验证操作已完成,您只需将用户重定向到登录成功页面即可。BsaltaAM1AM1M2

有一个可选的客户端步骤 3 是您可以验证授权服务器是否受信任,并确保双方生成相同的会话密钥 ()。如果您已完成此步骤3,则表示您已完成身份验证握手并执行了双向身份验证。如果要运行 step3 以完成所有进程,可以在 step3 完成后重定向用户。M2S

关于和SM

当客户端和服务器生成时,它们都会生成一个预主密钥()。即使双方没有发送给另一方,也应该是相同的。和是一个验证器,用于确保双方具有相同的 .因此,如果您将来想执行其他加密行为,则可以是受信任的会话密钥或加密密钥。MSSSM1M2SS

示例代码

下面是一个伪代码,用于显示 SRP 服务器端和客户端交叉的工作原理。

const server = SRPServer.create();
const client = SRPClient.create();

// Register
const identity = '...';
const password = '...';

// Register: generate new salt & verifier

// random()
const salt = client.generateSalt();
// (SHA(s | SHA(I | `:` | P)))
const x = client.generateX(salt, identity, password);
// (g^x % N)
const verifier = client.generateVerifier(x);


// Send salt and verifier to Server store


// Login start
// AJAX:hello?{identity} - Server step (1)
// salt & verifier has already stored on user data, server can get it from DB
// b & B must remember on session, we will use it at following steps.

// random()
const b = server.generateRandomSecret();
// ((k*v + g^b) % N)
const B = server.generateB(b, verifier);


// Server returns B & salt to client


// Client step (1)

// random()
const a = client.generateRandomSecret();
// (g^a % N)
const A = client.generateA(a);
// (SHA(s | SHA(I | `:` | P)))
const x = client.generateX(salt, identity, password);


// Client step (2)

// H(PAD(A) | PAD(B))
const u = client.generateU(A, B);
// ((B - (k * g^x)) ^ (a + (u * x)) % N)
const S = client.generateS(a, B, x, u);
// H(S)
const K = client.hash(S);
// H(H(N) xor H(g), H(I), s, A, B, K)
const M1Client = client.generateM1(identity, salt, A, B, K);


// AJAX:authenticate?{identity,A,M1} - Server step (2)
// Send identity & A & M1 to server and compare it.
// The salt & verifier stored on user data, get it from DB.
// The b, B stored in session state, get and clear them.

// H(PAD(A) | PAD(B))
const u = server.generateU(A, B);
// ((A * v^u) ^ b % N)
const S = server.generateS(A, b, verifier, u);
// H(S)
const K = server.hash(S);
// H(H(N) xor H(g), H(I), s, A, B, K)
const M1Server = server.generateM1(identity, salt, A, B, K);

// Do compare
if (!crypto.timeingSafeEquals(M1Client, M1Server)) {
  throw new Error('Invalid client session proof.');
}

// Now create a M2 as server proof
// H(A | M | K)
const M2Server = server.generateM2(A, M1Server, K);

// Server returns M2 to Client
// Client step (3) (optional)

// H(A | M | K)
const M2Client = client.generateM2(A, M1Client, K);


// Do compare
if (!crypto.timeingSafeEquals(M2Client, M2Server)) {
  throw new Error('Invalid server session proof.');
}

// If all passed, should not throw any exceptions.

一些重要提示

  • 无需使用 AJAX 来实现 SRP 流。您可以简单地使用表单发布来完成所有步骤。例如,您可以在网站上将用户名和密码分成 2 个步骤,并将值存储在隐藏的输入中。确保您在浏览器和服务器缓存中存储并可以使用它们,并且不会意外地将它们发送到远程端。ab
  • 是从身份和密码生成的,这意味着如果用户更改了 或 。verifieridentitypassword
  • 始终确保不要向每一端发送任何不必要的值,即使服务器或客户端忽略它们,也会被视为协议违规和安全错误。此外,MITM 攻击者可以使用这些敏感数据。
  • 重新启动身份验证进程时,请始终清除值。通常,您可以重新加载页面,以便重置所有值和 JS 对象。如果要开发 SPA 应用,请将整个过程包装在函数中,并且不要将值缓存到对象属性中,如果使用的是 SRP 库,请始终在用户重试时重新创建库对象。
  • SRP 不应取代 HTTPS,应始终在应用上使用 SSL/TLS,并启用 Cookie HttpOnly 和安全设置。