windwalker / srp
SRP-6a (RFC5054) 的现代 PHP/JS 包。包含服务器和客户端部分,以帮助开发者在任何情况下使用。
Requires
- php: >=8.1
- brick/math: ^0.11.0
Requires (Dev)
- phpunit/phpunit: ^10.0
- windwalker/utilities: ^4.0
README
SRP 包(PHP)
这是一个现代 PHP/JS 包,提供了 SRP-6a(《RFC5054》)的实现。PHP 和 JS 方面都包含服务器和客户端部分,以帮助开发者适应各种情况。
此包通过了 srptools《测试向量》,这意味着此包完全实现了 RFC5054 规范,您可以使用此包与其他完全遵守 RFC 规范的任何其他包一起工作。主要区别在于,在哈希之前,此包将填充值以适应 g
(素数)值的长度,然而,大多数 SRP 包在哈希之前可能不会填充这些值。
如果您想使用此包与另一个在哈希之前不填充值的包一起使用,我们还提供了一种禁用填充行为的方法。
安装
PHP
composer require windwalker/srp
JS
npm i @windwalker-io/srp --save
# OR
yarn add @windwalker-io/srp
有关 JS 包的文档,请参见此处。
入门指南
您必须准备一个大安全素数、一个生成器和密钥,素数和生成器以 10 为基数,密钥以十六进制(基16)格式表示。此包还提供了默认的安全素数,您可以直接使用它。
use Windwalker\SRP\SRPServer; use Windwalker\SRP\SRPClient; $server = new SRPServer( SRPServer::DEFAULT_PRIME, // 217661744586174357731910088918027537819... SRPServer::DEFAULT_GENERATOR, // 02 SRPServer::DEFAULT_KEY, // 5b9e8ef059c6b32ea59fc1d322d37f04aa30bae5aa9003b8321e21ddb04e300 );
这些值可以是 BigInteger 格式,我们使用 brick/math 作为 BigInteger 库。
use Brick\Math\BigInteger; use Windwalker\SRP\SRPServer; use Windwalker\SRP\SRPClient; $server = new SRPServer( BigInteger::of(SRPServer::DEFAULT_PRIME), BigInteger::of(SRPServer::DEFAULT_GENERATOR), BigInteger::fromBase(SRPServer::DEFAULT_KEY, 16), );
如果将配置设置为数组,请使用 createFromConfig()
。
use Windwalker\SRP\SRPServer; $config = [ 'prime' => SRPServer::DEFAULT_PRIME, // 217661744586174357731910088918027537819... 'generator' => SRPServer::DEFAULT_GENERATOR, // 02 'key' => SRPServer::DEFAULT_KEY, // 5b9e8ef059c6b32ea59fc1d322d37f04aa30bae5aa9003b8321e21ddb04e300 ];
使用 create()
忽略所有参数,此包将使用准备好的默认安全配置。
use Windwalker\SRP\SRPServer; use Windwalker\SRP\SRPClient; $server = SRPServer::create(); $client = SRPClient::create();
还有一些其他配置选项
use Windwalker\SRP\SRPServer; // Set the secret size $server->setSize(512); // Default is 256 // Same as $server->setLength(64); // Set Hash algo, default is `sha256` $server->setHaser('sha1'); $server->setHaser('sha256'); $server->setHaser('sha384'); $server->setHaser('sha512'); // Blake2b will use sodium ext to hash it. $server->setHaser('blake2b-256'); $server->setHaser('blake2b-224'); $server->setHaser('blake2b-384'); $server->setHaser('blake2b-512'); // Set custom hash logic $server->setHaser(fn(string $str) => ...); // Disable padding $server->enablePad(false);
示例代码
在这里,我们使用 PHP 服务器和客户端运行示例 SRP 流程。您可以替换客户端部分为 JS。由 SRP 包生成的所有值都是 BigInteger
对象,您可以通过 $v->toBase(16)
将其转换为十六进制,以便存储到数据库或通过 HTTP 请求传输。
该流程的完整描述见下一章。
use Windwalker\SRP\SRPServer; use Windwalker\SRP\SRPClient; $server = SRPServer::create(); $client = SRPClient::create(); // Register page: User input identify and password. $identity = '...'; $password = '...'; // Register: generate new salt & verifier $pf = $client->register($identity, $password); $salt = $pf->salt; // BigInteger object $verifier = $pf->verifier; // BigInteger object // Use toBase(16) convert to hex string $salt->toBase(16); $verifier->toBase(16); // Send 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. $r = $server->step1($identity, $salt, $verifier); $b = $r->secret; // BigInteger object $B = $r->public; // BigInteger object // Server hello: returns B & salt to client // Client step (1) & (2) $pr = $client->step1($identity, $password, $salt); $a = $pr->secret; $A = $pr->public; $x = $pr->hash; $pr = $client->step2($identity, $salt, $A, $a, $B, $x); $K = $pr->key; $M1 = $pr->proof; // 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. $pr = $server->step2($identity, $salt, $verifier, $A, $B, $b, $M1); $M2 = $pr->proof; // Server returns M2 to Client // Client step (3) (optional) $client->step3($A, $K, $M1, $M2); // If all passed, should not throw any exceptions.
SRP 流程
SRP-6a 的定义和过程分散在《RFC 2945》和《RFC 5054》中;这是一个尝试将它们整合为概述的尝试。请严格按照 RFC 规定的程序进行操作,不要进行自定义修改,并且不要不必要地传输任何变量,以避免安全漏洞。
定义
注册
当应用程序(Web/移动)启动注册流程时,它可能向用户显示 identity
(《I》)(用户名或电子邮件)和 password
(《P》)字段。用户输入他们的用户名和密码,然后点击注册按钮。SRP 客户端将生成一个随机的 salt
(《s》)和一个从盐、身份和密码生成的密码 verifier
(《v》)。
应用程序将只发送 盐
、验证器
和 身份
到服务器,不会发送 密码
。即使服务器忽略它,如果原始密码意外地传输到服务器,即使这是一种协议违规和安全漏洞。
当服务器收到注册请求时,您可以保存用户信息和 盐
、验证器
到数据库。是否在保存之前加密盐和验证器是可选的,确保您使用只有服务器知道的密钥来加密它们。
登录
你好,服务器步骤1
当用户开始登录过程时,他们可能会在表单字段中输入他们的身份和密码,然后点击登录按钮。SRP客户端将带有 身份
的 Hello 请求发送到服务器。服务器应该通过此身份检查用户是否存在,并从用户数据中获取 盐
和 验证器
。接下来,服务器将生成一个随机的私有 b
和一个公钥 B
,并通过数据库、会话或缓存存储记住它们,以便在后续步骤中使用,然后,将 盐
和 B
返回给客户端(服务器 Hello)。这个过程类似于握手,为双方创建一个连接会话。
一些包将客户端 Hello 称为
挑战
动作,而B
是服务器挑战值。
客户端步骤1 & 2
在接收到 B
和 盐
后,客户端执行步骤1以生成 a
和 A
,然后执行步骤2,使用所有上述值生成客户端证明 M1
。它将与 A
一起发送到服务器(认证动作)。服务器端也使用所有生成的值生成 M1
并进行比较。如果比较失败,服务器将报告错误;如果比较成功,服务器将生成服务器证明 M2
并发送回客户端。到这一步,认证动作完成,您可以简单地将用户重定向到登录成功页面。
有一个可选的客户端步骤3,您可以验证 M2
以确保权威服务器是可信的,并确保双方生成相同的会话密钥(S
)。如果您完成此步骤3,则意味着您完成了认证握手并执行了双向认证。如果要在步骤3完成后重定向用户,请运行步骤3以完成所有过程。
关于 S
和 M
当客户端和服务器生成 M
时,它们将同时生成一个预主密钥(S
)。S
应该相同,即使双方都没有将 S
发送到另一方。M1
和 M2
是验证器,以确保双方有相同的 S
。因此,S
可以是一个受信任的会话密钥或加密密钥,如果您想在将来执行其他加密行为。
一些重要提示
- 您不需要使用 AJAX 来实现 SRP 流。您可以直接使用表单提交来完成所有步骤。例如,您可能将用户名和密码分开为两个步骤,并在网站上存储值在隐藏的输入中。确保您在浏览器和服务器缓存中存储
a
和b
,以便在步骤之间使用,并且不要意外地将它们发送到远程端。 验证器
是从身份和密码生成的,这意味着如果您更改身份
或密码
中的任何一个,都必须重新创建一个新的验证器来替换旧的一个。- 始终确保您不会向每一方发送任何不必要的数据,即使服务器或客户端忽略它们,这也被视为协议违规和安全漏洞。此外,中间人攻击者 能够使用这些敏感数据。
- 在重启认证过程时,始终清除值。通常,您可以通过重新加载页面来重置所有值和JS对象。如果您正在开发SPA应用程序,请在函数中包装整个过程,并不要将值缓存在对象属性中,每次都创建一个新的客户端JS对象。
- SRP不应取代HTTPS,您应该在您的应用程序上始终使用SSL/TLS,并启用Cookies的HttpOnly和secure设置。
贡献
如果您想报告错误,只需打开一个问题。任何PR都受欢迎,并且可以加快修复过程。
如果您有安全问题,请发送到asika32764@gmail.com
(Simon)。