Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 89 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,30 @@
Google Authenticator (TOTP)
===========================
# Google Authenticator (TOTP)

![Build Status](https://github.com/Vectorface/GoogleAuthenticator/workflows/Test/badge.svg)

**English** | [中文](./README.zh-CN.md)

This is a fork of https://github.com/PHPGangsta/GoogleAuthenticator with the following changes:
A PHP library for two-factor authentication (2FA) compatible with the Google Authenticator mobile app.
It can generate secrets, generate and verify one-time codes, and render the secret as a scannable
QR code — implementing TOTP as specified in [RFC 6238](https://tools.ietf.org/html/rfc6238).

- Uses https://github.com/endroid/qr-code to generate QR code data URIs
- No longer generates Google's Chart API to make QR code links
- Uses namespacing
- Augmented test coverage to 100%
- Bumped minimum PHP version to 8.2

Original License:
-----------------
This is a fork of [PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator) with the following changes:

* Copyright (c) 2012-2016, [http://www.phpgangsta.de](http://www.phpgangsta.de)
* Author: Michael Kliewe, [@PHPGangsta](http://twitter.com/PHPGangsta) and [contributors](https://github.com/PHPGangsta/GoogleAuthenticator/graphs/contributors)
* Licensed under the BSD License.

Description:
------------
- Uses [endroid/qr-code](https://github.com/endroid/qr-code) to generate QR code data URIs
- No longer relies on Google's Chart API for QR code links
- Uses namespacing
- Test coverage raised to 100%
- Minimum PHP version bumped to 8.2

This PHP class can be used to interact with the Google Authenticator mobile app for 2-factor-authentication. This class
can generate secrets, generate codes, validate codes and present a QR-Code for scanning the secret. It implements TOTP
according to [RFC6238](https://tools.ietf.org/html/rfc6238)
## Installation

For a secure installation you have to make sure that used codes cannot be reused (replay-attack). You also need to
limit the number of verifications, to fight against brute-force attacks. For example you could limit the amount of
verifications to 10 tries within 10 minutes for one IP address (or IPv6 block). It depends on your environment.
Install via [Composer](https://getcomposer.org/doc/01-basic-usage.md):

Usage:
------
```bash
composer require vectorface/googleauthenticator
```

See following example:
## Quick Start

```php
<?php
Expand All @@ -43,24 +33,26 @@ require_once 'vendor/autoload.php';
use Vectorface\GoogleAuthenticator;

$ga = new GoogleAuthenticator();

// 1. Create a secret and share it with the user
$secret = $ga->createSecret();
echo "Secret is: {$secret}\n\n";

// 2. Render the secret as a QR code (PNG data URI) for the user to scan
$qrCodeUrl = $ga->getQRCodeUrl('Admin', $secret, 'Blog');
echo "PNG Data URI for the QR-Code: {$qrCodeUrl}\n\n";

// 3. Verify the one-time code entered by the user
$oneCode = $ga->getCode($secret);
echo "Checking Code '$oneCode' and Secret '$secret':\n";

// 2 = 2*30sec clock tolerance
// discrepancy = 2 allows a clock tolerance of ±2 × 30 seconds
$checkResult = $ga->verifyCode($secret, $oneCode, 2);
if ($checkResult) {
echo 'OK';
} else {
echo 'FAILED';
}
echo $checkResult ? 'OK' : 'FAILED';
```
Running the script provides output similar to:

Running the script produces output similar to:

```
Secret is: OQB6ZZGYHCPSX4AK

Expand All @@ -70,17 +62,71 @@ Checking Code '848634' and Secret 'OQB6ZZGYHCPSX4AK':
OK
```

Installation:
-------------
## Security Considerations

### Preventing replay attacks

A TOTP code must never be accepted twice. `verifyCode()` accepts an optional by-reference
parameter that receives the time slice which matched. As required by
[RFC 6238 section 5.2](https://tools.ietf.org/html/rfc6238#section-5.2), persist the last
accepted time slice per user and refuse any code that matches a slice less than or equal to
the stored value:

```php
$matchedTimeSlice = null;
if ($ga->verifyCode($secret, $oneCode, 2, $matchedTimeSlice)) {
if ($matchedTimeSlice <= $user->lastUsedTimeSlice) {
// Code already used: reject to prevent a replay attack
} else {
$user->lastUsedTimeSlice = $matchedTimeSlice; // persist this value
// Code accepted
}
}
```

### Rate limiting

To defend against brute-force attacks, limit the number of verification attempts. For example,
allow at most 10 tries within 10 minutes per IP address (or IPv6 block) — adjust to fit your
environment.

## Advanced Usage

### Custom code length

Codes are 6 digits by default. Lengths of 6 to 8 digits are supported (RFC 4226):

```php
$ga = (new GoogleAuthenticator())->setCodeLength(8);
```

### Building otpauth:// URIs

For full control over the provisioning URI (algorithm, digits, period, HOTP counter, etc.),
use the fluent `UriBuilder`:

```php
$uri = $ga->getUriBuilder()
->issuer('Blog')
->account('Admin')
->secret($secret)
->getUri(); // otpauth://totp/Blog:%20Admin?secret=...&issuer=Blog
```

## Running Tests

All tests live in the `tests` folder.

```bash
composer install
composer test
```

- Use [Composer](https://getcomposer.org/doc/01-basic-usage.md) to
install the package
## License

```composer require vectorface/googleauthenticator```
Licensed under the [BSD License](./LICENSE.md).

Run Tests:
----------
Original work:

- All tests are inside `tests` folder.
- Execute `composer install` to prepare your environment.
- Run `composer test` from the project root directory.
- Copyright (c) 2012-2016, [http://www.phpgangsta.de](http://www.phpgangsta.de)
- Author: Michael Kliewe, [@PHPGangsta](http://twitter.com/PHPGangsta) and [contributors](https://github.com/PHPGangsta/GoogleAuthenticator/graphs/contributors)
127 changes: 87 additions & 40 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
Google Authenticator (TOTP)
===========================
# Google Authenticator (TOTP)

![构建状态](https://github.com/Vectorface/GoogleAuthenticator/workflows/Test/badge.svg)

**中文** | [English](./README.md)

这是 https://github.com/PHPGangsta/GoogleAuthenticator 的一个分支,包含以下更改:
一个与 Google Authenticator 移动应用兼容的 PHP 双因素认证(2FA)库。
它可以生成密钥、生成并校验一次性验证码,并将密钥渲染为可扫描的二维码 —— 按照
[RFC 6238](https://tools.ietf.org/html/rfc6238) 规范实现 TOTP。

- 使用 https://github.com/endroid/qr-code 生成二维码数据 URIs
- 不再生成 Google 的 Chart API 来制作二维码链接
- 使用命名空间
- 将测试覆盖率增加到 100%
- 将最低 PHP 版本提升到 8.2

原始许可证:
-----------------
本项目是 [PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator) 的分支,包含以下改动:

* 版权所有 (c) 2012-2016, [http://www.phpgangsta.de](http://www.phpgangsta.de)
* 作者:Michael Kliewe, [@PHPGangsta](http://twitter.com/PHPGangsta) 和 [贡献者](https://github.com/PHPGangsta/GoogleAuthenticator/graphs/contributors)
* 根据 BSD 许可证授权。

描述:
------------
- 使用 [endroid/qr-code](https://github.com/endroid/qr-code) 生成二维码数据 URI
- 不再依赖 Google Chart API 生成二维码链接
- 使用命名空间
- 测试覆盖率提升至 100%
- 最低 PHP 版本提升至 8.2

这个 PHP 类可以用来与 Google Authenticator 移动应用进行双重因素认证交互。该类可以生成密钥、生成代码、验证代码并提供用于扫描密钥的二维码。它实现了根据 [RFC6238](https://tools.ietf.org/html/rfc6238) 的 TOTP。
## 安装

为了您能安全安装,您必须确保使用的代码不能被重复使用(重放攻击)。您还需要限制验证次数,以对抗暴力攻击。例如,您可以将一个 IP 地址(或 IPv6 块)的验证次数限制为 10 分钟内 10 次尝试。这取决于您的环境。
通过 [Composer](https://getcomposer.org/doc/01-basic-usage.md) 安装:

用法:
------
```bash
composer require vectorface/googleauthenticator
```

请参见以下示例:
## 快速开始

```php
<?php
Expand All @@ -39,43 +33,96 @@ require_once 'vendor/autoload.php';
use Vectorface\GoogleAuthenticator;

$ga = new GoogleAuthenticator();

// 1. 创建密钥并分发给用户
$secret = $ga->createSecret();
echo "密钥是: {$secret}\n\n";

// 2. 将密钥渲染为二维码(PNG 数据 URI)供用户扫描
$qrCodeUrl = $ga->getQRCodeUrl('Admin', $secret, 'Blog');
echo "二维码的 PNG 数据 URI: {$qrCodeUrl}\n\n";

// 3. 校验用户输入的一次性验证码
$oneCode = $ga->getCode($secret);
echo "检查代码 '$oneCode' 和密钥 '$secret':\n";
echo "检查验证码 '$oneCode' 和密钥 '$secret':\n";

// 2 = 2*30秒的时钟容差
// discrepancy = 2 表示允许 ±2 × 30 秒的时钟偏差
$checkResult = $ga->verifyCode($secret, $oneCode, 2);
if ($checkResult) {
echo 'OK';
} else {
echo 'FAILED';
}
echo $checkResult ? 'OK' : 'FAILED';
```
运行脚本会提供类似以下的输出:

运行脚本会得到类似以下的输出:

```
密钥是: OQB6ZZGYHCPSX4AK

二维码的 PNG 数据 URI: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAIAAAAI[已截断]

检查代码 '848634' 和密钥 'OQB6ZZGYHCPSX4AK':
检查验证码 '848634' 和密钥 'OQB6ZZGYHCPSX4AK':
OK
```

安装:
-------------
## 安全注意事项

### 防止重放攻击

同一个 TOTP 验证码绝不能被接受两次。`verifyCode()` 接受一个可选的引用参数,
用于接收匹配成功的时间片。按照 [RFC 6238 第 5.2 节](https://tools.ietf.org/html/rfc6238#section-5.2)
的要求,请为每个用户持久化最后一次接受的时间片,并拒绝任何匹配时间片小于或等于该存储值的验证码:

```php
$matchedTimeSlice = null;
if ($ga->verifyCode($secret, $oneCode, 2, $matchedTimeSlice)) {
if ($matchedTimeSlice <= $user->lastUsedTimeSlice) {
// 验证码已被使用:拒绝以防止重放攻击
} else {
$user->lastUsedTimeSlice = $matchedTimeSlice; // 持久化该值
// 验证码通过
}
}
```

### 限制验证频率

为防御暴力破解攻击,请限制验证尝试次数。例如,可以将单个 IP 地址(或 IPv6 网段)
的验证限制为 10 分钟内最多 10 次尝试 —— 请根据实际环境调整。

## 进阶用法

### 自定义验证码长度

验证码默认为 6 位,支持 6 到 8 位(RFC 4226):

```php
$ga = (new GoogleAuthenticator())->setCodeLength(8);
```

### 构建 otpauth:// URI

如需完全控制配置 URI(算法、位数、周期、HOTP 计数器等),可使用链式的 `UriBuilder`:

```php
$uri = $ga->getUriBuilder()
->issuer('Blog')
->account('Admin')
->secret($secret)
->getUri(); // otpauth://totp/Blog:%20Admin?secret=...&issuer=Blog
```

## 运行测试

所有测试位于 `tests` 文件夹内。

```bash
composer install
composer test
```

- 使用 [Composer](https://getcomposer.org/doc/01-basic-usage.md) 安装包
## 许可证

```composer require vectorface/googleauthenticator```
基于 [BSD 许可证](./LICENSE.md)授权。

运行测试:
----------
原始项目:

- 所有测试都在 `tests` 文件夹内。
- 执行 `composer install` 来准备您的环境。
- 从项目根目录运行 `composer test`。
- 版权所有 (c) 2012-2016, [http://www.phpgangsta.de](http://www.phpgangsta.de)
- 作者:Michael Kliewe, [@PHPGangsta](http://twitter.com/PHPGangsta) 及[贡献者](https://github.com/PHPGangsta/GoogleAuthenticator/graphs/contributors)
20 changes: 17 additions & 3 deletions src/GoogleAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Writer\PngWriter;
use Exception;
use InvalidArgumentException;
use Vectorface\OtpAuth\Base32;
use Vectorface\OtpAuth\UriBuilder;

Expand Down Expand Up @@ -138,12 +139,19 @@ protected function getQRCodeDataUri(string $uri) : string
* Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now
*
* @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
* @param int|null $matchedTimeSlice Set to the time slice that matched on success; persist the last accepted
* slice and reject codes matching a slice <= that value to prevent replay
* @throws InvalidArgumentException if $discrepancy is out of the 0-60 range
*/
public function verifyCode(string $secret, string $code, int $discrepancy = 1) : bool
public function verifyCode(string $secret, string $code, int $discrepancy = 1, ?int &$matchedTimeSlice = null) : bool
{
if ($discrepancy < 0 || $discrepancy > 60) {
throw new InvalidArgumentException('Discrepancy must be between 0 and 60 time slices');
}

$currentTimeSlice = floor(time() / 30);

if (strlen($code) != 6) {
if (strlen($code) !== $this->_codeLength || !ctype_digit($code)) {
return false;
}

Expand All @@ -155,6 +163,7 @@ public function verifyCode(string $secret, string $code, int $discrepancy = 1) :
}

if (hash_equals($calculatedCode, $code)) {
$matchedTimeSlice = (int) ($currentTimeSlice + $i);
return true;
}
}
Expand All @@ -163,10 +172,15 @@ public function verifyCode(string $secret, string $code, int $discrepancy = 1) :
}

/**
* Set the code length, should be >=6
* Set the code length, must be between 6 and 8 (RFC 4226)
*
* @throws InvalidArgumentException
*/
public function setCodeLength(int $length) : self
{
if ($length < 6 || $length > 8) {
throw new InvalidArgumentException('Code length must be between 6 and 8');
}
$this->_codeLength = $length;
return $this;
}
Expand Down
Loading
Loading