MENU

多用户 PassKey 认证插件

March 8, 2026 • Read: 810 • PHP

出于安全考虑,去年开发了一个 OTP 多因素认证插件,后面就一直想着开发通行密钥插件,这样就不需要输入密码了,点两下就能登录进来,于是就花了两个小时使用 GPT-5.3-Codx 写了这个插件,不得不说 AI 还是太好用了。

功能

  • 支持多用户的 PassKey 插件(通行密钥、WebAuthn 说的都是它),
  • 支持 Typecho 1.2.0 + 的版本。
  • 支持自定义通行密钥名称
  • 一个用户支持创建多个通行密钥

使用

  1. 安装插件后,去个人设置页面绑定一个通行密钥
    69ad3d80e7691.jpg
  2. 通行密钥绑定后 就可以在登录时使用
    69ad3dd1c9cb3.jpg

下载

去 CNB 下载这个插件

https://cnb.cool/dbkuaizi/typecho/passkey/-/releases

或者直接下载代码仓库:
https://cnb.cool/dbkuaizi/typecho/passkey

注意:releases 发布版本 中的 zip 包已经包含 PassKey 插件目录,如果是直接下载的代码仓库,需要自行创建 usr/plugins/PassKey 目录

Leave a Comment

6 Comments
  1. 无名氏 无名氏

    那个,建议你问一下其他 AI 就是,插件是否完整实现了 Webauthn 的后端验证以及 challenge 验证。这个相当于把公钥当密码了,只是验证你传输给服务器的公钥是否和数据库的公钥一致,没有用 Openssl 这些就行验证。

    1. @无名氏插件在登录阶段,有后端 challenge + OpenSSL 验签,不是简单的公钥对比(可参考 Action.php 文件中的 `verifyAssertionSignature` 函数)。

      但在个人设置页的绑定阶段,后端并没有完整实现 WebAuthn 的注册校验流程,这是基于 “已登录的会话是可信的” 这一前提做的取舍。

      总的来说:登录是的验证链路是完整的,绑定时的流程确实是有一些缺陷,但登录时的安全性还是有保障的。

    2. 无名氏 无名氏

      @两双筷子还是建议检查一下 GPT 实现的那个 challenge 验证,是硬编码的验证,也就是 GPT,硬编码了 OPENSSL_ALGO_SHA256 实际上只能处理 RSA 签名,对于目前最主流的 ES256 (椭圆曲线) 签名,在底层逻辑上是不匹配的。并且还多处用 @ 抑制 PHP 和 OpenSSL 报错,看一下你本地的公钥的算法,建议一下,或者是 JavaScript 里面有没有声明服务器支持的算法。以及这个插件硬编码了登录的 session 是有问题的,Typecho 登录本身是防御 session 重放的,但是这个硬编码不行,你可以参考另外一个 Passkey 插件,实现的就很好,就是:
      // 使用 Typecho 标准的 simpleLogin 方法
      $userWidget = \Widget\User::alloc();
      $expire = 30 * 24 * 3600;

      if (!$userWidget->simpleLogin($user['uid'], false, $expire)) {
      error_log('[Passkey][ERROR] Failed to set login state for user: ' . $user['uid'] . ' - ErrCode: ' . self::ERR_UNKNOWN);
      $this->error('登录失败,请重试', self::ERR_UNKNOWN);
      return;
      }
      以及这个解码的时候,如果构造一个循环的证书套,这个没有限制解码层数,会把服务器的计算资源耗尽的。

    3. 无名氏 无名氏

      @无名氏以及防御性编程,就是早期的 Typecho 的simpleLogin,安全性没有现在的这么好,另外一个 Passkey 也考虑到了,就是:
      // 登录成功后重新生成 session ID 防止会话固定攻击
      if (session_status() === PHP_SESSION_ACTIVE) {
      session_regenerate_id(true);
      }
      我说的循环资源耗尽攻击,在另外一个插件的防御形式是这样的:
      * 递归解码 CBOR 值(添加深度参数)
      */
      private static function decodeCBORValue($data, &$offset, $depth)
      {
      if ($offset >= strlen($data)) {
      throw new \Exception('Unexpected end of CBOR data');
      }

      $initialByte = ord($data[$offset++]);
      $majorType = $initialByte >> 5;
      $additionalInfo = $initialByte & 0x1f;
      并且,因为用了 @ 抑制错误,本质上可以通过构造一个会使得 PHP 内存溢出的构造请求体,让OpenSSL内存溢出,尝试通过这个注入恶意代码,虽然权限是OpenSSL的www权限,但是也很危险了,比如说:
      // 安全处理64位整数,防止32位系统溢出
      // 检查是否超出PHP_INT_MAX
      if (PHP_INT_SIZE === 8) {
      // 64位系统,直接计算
      $value = ($high 0) {
      throw new \Exception('CBOR 64-bit integer not supported on 32-bit PHP');
      }
      $value = $low;
      }

    4. @两双筷子留言看到了,还没修复先不放出来了,今天有点忙 这几天修复一下 发个新版本

    5. @无名氏(修复内容过多 所以以下内容是通过 AI 整理出来的调整报告)
      本次主要针对 WebAuthn 注册、登录流程做了补强,并处理了一些兼容和稳定性问题。
      已确认并修复的风险包括:服务端校验不完整、challenge/origin/rpId 校验链路不够严谨、签名计数器校验不足、登录状态未完全走 Typecho 标准流程、CBOR/请求体缺少长度与深度限制、错误处理和存储结构在特定情况下可能引发异常。现在注册和登录都会在服务端完成必要校验,登录改为使用 Typecho 标准 simpleLogin,并补充 session 安全处理。

      原反馈里有些判断并不完全准确。比如“完全没做 challenge 验证”“只是把公钥当密码比对”这类说法不成立,插件原先已经有部分校验逻辑,只是实现不够完整,边界处理也不够严谨。这次不是简单的修复,而是通过 AI 把注册、验证、存储、会话这几块都重新理顺了一遍。

      另外还顺手处理了几项非直接漏洞但会影响稳定性的点:新增路由自动补齐、禁用再启用后的存储兼容修复、前端提示统一中文化,并在非 HTTPS / 非安全上下文下明确给出提示,避免误判为“功能失效”。