# 前言

1202 年,越来越多的软件 / 网站支持用短信验证码直接登陆,很多手机上也推出了一键填入短信验证码的功能,然而这个功能没办法很好的衍生到电脑上。

因为博主没有安卓设备,本文目前只针对 macOS+iPhone 进行探讨

# 方案

虽然 macOS 上的 Safari 已经支持自动填入 iPhone 收到的短信验证码,但是作为一个软件工程表演专业的人来说,Safari 太不(普)专(通)业了,况且如果不用 Chrome 怎么把内存用完呢(OOM 警告)。
一次偶然的机会,我在找 workflow 的时候看到了 iMessage 2FA Workflow for Alfred,看效果图还不错,于是就安装试了试
2fm
第一感觉效果确实可以,但是实际检索结果有点差强人意,通常只能查到很早之前的验证码。
imsg
于是就去看了看 2fm 的实现原理,发现 macOS 的短信是通过 /Users/teaven/Library/Messages/chat.db 下的 SQLite DB 保存的。问题一下子就简单起来了。
由于对 php(2fm 使用 php 编写)不是很熟悉,加上想在此基础上拓展一些功能,例如把 email 的的验证码 / 2FALink 也整合进来,于是打算用 Python 重写一遍,并且将其从 Alfred 中剥离出来。

# 实现

大体思路是
解析chat.db数据
获取验证码
监控更新
读取邮件
匹配2FALink/CODE
菜单栏控制中心
打包app

# 解析 chat.db 原始数据

调试的时候发现一直没办法访问 chat.db,一开始以为是用户资源库有权限限制,但是 alfred 是可以正常访问的。于是把 chat.db 复制到下载目录,可以正常访问,更加坚定了我的想法。Google 了一下没有结论,才想起来 macOS 有一个文件访问权限,之前只给 iTerm 开了 downloads 目录的,开了全盘就好了。

短信查询语句直接套用了 2fa 的,做了一点调整,加上 4 位验证码的支持,然后将 datetime 的输出改成 timestamp 方便后面做判断

# 获取验证码

因为验证码短信的模板五花八门,常规匹配 4-8 位数字的方法会混进一些脏数据。例如

【预存话费,不止十倍优惠】尊敬的杭州移动用户,现邀您参加“杭州移动消费抵扣合约”活动,您只需预存10元,每月保底38元,即可得117元消费抵扣(用于话费减免),前3个月每月返15元,后9个月每月返8元,合约期12个月。点击 https://dx.10086.cn/ 可直接办理,本短信转发无效。【中国移动 和你一起】

会匹配到 10086 的结果。目前的过滤办法是判断 [code|码] 是否在内容里。
获取到验证码之后调用 AppleScript 发送通知,效果如图
as
然后借助 pyperclip 把验证码复制到剪切板

# 监控更新

完成上述步骤后整个流程已经走通了,接下来就是如何感知更新。
最先想到的是设置一个定时任务定时去读取 db,但是考虑到验证码获取的及时性,定时任务间隔需要设置很短,频繁创建连接读取会造成不必要的性能损耗。
突然想起来 SQLite 保存的是一个 db 文件,那么就可以通过文件的更新时间来判断是否更新了

# 读取邮件

读取邮件花了比较多的时间,主要原因是 Google 百度上的资料不多,主要是发送邮件,而且都是相互转载的。看完廖雪峰大佬的博客和 email.pop3 官方文档后搞明白了。
同时也发现网上好多博客写的监听新邮件都是有问题的,因为 POP3 本身就是长连接协议,完成认证后就进入了 授权状态 然后通过 RETR命令 向服务器请求传送邮件内容,而很多写的都是 “认证 -> 获取邮件 -> 关闭连接 -> 认证” 的循环。先不说服务器是否允许频繁的认证操作,这么去实现总感觉很奇怪。

纠正:经过实际验证,Gmail 必须要重新连接才能获取到最新的邮件,没找到原因;qq 邮箱不需要

现有的邮箱基本上都默认关闭 pop3 的授权,尝试了 qq 邮箱、gmail 都需要手动开启。
qq 邮箱需要为 pop3 设置独立密码;gmail 需要先开启两步验证后才能启用独立密码(参考使用应用专用密码登录

# 匹配 2FALink/CODE

根据以往的一些邮件做了统计

验证码:609469,该验证码5分钟内有效。为了保障您的账户安全,请勿向他人泄漏验证码信息。
您的验证码为:969047
您的验证码为:474664,序号:4498,有效期5分钟,请勿向他人提供收到的信息
[富途]验证码:603643,十分钟内输入,请勿泄露给其他人。

匹配验证码的方式决定与短信的保持一致
针对两步验证的链接也做了关键词的统计,大概是这一些
(licenses|validation|confirmemail|authorizedevice|verification|register|verify|activate)
因为 pop3 可以获取到 html 格式的邮件内容,所以通过 a 标签 + 关键词的形式很快就能匹配到两步验证的链接
同样使用短信验证码相同的方法将结果发送到通知中心,并复制到剪切板
CleanShot 2021-06-19 at 17.35.45@2x

在此基础上,希望把邮件里的数字验证码,同步发送到手机的剪切板里。
这里需要借助 bark 这个 app,可以通过一个请求给对应的设备发送 iOS 通知
IMG_1725

# 菜单栏控制中心

虽然读取短信、邮件并发送通知以及自动复制到剪切板不是具有很强干扰性的功能,但是从安全性上考虑最好有一个快捷开关可以分别控制工具 / 通知 / 剪切板的启停。在 macOS 上,最方便且容易实现的就是菜单栏控制中心了。借助 rumps 比较方便的实现了常驻菜单栏的功能。
CleanShot 2021-06-20 at 14.53.25@2x
rumps 支持 app 自身发送通知,但是调试的时候发送不了,后面打包完验证才发现需要打包成 app 才能请求发送系统通知的权限
CleanShot 2021-06-20 at 15.32.09@2x

菜单栏暗黑模式检测借助 darkdetect 库封装的底层 oc 方法调用,能够根据系统版本和暗黑模式是否启用自动切换图标颜色。
darkdetect 省去了 pyojbc 的引入,并且支持多平台方便后续做跨平台支持。
CleanShot 2021-06-20 at 14.58.20@2x

# 打包 app

打包也花了很多时间,之前打包 exe 一直用的 pyinstaller,这个项目实际打包出来启动上有问题,直接进入 app 包的 macOS 目录启动是正常的,但是双击 app 启动就闪退了。
随后切换到 py2app 的打包方式,用的时候发现好像会把整个 site-package 打包进去,因为打包出来的 app 非常大,有 500+M,conda 创建了一个新环境大小就正常了。
打包的问题,考虑单独出一个 pyinstaller、py2app 的打包教程以及两者差异的文章

# 总结

整个项目 pop3 调试和打包花的时间比写代码花的时间还要多,并且功能实现之后总是会陆陆续续想到很多优化项,比如开关通知 / 剪切板功能、菜单栏控制中心、暗黑模式检测等等。有些功能之前也没有实现过,后续再遇到就会有经验了,也是最近开始写博客的原因,以往写的很多代码缺少沉淀,写完可能就忘了;也有一些 idea 没有动力 push 去实现。写博客算是一种鞭策吧,也能加强一下自己的表达能力。

# 附言

PY2FA 下载地址
本文所有源代码基于 GPL (GNU General Public License) 开源,项目地址

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Teaven 微信支付

微信支付

Teaven 支付宝

支付宝

Teaven 贝宝

贝宝