TOTP双因素认证(2FA)php简单实现

TOTP身份验证的工作原理基于时间戳和密钥。用户需要在设置阶段将密钥与相应的身份验证器进行绑定。通常,用户会在需要使用TOTP动态验证码的APP或网站上获得一个密钥,然后将该密钥添加到TOTP验证器工具上。验证器会根据当前的时间戳和密钥生成一个一次性密码(通常是6位数字),用户需要将这个密码输入到需要验证的系统中以完成身份验证。

1、针对某个登录用户生成秘钥

php 复制代码
<?php
//生成32位的秘钥
function generateSecret($length = 32) {
    $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // Base32 字符集
    $secret = '';
    for ($i = 0; $i < $length; $i++) {
        $secret .= $characters[random_int(0, strlen($characters) - 1)];
    }
    return $secret;
}

$secret = generateSecret(); // 用户的唯一 Secret
echo "Secret Key: $secret";

2、生成某个登录用户需要绑定到Authenticator的链接(二维码形式显示)

php 复制代码
<?php
function generateQRCodeUrl($appName, $userEmail, $secret) {
    $issuer = urlencode($appName);
    $email = urlencode($userEmail);
    return "otpauth://totp/{$issuer}:{$email}?secret={$secret}&issuer={$issuer}";
}

$appName = "xxx-test";//在app列表中显示的名称
$userEmail = "xxx.xxx@gmail.com";
$secret='4IRT6WN2RHWZHJ4ZOO2POMYB6W43FQ7G';
$qrCodeUrl = generateQRCodeUrl($appName, $userEmail, $secret);
echo $qrCodeUrl;//当前链接生成为二维码的链接
//echo '<img src="' . $qrCodeUrl . '" alt="QR Code">';

3、登录的时候验证用户输入的code是否正确

php 复制代码
<?php
/**
 * 生成动态验证码(验证用)
 */
function getOtp($secret, $timeSlice = null) {
    if ($timeSlice === null) {
        $timeSlice = floor(time() / 30); // 当前时间戳除以30秒
    }

    $secretKey = base32Decode($secret); // 解码 Base32 密钥
    $time = pack('N*', 0) . pack('N*', $timeSlice); // 将时间戳转换为 64 位二进制

    // 使用 HMAC-SHA1 计算哈希值
    $hmac = hash_hmac('sha1', $time, $secretKey, true);

    // 截取动态偏移量
    $offset = ord(substr($hmac, -1)) & 0x0F;
    $binary = (ord($hmac[$offset]) & 0x7F) << 24 |
        (ord($hmac[$offset + 1]) & 0xFF) << 16 |
        (ord($hmac[$offset + 2]) & 0xFF) << 8 |
        (ord($hmac[$offset + 3]) & 0xFF);

    // 生成6位数字验证码
    return str_pad($binary % pow(10, 6), 6, '0', STR_PAD_LEFT);
}

function base32Decode($base32) {
    $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    $base32 = strtoupper($base32);
    $bits = '';
    foreach (str_split($base32) as $char) {
        $bits .= str_pad(decbin(strpos($characters, $char)), 5, '0', STR_PAD_LEFT);
    }
    $bytes = '';
    foreach (str_split($bits, 8) as $byte) {
        $bytes .= chr(bindec($byte));
    }
    return $bytes;
}

// $otp = getOtp($secret);
// echo "Generated OTP: $otp".PHP_EOL;
//验证方法
function verifyOtp($secret, $userInput, $window = 1) {
    $timeSlice = floor(time() / 30);//TOTP 的核心是基于当前时间的30秒时间片。一个时间片的公式是
    for ($i = -$window; $i <= $window; $i++) {
        echo "OTP: " . getOtp($secret, $timeSlice + $i) . PHP_EOL;
        echo "$i:$window".PHP_EOL;
        if (getOtp($secret, $timeSlice + $i) === $userInput) {
            return true;
        }
    }
    return false;
}
//index.php 生成的secret
$secret='4IRT6WN2RHWZHJ4ZOO2POMYB6W43FQ7G';
$userInput = '927055'; // 用户输入的动态验证码
if (verifyOtp($secret, $userInput)) {
    echo "验证通过!";
} else {
    echo "验证码错误,请重试。";
}
相关推荐
两个人的幸福10 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo12 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack12 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820713 天前
PHP 扩展——从入门到理解
php
鹏仔先生14 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下14 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip14 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒14 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog25014 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis14 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel