日常开发/运维里,2FA(双重验证)几乎已经是标配:GitHub、Google、AWS、Cloudflare、各类后台……账号多了以后,验证码查找反而变成了一个高频“打断流”动作:打开手机 → 找到对应 App → 等刷新 → 复制粘贴。
于是我做了一个小工具:一个只给自己用的 Telegram Bot,用来离线计算 TOTP 验证码,并且做成“菜单化”的账号管理器:点选账号即可显示当前 2FA 码,支持刷新/重命名/删除/导入。
缘由:为什么要做一个 Bot 来管 MFA
- 减少切换成本:很多时候电脑在手、手机不在手(或解锁麻烦),但登录动作又必须要验证码。
- 多账号管理:同一个平台多个身份(工作/个人/客户),在 Authenticator 里也会很长。
- 可自托管:TOTP 计算本质是本地算法,不依赖外部接口;用 Bot 只是把“展示界面”换成消息交互。
备注:这个项目的定位是“自己掌控运行环境”的个人工具,不是公用服务(安全注意事项见文末)。
使用场景
- 远程登录/运维:在服务器上操作时,需要临时拿到验证码(前提是 bot 运行在你可信的机器上)。
- 多平台/多租户:几十个账号时,通过菜单快速定位,不用在手机里翻。
- 导入迁移:已有的密钥(例如导出表格)可以一键导入到 SQLite。
最终效果(交互体验)
- 发送
/start后出现主菜单:- 查看账号列表(单列带序号,长名称会自动缩略)
- 手动添加账号(两步对话:备注名 → Secret)
- 批量导入(从项目目录的
mfa_secrets_export.xlsx导入)
- 点选某个账号:
- 显示当前验证码(6 位自动加空格,便于肉眼核对)
- 显示剩余有效期进度条(30 秒制)
- 按钮:刷新 / 重命名 / 删除 / 返回列表
(这里你可以在 Hexo 里插入两张截图:主菜单 & 验证码详情页)
实现方式:代码结构与关键点
项目很小,核心就两层:
bot.py:Telegram 交互层(菜单、回调、对话状态机、权限控制)mfa_manager.py:数据与算法层(SQLite 持久化、Excel 导入、pyotp 计算 TOTP)
下面摘几段关键实现(为便于阅读做了少量裁剪):
0) 入口与权限:只给自己用
通过 OWNER_ID 做最小权限控制:只有指定 Telegram 用户才能操作菜单。
1 | # bot.py(节选) |
1) TOTP 计算:完全离线
TOTP 的本质是共享密钥 + 时间窗口(通常 30 秒)生成动态码。项目使用 pyotp 来做算法实现:
1 | import pyotp |
因此运行时不需要调用任何第三方 API —— 只要机器时间相对准确即可。
项目里封装成方法,顺便把用户输入里常见的空格去掉:
1 | # mfa_manager.py(节选) |
2) SQLite 存储:轻量、够用
所有账号密钥存进本地 mfa_data.db,表结构大致是:
account:账号/名称(用于展示或备注默认值)issuer:发行方(导入时可带)secret:TOTP 密钥(Base32)remark:用户自定义备注(菜单里优先显示)created_at:创建时间
这意味着 不会丢在聊天记录里;但也带来一个重要前提:运行机器的磁盘必须可信(后面会说风险)。
初始化表结构的代码如下:
1 | # mfa_manager.py(节选) |
3) Bot 交互:InlineKeyboard + CallbackQuery
菜单体验依赖 Telegram 的 Inline Keyboard:
- 账号列表:每个按钮的
callback_data形如show_<id>,点击后查询 DB → 生成验证码 → 编辑原消息展示。 - 刷新:按钮还是同一个
show_<id>,点击即重新计算totp.now()。 - 重命名/删除:分别走对话状态机和二次确认。
为了避免频繁编辑导致的 “Message is not modified” 报错,封装了一个安全编辑函数:当内容没变化时静默跳过。
1 | # bot.py(节选) |
菜单回调的核心分发逻辑是:识别 callback_data 前缀并路由到对应动作(展示验证码/返回列表/导入/删除确认等):
1 | # bot.py(节选) |
4) 权限控制:只允许 Owner 使用
通过环境变量 OWNER_ID 限制访问:只有指定 Telegram 用户 id 才能使用机器人。没配置(或为 0)时默认放开(更适合本地测试,但不建议线上这么做)。
5) 代理与网络波动:更稳定的轮询
机器人使用 polling 模式,并且:
- 支持
PROXY_URL走代理 - 延长
read_timeout/connect_timeout - 统一错误处理:对常见网络波动只打印关键信息
Excel 批量导入格式
机器人会从项目目录读取固定文件名:mfa_secrets_export.xlsx。
表头至少需要包含:
AccountSecret
可选:
Issuer
导入时会按 Secret 去重,避免重复插入。
导入逻辑的关键点是:读取表格 → 规范化 Secret → 以 Secret 做去重 → 写入 SQLite:
1 | # mfa_manager.py(节选) |
如何运行(自托管)
1) 准备环境变量
建议在项目根目录放一个 .env(不要提交到仓库):
1 | TELEGRAM_BOT_TOKEN=123456:xxxxx |
2) 安装依赖
项目用到的主要依赖:
python-telegram-bot(代码风格看起来是 v13 系列:Updater/Filters/use_context=True)python-dotenvpyotppandas(以及openpyxl用于读取 xlsx)
安装示例:
1 | pip install python-telegram-bot==13.* python-dotenv pyotp pandas openpyxl |
3) 启动
1 | python bot.py |
然后在 Telegram 对你的 Bot 发送 /start 即可。
安全注意事项(务必读)
这个项目能省事,但密钥的安全性永远优先:
- Telegram 聊天不是端到端加密(Bot 会经过 Telegram 服务器),并且你的 Bot Token 一旦泄露,风险极高。
- SQLite 明文存储 secret:拿到你的磁盘/备份的人就可能拿到所有 2FA 密钥。
- OWNER_ID 只是应用层限制:能防止“正常使用路径”的别人点进来,但防不住运行机被入侵、Token 泄露等问题。
建议:
- 只在你完全信任的机器上自托管(例如个人小服务器/家用 NAS,且做好系统加固)。
OWNER_ID必须设置成你的 Telegram id。.env、mfa_data.db、mfa_secrets_export.xlsx做好权限控制与备份策略(备份本身也要加密)。- 如果要更进一步:给
secret做本地加密(例如用主密码派生密钥),并考虑把敏感输入改为一次性会话、避免在聊天里长期留痕。
可以继续完善的方向
- 加密存储:对
secret字段加密,主密码只在进程内存中短暂存在。 - 更友好的导入/导出:支持上传文件导入、导出加密备份。
- 支持 HOTP / 不同位数 / 不同周期:适配更多平台。
- 更严格的安全策略:白名单 chat、限制命令、自动超时锁定等。
这个 Bot 的目标很明确:把 TOTP 的“计算”留在你掌控的机器上,把“取码”变成一次点击。如果你准备长期使用,最值得优先投入的是:本地加密存储(保护 secret)、更严格的访问边界(限制 chat/命令)、以及对备份介质的加密与权限控制。