jiripudil / otp
生成和验证一次性密码的库。
Requires
- php: ^8.1
- paragonie/constant_time_encoding: ^2.0
Requires (Dev)
- mockery/mockery: ^1.5
- nette/tester: ^2.4
- phpstan/phpstan: ^1.6
README
OTP是一个库,用于生成和验证符合HOTP(RFC 4226)和TOTP(RFC 6238)算法的一次性密码。这些一次性密码通常在用户认证过程中作为第二因素使用。简而言之,您的应用程序和用户的OTP应用程序都能根据共享密钥生成一个数字。每次需要验证用户时,都要求他们输入由其应用程序生成的代码,并使用此库进行验证。
安装和需求
$ composer require jiripudil/otp
OTP需要PHP >= 8.1。
用法
此库的入口点是JiriPudil\OTP\OTP
类
$otp = new JiriPudil\OTP\OTP('My Application', $otpType);
它期望您提供以下内容:
- 发行者名称
$issuer
。这通常由最终用户应用程序用于区分各种服务的OTP,因此应清楚地识别您的应用程序。 - OTP类型
$otp
。此库提供了HOTP和TOTP的实现。以下为详细信息。 - 哈希算法
$algorithm
。这是可选的,默认为SHA-1。⚠请注意,某些最终用户实现(如Google Authenticator)仅支持此算法。
基于时间的OTP
JiriPudil\OTP\TimeBasedOtp
类实现了RFC 6238中的TOTP,可能是最常用的一种OTP类型。它在固定的30秒间隔内生成代码。⚠虽然间隔可以更改,但某些最终用户实现不支持默认以外的间隔。
使用基于时间的OTP需要您的服务器和用户的应用程序中的时钟同步。为了弥补时间之间的可能差异,您可以提供一个可选的$tolerance
,它确定在当前时间之前和之后应该考虑多少个时间段为有效。默认为1,这意味着上一个和下一个周期的代码将通过验证。
$otpType = new JiriPudil\OTP\TimeBasedOTP();
基于HMAC的OTP
JiriPudil\OTP\HmacBasedOTP
类实现了RFC 4226中的HOTP。这种OTP类型不依赖于时间,而是依赖于用户的应用程序和服务器都保留的计数器。因此,该类需要您提供一个JiriPudil\OTP\HmacBasedOTP\CounterRepository
的实现,该实现检索和更新给定$account
的计数器。您的实现应在某些持久存储(如关系数据库)上操作。
用户的计数器每次请求新代码时都会增加,而服务器的计数器仅在验证尝试成功后才会增加。为了解决计数器值可能的去同步,您可以配置一个$lookAhead
参数,告诉此库检查几个后续的计数器值。此参数默认为3。
$otpType = new JiriPudil\OTP\HmacBasedOTP($myCounterRepository);
设置双因素认证
首先,您需要生成一个随机密钥。OTP
类提供了一个方法来确保密钥足够长,以符合配置的哈希算法
$secret = $otp->generateSecret();
密钥必须是用户的唯一密钥,因此应该存储在其他用户数据的地方,最好加密,因为它是一个非常敏感的值
$myUserRepository->encryptAndSaveOtpSecret($myUser, $secret);
与用户相关联的第二个值是账户名称:这应该是一个唯一标识应用程序中用户的值,例如他们的电子邮件地址。这两条信息以 JiriPudil\OTP\Account\AccountDescriptor
的形式暴露给此 OTP 库。在 JiriPudil\OTP\Account\SimpleAccountDescriptor
中有一个方便的简单实现,对于大多数情况应该足够使用。
$account = new SimpleAccountDescriptor($myUser->getEmailAddress(), $myUser->getOtpSecret());
然后,您可以调用 getProvisioningUri
来获取用于设置最终用户 OTP 应用程序的 URI。您可以可选地指定生成的代码应该有多少位数字 - 这可以是一个介于 6 和 8 之间的值,默认值为 6,这是最常用的值。
$uri = $otp->getProvisioningUri($account, digits: 6);
此 URI 通常以二维码的形式显示,用户应用程序可以扫描。或者,您可以直接显示 Base32 编码的秘密,以便用户输入或复制到他们的应用程序中。
$encodedSecret = $account->getSecret()->asBase32();
验证
一旦用户在其 OTP 应用程序中设置了账户,您可以要求他们输入代码,并轻松验证设置是否正确。
if ($otp->verify($account, $enteredCode, expectedDigits: 6)) { // successfully verified } else { // incorrect code }
在此阶段,您应生成并显示一组恢复代码,以防用户无法访问其 OTP 应用程序。之后,您可以认为用户在您的应用程序中的 OTP 设置已完成,并可以开始在他们认证期间要求代码作为第二因素。
客户端使用
此包也可以用作 OTP 客户端。
$code = $otp->generate($account, digits: 6);