toggio/SecureTokenizer
GitHub: toggio/SecureTokenizer
一个基于 AES-256-GCM 认证加密的 PHP 无状态安全令牌库,无需数据库或 Session 即可实现令牌的生成、定时过期、轮换刷新与验证。
Stars: 7 | Forks: 2
# SecureTokenizer
一个用于加密安全令牌生成和管理的 PHP 库
SecureTokenizer 是一个 PHP 库,旨在通过提供生成和管理安全无状态令牌的高级功能,来增强 Web 应用程序的安全性。该库可集成到 PHP 和 AJAX 项目中,为创建不可预测的、经过身份验证的令牌提供了强大的解决方案,适用于请求验证、攻击缓解、短暂的 AJAX 流程以及加密数据交换。
它使用 OpenSSL AES-256-GCM、原生 HKDF、SHA-256 和 `random_bytes()` 来提供认证加密、上下文绑定、定时/可刷新令牌,以及无需数据库、PHP session、缓存或服务端令牌表的无状态验证。
## 功能特性
- **加密安全的令牌生成**:使用 `random_bytes()` 和 OpenSSL AES-256-GCM 认证加密。
- **无状态验证**:令牌在加密 payload 中携带着其经过认证的元数据,因此无需数据库、缓存、PHP session 或服务端令牌表。
- **不透明的十六进制格式**:令牌以单一的十六进制字符串形式输出。可见的字节以随机 IV 开头,因此没有稳定的前缀或明文令牌部分。普通、定时和可刷新令牌具有相同的可见长度。
- **定时令牌**:`tokenCreate(false, 900)` 会创建一个在 15 分钟后过期且保持完全无状态的不透明令牌。
- **可刷新令牌**:浏览器将其当前令牌发送到后端并接收一个替换令牌。不需要浏览器端的 HMAC/证明密钥。
- **AJAX 集成**:重复的 AJAX 调用可以从后端响应中更新内存中的令牌,而无需重新加载 PHP 页面。
- **同服务器和同客户端绑定**:可选择独立地将令牌绑定到 `SERVER_ADDR` 和/或 `REMOTE_ADDR`。
- **加密数据助手**:`encrypt()` 和 `decrypt()` 为验证后的数据交换提供了紧凑的认证加密。
## 安全范围
SecureTokenizer 专为无状态请求验证和短暂的 PHP/AJAX 流程而设计。定时和可刷新令牌通过使用短有效期加上绝对的 `valid_until` 限制来减少重放暴露。
默认的同服务器绑定(`ssb`)和同客户端绑定(`scb`)增加了额外的无状态重放缓解:令牌会根据预期的 `SERVER_ADDR` 和 `REMOTE_ADDR` 进行认证,因此当启用了这些绑定且地址稳定时,从不同服务器上下文或客户端 IP 重新发送的复制令牌将被拒绝。
## 环境要求
- 支持 `random_bytes()` 和 `hash_hkdf()` 的 PHP
- 支持 `aes-256-gcm` 的 OpenSSL 扩展
## 安装说明
包含此单个 PHP 文件:
```
require_once 'path/to/SecureTokenizer.php';
```
## 快速入门指南
### 初始化类
```
$key = 'demo-only-change-this-32-byte-minimum-key';
$tokenizer = new secureTokenizer($key);
```
上面显式指定的密钥旨在让快速入门示例可以直接复制运行。这是一个演示密钥,而非生产环境密钥。
### 生成安全令牌
```
$secureToken = $tokenizer->tokenCreate();
echo $secureToken;
```
### 验证令牌
```
// The token you receive, for example from a form or AJAX request.
$secureToken = 'paste-the-token-here';
if ($tokenizer->checkToken($secureToken)) {
echo 'Token is valid.';
}
```
在生产环境中,请将应用程序密钥存储在源代码之外,并从配置中读取:
```
$key = getenv('SECURETOKENIZER_KEY');
if ($key === false || $key === '') {
// Demo fallback only. Set SECURETOKENIZER_KEY in real deployments.
$key = 'demo-only-7c29cdb0e6d1b8f44e1f3a9d5c8420ea463b9f1c7d2e8564a0b3c6d8e9f2a1b';
}
$tokenizer = new secureTokenizer($key);
```
## 令牌模式
```
// Opaque stateless token with no expiration.
$token = $tokenizer->tokenCreate();
// Opaque stateless token valid for 15 minutes.
$token = $tokenizer->tokenCreate(false, 900);
// Refreshable token with defaults: 60 seconds per token, 1 day maximum.
$token = $tokenizer->tokenCreate(true);
// Refreshable token: each token lasts 30 seconds; the whole chain lasts 2 hours.
$token = $tokenizer->tokenCreate(true, 30, 7200);
```
## 定时和可刷新令牌
```
$key = getenv('SECURETOKENIZER_KEY');
if ($key === false || $key === '') {
// Demo fallback only. Set SECURETOKENIZER_KEY in real deployments.
$key = 'demo-only-7c29cdb0e6d1b8f44e1f3a9d5c8420ea463b9f1c7d2e8564a0b3c6d8e9f2a1b';
}
$tokenizer = new secureTokenizer($key);
$secureToken = $tokenizer->tokenCreate(true);
if ($tokenizer->checkToken($secureToken)) {
$newToken = $tokenizer->tokenRefresh($secureToken);
}
```
调用 `tokenCreate(true)` 会使用以下默认值创建一个可刷新令牌:
- 单个令牌有效期:`60` 秒
- 最大令牌链有效期:`86400` 秒(一天)
您可以覆盖这两个值:
```
// Each token lasts 30 seconds; the refresh chain can continue for 2 hours.
$token = $tokenizer->tokenCreate(true, 30, 7200);
```
Payload 是经过加密和认证的。定时 payload 包含单个令牌有效期、当前令牌过期时间、绝对最大有效时间戳和信息性计数器。后端密钥永远不会离开 PHP。
`$tokenLifetime` 是当前发出的单个令牌的有效窗口。
`$maxTokenLifetime` 是整个令牌链的绝对上限。当前令牌的 `expires_at` 也被限制为 `valid_until`,因此即将结束的令牌链无法创建超过该最终限制的令牌。
`tokenCreate(false, 900)` 是创建没有较长刷新链的定时令牌的紧凑方式。在内部,其 `$tokenLifetime` 和 `$maxTokenLifetime` 均为 900 秒。在它过期之前调用 `tokenRefresh()` 可能会重新发出替换令牌,但它无法延长最初的 15 分钟有效窗口。
普通、定时和可刷新令牌均为 92 个十六进制字符。普通令牌包含加密的随机填充,因此不会通过外部长度泄露令牌类型。
## 实际用例示例:重复的 AJAX 调用
对于重复的 AJAX 调用,脚本 1 将初始令牌发送到 JavaScript 中。浏览器将当前令牌发送到脚本 2。脚本 2 验证它并返回一个替换令牌。然后浏览器替换其内存中的令牌。
```
tokenCreate(true, 30, 7200);
?>
Ready.``` 将令牌写入 JavaScript 时,请使用 `json_encode($secureToken)`。虽然令牌是十六进制格式,但 `json_encode()` 仍能保持 PHP 到 JavaScript 边界的正确性,并避免手动字符串转义。 **AjaxRefreshReceiver.php** ``` checkToken($token); $newToken = $isTokenValid ? $tokenizer->tokenRefresh($token) : false; if (!$isTokenValid) { http_response_code(401); } header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store'); echo json_encode([ 'valid' => $isTokenValid, 'token' => $newToken === false ? null : $newToken, 'message' => $isTokenValid ? 'Token is valid.' : 'Token is invalid.', ], JSON_UNESCAPED_SLASHES); ?> ``` JSON 响应格式: ``` { "valid": true, "token": "92-hex-character-refreshed-token-or-null", "message": "Token is valid." } ``` - `valid`:提交的令牌的布尔验证结果。 - `token`:当验证和刷新成功时的刷新令牌,否则为 `null`。 - `message`:简短的易读状态字符串。 AJAX 刷新示例支持 `X-Secure-Token`,并且也接受 GET 作为调试传输方式:生成的 URL 可以直接在浏览器中打开、复制到另一个测试客户端,并在检查同服务器/同客户端绑定行为时进行检查。请仅将 GET 视为一种测试便利手段。在生产环境中,优先使用 `X-Secure-Token` 或 POST body,这样令牌就不会存储在浏览器历史记录、服务器日志、反向代理日志、`Referer` 头或分析 URL 中。无效请求将返回带有相同 JSON 结构的 HTTP `401` 状态码。 ## 构造函数 ``` $tokenizer = new secureTokenizer($key, true, true); // $ssb, $scb ``` - `string|null $key`:用于派生加密和认证密钥的应用程序密钥。使用相同的强密钥来创建和验证令牌。 从配置、环境变量或密钥管理器中传入至少 32 字节的秘密材料。当提供的密钥短于 32 字节时,SecureTokenizer 会发出警告。如果省略或为空,它将生成一个随机运行时密钥。 这仅在同一个 tokenizer 对象内部有用;使用该随机密钥创建的令牌将无法在另一个对象、跨独立请求、页面加载、不同脚本或不同服务器中进行验证。对于实际使用,请始终从配置中传入稳定的秘密。该密钥在类内部是私有的;如果必须在运行时替换它,请使用 `changeKey()`。 - `bool $ssb`:同服务器绑定。为 true 时,令牌绑定到 `SERVER_ADDR`;具有不同服务器上下文的接收方将拒绝该令牌。 - `bool $scb`:同客户端绑定。为 true 时,令牌绑定到 `REMOTE_ADDR`;来自不同客户端 IP 的请求将拒绝该令牌。 ## 上下文绑定:`ssb` 和 `scb` 默认情况下,这两个绑定都是启用的: ``` $tokenizer = new secureTokenizer($key); // same as true, true $tokenizer = new secureTokenizer($key, true, true); // explicit ssb/scb ``` - `ssb` 表示同服务器绑定。令牌根据当前的 `SERVER_ADDR` 进行认证。仅当发送方和接收方有意在不同的服务器地址后运行,或者该值不稳定的负载均衡设置中时,才禁用它。 - `scb` 表示同客户端绑定。令牌根据当前的 `REMOTE_ADDR` 进行认证。当代理、CDN、移动网络或负载均衡器导致客户端地址在请求之间发生变化时,请禁用它。 这些绑定是网络上下文检查,在服务器和客户端地址在令牌生命周期内保持稳定时效果最佳。 ## 自定义和高级用法 SecureTokenizer 可以通过独立的同服务器和同客户端绑定、自定义的单个令牌有效期、自定义的最大刷新链有效期、定时检查的时钟偏差容差以及下述的加密交换辅助方法进行配置。 ## 公共属性和方法 ### 属性 - `int $defaultTokenLifetime`:当省略 `tokenCreate()` 的第二个参数时,默认的单个令牌有效期(以秒为单位)。默认值:`60`。 - `int $defaultMaxTokenLifetime`:当省略 `tokenCreate()` 的第三个参数时,默认的最大令牌链有效期(以秒为单位)。默认值:`86400`。 ### 方法 - `changeKey(string $key): void` 替换已配置的应用程序密钥并清除派生的交换密钥。 请使用此方法而不是直接修改内部结构。 - `getExchangeKey(): string` 返回在 `tokenCreate()` 或成功执行 `checkToken()` 后派生的二进制密钥。这两个 PHP 脚本都可以在令牌被接受后,将其与 `encrypt()` / `decrypt()` 结合使用以交换加密数据。在执行 `tokenRefresh()` 之后,交换密钥仍然指向为当前请求接受的令牌,这通常是保护对该请求的响应的正确密钥。 稍后对返回的令牌调用 `checkToken()` 会派生出该返回令牌自己的交换密钥。 - `tokenCreate(bool $refreshable = false, int|null $tokenLifetime = null, int|null $maxTokenLifetime = null): string` 创建一个经过认证的令牌。如果没有有效期,它将创建一个没有过期时间的普通不透明令牌。当设置 `$refreshable = false` 并提供 `$tokenLifetime` 时,它会创建一个最大有效期等于当前令牌有效期的定时令牌。当 `$refreshable = true` 时,`$tokenLifetime` 是每个发出的令牌的有效窗口,而 `$maxTokenLifetime` 是整个刷新链的最大总有效期。这两个值都被限制在 1 秒到 366 天之间。 - `checkToken(string $string, int $tolerance = 0): bool` 验证真实性、同服务器绑定、同客户端绑定、令牌格式和可选的过期时间。普通/定时格式是在认证成功后从加密标志中读取的。成功时,派生出可通过 `getExchangeKey()` 读取的交换密钥。`$tolerance` 是用于定时令牌过期检查的时钟偏差容差(以秒为单位)。它被限制为最大 `300` 秒,因为较大的值会过多地延长接受的重放窗口。 - `tokenRefresh(string $string, int $tolerance = 0): string|false` 验证定时令牌并返回带有全新过期时间的替换令牌,同时保留相同的令牌有效期和绝对最大有效期限制。如果令牌无效、已过期、是普通类型或已超过 `valid_until`,则返回 `false`。该方法将交换密钥设置为为当前请求接受的令牌,可通过 `getExchangeKey()` 读取。`$tolerance` 具有与 `checkToken()` 中相同的时钟偏差含义和最大 `300` 秒限制。 - `encrypt(string $string, string $key = null): string` 加密任意二进制/文本数据并返回二进制加密帧。如果需要将其作为文本存储或传输,请使用 `bin2hex()` 或 base64。 - `decrypt(string $string, string $key = null): string|false` 解密由 `encrypt()` 生成的数据,并在认证失败时返回 `false`。 ## 工作原理 1. 构建一个紧凑的 18 字节二进制 payload。普通令牌包含版本、标志和随机填充。定时令牌包含版本、标志、令牌有效期、过期时间、最大有效时间戳和计数器。 2. 原生 `hash_hkdf()` 从应用程序密钥和启用的同服务器/同客户端绑定上下文中派生出特定用途的密钥。 3. OpenSSL AES-256-GCM 对 payload 进行加密和认证。二进制帧为 `randomIV | authenticationTag | ciphertext`,然后进行十六进制编码。普通和定时令牌使用相同的令牌 AAD 和相同的帧长度。 4. 对于 AJAX 刷新,客户端将当前令牌发送到后端。后端对其进行验证,检查 `expires_at` 和 `valid_until`,并返回一个新的不透明令牌。 5. 在令牌创建或成功验证时,SecureTokenizer 从接受的令牌帧派生出一个交换密钥。当两个 PHP 脚本需要交换加密数据而不暴露后端密钥时,请通过 `getExchangeKey()` 读取它。 ## 示例 - `examples/BasicExample.php`:关于普通、定时和可刷新令牌的紧凑报告。 - `examples/AjaxRefreshSender.php`:带有刷新计时/状态详细信息的最简浏览器 UI。 - `examples/AjaxRefreshReceiver.php`:令牌验证和刷新端点。 示例文件在可用时会读取 `SECURETOKENIZER_KEY`。如果未设置,它们将使用公开的演示回退方案,以便示例能够立即运行。请勿在生产环境中使用该回退密钥。 ## 许可证 SecureTokenizer 基于 Apache License, Version 2.0 授权。您可以在遵守许可证的前提下自由使用、修改和分发本库。 Copyright (C) 2024-2026 Luca Soltoggio - https://www.lucasoltoggio.it/
标签:AES-256-GCM, AJAX, CSRF防护, ffuf, HKDF, OpenSSL, OpenVAS, PHP, PHP库, Web安全, 令牌生成, 会话管理, 前后端分离, 加密, 安全, 安全测试工具, 数据加密, 无状态, 漏洞扫描器, 状态管理, 蓝队分析, 认证, 超时处理, 随机数