将Tunnelblick和Google身份验证器结合在一起

Github Repo

https://github.com/wangyufeng0615/auto_tunnelblick_googleauth

背景

使用Tunnelblick拨入VPN的时候,除了固定的密码,你可能还要输入一段“动态密码”(以下称为”OTP Code”)。一般比较常用的生成工具是Google身份验证器(Google Authenticator)。

这种验证器作为一种“双因素认证”,使用非常广泛。我最早接触它,应该是小时候买点卡时见到的“网易令牌”之类的东西。现在的话,Steam的登录也有这种验证机制,原理都是类似的。

Tunnelblick + Google身份验证器配合起来,使用上的痛点是:Google身份验证器一般安装在手机上,当我输入Tunnelblick密码时,需要打开手机,读出6位OTP Code,手动输入,而且还可能因为超时因素,需要重新输入。使用不便。

虽然Google身份验证器也能安装在电脑上,例如作为Chrome插件安装,但仍略显繁琐。

结合网络上前人的工作,我研究了一种方法并实现为脚本,能够一键连接Tunnelblick中的vpn,而不需要手动输入OTP Code。同时,在一定程度上又不失太多安全性。

请注意,本方案纯属研究性质,因为会弱化双因素认证的安全性,所以请不要在任何非实验性质的环境中使用。

实现思路

首次设置

Keychain是macOS上一个很好的系统自带工具。我们可以将需要使用的敏感信息,如密码,存放在Keychain中。

首先我们使用/usr/bin/security这个工具,将vpn的具体信息存放到keychain中。例如设置用户名:

1
2
# Set VPN account
/usr/bin/security add-generic-password -U -s Tunnelblick-Auth-"$1" -a username -w "$2"

以此类推。除了vpn用户名外,还需要设置vpn名称、vpn密码、OTP Key。

关于OTP Key,这个称呼可能不是很准确,我想表达的意思就是用于生成OTP动态密码的原始key。这个key一般长16位,如果你拿到的是一个二维码(Google Authenticator支持扫描二维码),那就用合适的工具,将二维码解码为字符串,secret字段的值就是OTP Key。

开启vpn

首先是读取vpn名称、vpn用户名、vpn密码、OTP Key。

关键的地方在于使用OTP Key,像Google Authenticator一样,计算出6位动态密码。网上有人用一大段代码实现,实际上已经有一个很好的工具oathtool

安装:

1
brew install oathtool

然后我们在脚本中运行了

1
OTPCode=$(oathtool --totp -b -d 6 "$OTPKey")

这样就获取了6位动态密码。大致的原理分析请看后文。

最后,我们将vpn密码和动态密码拼接在一起,并通过applescript传递给Tunnelblick即可。applescript是macOS支持的一种脚本语言,而Tunnelblick具有相关接口,非常方便。

1
echo 'Tell app "Tunnelblick" to connect '\"$1\" | osascript

Tunnelblick在连接的时候,会自动从Keychain中读取名为Tunnelblick-Auth-VPN_NAME的username和password字段(如果有的话)。

关闭VPN

只需要将connect替换为disconnect即可。

生成OTP Code的原理

Google Authenticator使用的是”基于时间的一次性密码算法”,简称TOTP。简单来说

  1. 将OTP Key视为base32字符串,并解码为二进制key。
  2. 根据Unix时间计算一个时间戳(timestamp),周期为30秒。30秒内的话,时间戳是不变的。
  3. 计算[key, timestamp]的HMAC-SHA1值,也就是计算哈希值。
  4. 读取哈希值的最后一个字节,并取这个字节的低位4个bits构成一个数,将这个数作为index。
  5. 以这4个值作为index,取哈希值的连续4个字节,构成一个十进制数,作为token的基底。
  6. 用这个十进制数做模运算,mod 10^digit,这个digit一般取6。所以最后会生成一个6位数。

这个六位数就是动态密码了。

TOTP算法非常可靠,是国际标准。

安全性评估

Google Authenticator一般安装在手机上,绝不是没有道理的。毕竟,即便小白用户将密码存放在电脑上,攻击者也不太可能同时获得用户的手机不是?

本方案将OTP Key存放在Keychain中,在每次拨号都需要输入macOS用户密码的情况下,相当于是把OTP Code的使用权交给了macOS用户。这样一来,OTP Code的安全性,就等同于macOS用户密码的安全性。从这个角度来看,双因素认证的安全性被弱化了。因为,如果用户的macOS密码失守,等同于攻击者获取了OTP Key、OTP Code和OTP Code的使用权(VPN密码自然更是如此)。

不论如何,这种将OTP Key存放在Keychain中的方案,还是比把OTP Key放在文件里要安全一些。

用户的便利性和系统的安全性是鱼和熊掌,是trade-off。