根据RFC 6238和RFC 4226实现的一次性密码(OTP),用于两因素认证中的TOTP

v1.0.1 2023-07-03 08:13 UTC

This package is auto-updated.

Last update: 2024-09-02 12:16:09 UTC


README

根据RFC 6238和RFC 4226实现的一次性密码(OTP),用于两因素认证中的TOTP

需求

  • PHP 5.6.0+
    • OpenSSL扩展(openssl

安装

  1. 通过Composer包含库[?]

    $ composer require delight-im/otp
  2. 包含Composer自动加载器

    require __DIR__ . '/vendor/autoload.php';

用法

创建新的密钥、共享密钥或种子

\Delight\Otp\Otp::createSecret();
// string(32) "WQ2S54TEQYY4Z2PWHB2Y6W443ZCEKJCQ"


// or


\Delight\Otp\Otp::createSecret(\Delight\Otp\Otp::SHARED_SECRET_STRENGTH_HIGH);
// string(32) "X4YSNUYTL6NE2PF7PMFN4QDUD3WPUR75" (160 bits or 20 bytes)

// or

\Delight\Otp\Otp::createSecret(\Delight\Otp\Otp::SHARED_SECRET_STRENGTH_MODERATE);
// string(26) "7OX7TNZAKKXFAWH2P4RB4VR2DE" (128 bits or 16 bytes)

// or

\Delight\Otp\Otp::createSecret(\Delight\Otp\Otp::SHARED_SECRET_STRENGTH_LOW);
// string(16) "3KXNADOF5GTCCTKZ" (80 bits or 10 bytes)

创建新的密钥后,您需要将字符串(存储在您的数据库中)与为其生成的用户关联起来。密钥必须对每个用户都是唯一的,即新生成的密钥不应在用户之间重复使用。接下来,必须将密钥向用户展示一次,以将其传输到将用于生成一次性密码的客户端应用程序,即他们的身份验证器应用程序。始终使用安全通道,例如HTTPS或TLS,在服务器和客户端之间共享密钥。

向用户展示密钥以设置身份验证器应用程序

使用存储的密钥以及您服务或应用程序的名称和用户的账户名称,让用户在客户端设置身份验证器应用程序

\Delight\Otp\Otp::createTotpKeyUriForQrCode('app.example.com', 'john.doe@example.org', $storedSecret);
// string(116) "otpauth://totp/app.example.com:john.doe%40example.org?secret=T7...4D&issuer=app.example.com"

现在,您可以将密钥URI编码为QR码,最好在客户端进行,并要求用户使用他们的身份验证器应用程序扫描它。

此外,您还应允许用户在设置期间查看一次存储的密钥(以Base32编码的字符串形式),并允许他们将其手动输入到他们的身份验证器应用程序中,以防他们无法使用身份验证器应用程序扫描QR码。

从服务器端到客户端端的种子传输必须只发生一次,即在设置期间。

现在,服务器和客户端有一个共享密钥,可以使用该密钥安全地生成一次性密码。

但在完成两因素认证的设置并启用用户使用一次性密码之前,您应要求成功验证一个初始一次性密码,如下所示,以确保用户正确完成了设置。

如果您想要为用户的一次性密码进行任何自定义配置,例如一次性密码的长度不同(例如,8个字符而不是6个)或非标准间隔后刷新一次性密码,则在设置的这个阶段设置这些配置。自定义配置可以通过在密钥URI中提供额外的参数提供给用户的身份验证器应用程序,但遗憾的是,并非所有身份验证器应用程序都支持这些。因此,请确保服务器期望的配置,以及存储给用户的配置,与用户客户端身份验证器应用程序将要使用的配置一致。

重要:您应该生成并存储一些至少包含8个字符的随机代码,例如使用PHP-Random,作为备份代码。在设置期间一次向用户展示这些。

验证一次性密码

只需提供用户输入的一次性密码,以及设置过程中存储的共享密钥,以验证用户输入的一次性密码(他们从认证应用中获取的)是否与服务器端存储的密钥有效。

\Delight\Otp\Otp::verifyTotp($storedSecret, '390108');
// bool(true)

为了解决轻微的时钟偏差、网络延迟和用户输入延迟,接受一些较旧和较新的一次性密码,以实现安全和可用性的良好平衡。

重要:您必须通过在服务器端进行节流来防止暴力攻击。用户在一定时间内不允许尝试多次输入一次性密码。

重要:一旦一次性密码被验证为正确,您需要使用拒绝列表来防止重放攻击,例如,通过跟踪数据库中每个用户成功使用的的一次性密码,并防止它们再次使用。拒绝列表中跟踪的一次性密码应在几分钟后过期。当您为一次性密码使用自定义有效期或刷新间隔,或者允许接受更多过去或未来的一次性密码时,必须调整此过期时间。

如果您在早期设置期间向用户的客户端认证应用提供了任何自定义配置,则现在在验证从用户收到的一次性密码时必须使用相同的配置。

$lookBehindSteps = 3;
$lookAheadSteps = 3;
$currentTime = \time();
$otpLength = 8;
$refreshInterval = 60;
$epoch = 0;
$hashFunction = \Delight\Otp\Otp::HASH_FUNCTION_SHA_512;

\Delight\Otp\Otp::verifyTotp(
    $storedSecret,
    '38618901',
    $lookBehindSteps,
    $lookAheadSteps,
    $currentTime,
    $otpLength,
    $refreshInterval,
    $epoch,
    $hashFunction
);
// bool(true)

贡献

所有贡献都受欢迎!如果您希望贡献,请首先创建一个问题,以便您的功能、问题或问题可以讨论。

许可证

本项目根据MIT许可证的条款许可。