SRP-6a (RFC5054) 的现代 PHP/JS 包。包含服务器和客户端部分,以帮助开发者在任何情况下使用。

1.0.1 2023-11-19 07:30 UTC

This package is auto-updated.

Last update: 2024-09-10 09:25:39 UTC


README


Windwalker

SRP 包(PHP)

Windwalker SRP 包 PHP | JS

GitHub GitHub Workflow Status Packagist Downloads Packagist Version

这是一个现代 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 规定的程序进行操作,不要进行自定义修改,并且不要不必要地传输任何变量,以避免安全漏洞。

定义

注册

Registration

当应用程序(Web/移动)启动注册流程时,它可能向用户显示 identity(《I》)(用户名或电子邮件)和 password(《P》)字段。用户输入他们的用户名和密码,然后点击注册按钮。SRP 客户端将生成一个随机的 salt(《s》)和一个从盐、身份和密码生成的密码 verifier(《v》)。

应用程序将只发送 验证器身份 到服务器,不会发送 密码。即使服务器忽略它,如果原始密码意外地传输到服务器,即使这是一种协议违规和安全漏洞。

当服务器收到注册请求时,您可以保存用户信息和 验证器 到数据库。是否在保存之前加密盐和验证器是可选的,确保您使用只有服务器知道的密钥来加密它们。

登录

Login

你好,服务器步骤1

当用户开始登录过程时,他们可能会在表单字段中输入他们的身份和密码,然后点击登录按钮。SRP客户端将带有 身份 的 Hello 请求发送到服务器。服务器应该通过此身份检查用户是否存在,并从用户数据中获取 验证器。接下来,服务器将生成一个随机的私有 b 和一个公钥 B,并通过数据库、会话或缓存存储记住它们,以便在后续步骤中使用,然后,将 B 返回给客户端(服务器 Hello)。这个过程类似于握手,为双方创建一个连接会话。

一些包将客户端 Hello 称为 挑战 动作,而 B 是服务器挑战值。

客户端步骤1 & 2

在接收到 B 后,客户端执行步骤1以生成 aA,然后执行步骤2,使用所有上述值生成客户端证明 M1。它将与 A 一起发送到服务器(认证动作)。服务器端也使用所有生成的值生成 M1 并进行比较。如果比较失败,服务器将报告错误;如果比较成功,服务器将生成服务器证明 M2 并发送回客户端。到这一步,认证动作完成,您可以简单地将用户重定向到登录成功页面。

有一个可选的客户端步骤3,您可以验证 M2 以确保权威服务器是可信的,并确保双方生成相同的会话密钥(S)。如果您完成此步骤3,则意味着您完成了认证握手并执行了双向认证。如果要在步骤3完成后重定向用户,请运行步骤3以完成所有过程。

关于 SM

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

一些重要提示

  • 您不需要使用 AJAX 来实现 SRP 流。您可以直接使用表单提交来完成所有步骤。例如,您可能将用户名和密码分开为两个步骤,并在网站上存储值在隐藏的输入中。确保您在浏览器和服务器缓存中存储 ab,以便在步骤之间使用,并且不要意外地将它们发送到远程端。
  • 验证器 是从身份和密码生成的,这意味着如果您更改 身份密码 中的任何一个,都必须重新创建一个新的验证器来替换旧的一个。
  • 始终确保您不会向每一方发送任何不必要的数据,即使服务器或客户端忽略它们,这也被视为协议违规和安全漏洞。此外,中间人攻击者 能够使用这些敏感数据。
  • 在重启认证过程时,始终清除值。通常,您可以通过重新加载页面来重置所有值和JS对象。如果您正在开发SPA应用程序,请在函数中包装整个过程,并不要将值缓存在对象属性中,每次都创建一个新的客户端JS对象。
  • SRP不应取代HTTPS,您应该在您的应用程序上始终使用SSL/TLS,并启用Cookies的HttpOnly和secure设置。

贡献

如果您想报告错误,只需打开一个问题。任何PR都受欢迎,并且可以加快修复过程。

如果您有安全问题,请发送到asika32764@gmail.com(Simon)。