建立安全的VPN连接,不仅需要输入用户名和密码,还需要输入动态口令(token)。作为一个懒人,我更喜欢什么手工输入都不需要,既不需要输入password,也不需要输入token。也就是说,只需一个命令就能径直连接上VPN,那自然是极好滴。那么,懒人的愿望能实现吗?答案是肯定的!本文将基于FreeOTP 支持的TOTP(Time-based One-Time Password)算法,介绍如何利用Python代码自动获取动态口令(token),进而利用Expect实现一个自动连接VPN的Bash脚本。

PyOTP是一套开源的函数库,可用来计算基于TOTP算法的Token。有关TOTP算法的细节,本文不做介绍,如有兴趣请参考这里

1. 下载PyOTP

huanli@ThinkPadT460:tmp$ git clone https://github.com/pyotp/pyotp.git
Cloning into 'pyotp'...
remote: Counting objects: , done.
remote: Total (delta ), reused (delta ), pack-reused
Receiving objects: % (/), 165.02 KiB | 207.00 KiB/s, done.
Resolving deltas: % (/), done.
huanli@ThinkPadT460:tmp$
huanli@ThinkPadT460:tmp$ tree /tmp/pyotp/src
/tmp/pyotp/src
└── pyotp
├── compat.py
├── hotp.py
├── __init__.py
├── otp.py
├── totp.py
└── utils.py directory, files
huanli@ThinkPadT460:tmp$

2. 使用PyOTP

huanli@ThinkPadT460:tmp$ export PYTHONPATH=/tmp/pyotp/src:$PYTHONPATH
huanli@ThinkPadT460:tmp$ python
...<snip>...
>>> import base64
>>> import pyotp
>>> s = 'Hello World'
>>> secret = base64.b32encode(s)
>>> totp = pyotp.TOTP(secret)
>>> token = totp.now()
>>> print token
338462
>>>

由此可见,通过pyotp.TOTP()获取token非常容易。我们将调用到的核心代码实现如下:

# https://github.com/pyotp/pyotp/blob/master/src/pyotp/otp.py
..
10 class TOTP(OTP):
11 """
12 Handler for time-based OTP counters.
13 """
14 def __init__(self, *args, **kwargs):
15 """
16 :param interval: the time interval in seconds
17 for OTP. This defaults to 30.
18 :type interval: int
19 """
20 self.interval = kwargs.pop('interval', 30)
21 super(TOTP, self).__init__(*args, **kwargs)
..
37 def now(self):
38 """
39 Generate the current time OTP
40
41 :returns: OTP value
42 :rtype: str
43 """
44 return self.generate_otp(self.timecode(datetime.datetime.now()))
.. # https://github.com/pyotp/pyotp/blob/master/src/pyotp/otp.py
..
8 class OTP(object):
9 """
10 Base class for OTP handlers.
11 """
12 def __init__(self, s, digits=6, digest=hashlib.sha1):
13 """
14 :param s: secret in base32 format
15 :type s: str
16 :param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
17 :type digits: int
18 :param digest: digest function to use in the HMAC (expected to be sha1)
19 :type digest: callable
20 """
21 self.digits = digits
22 self.digest = digest
23 self.secret = s
24
25 def generate_otp(self, input):
26 """
27 :param input: the HMAC counter value to use as the OTP input.
28 Usually either the counter, or the computed integer based on the Unix timestamp
29 :type input: int
30 """
31 if input < 0:
32 raise ValueError('input must be positive integer')
33 hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
34 hmac_hash = bytearray(hasher.digest())
35 offset = hmac_hash[-1] & 0xf
36 code = ((hmac_hash[offset] & 0x7f) << 24 |
37 (hmac_hash[offset + 1] & 0xff) << 16 |
38 (hmac_hash[offset + 2] & 0xff) << 8 |
39 (hmac_hash[offset + 3] & 0xff))
40 str_code = str(code % 10 ** self.digits)
41 while len(str_code) < self.digits:
42 str_code = '' + str_code
43
44 return str_code
..

下面给出完整的Python脚本:

  • vpn_token.py
 #!/usr/bin/python
import sys
import datetime
import time def main(argc, argv):
if argc != 3:
sys.stderr.write("Usage: %s <token secret> <pyotp path>\n" % argv[0])
return 1 token_secret = argv[1]
pyotp_path = argv[2] sys.path.append(pyotp_path)
import pyotp
totp = pyotp.TOTP(token_secret) #
# The token is expected to be valid in 5 seconds,
# else sleep 5s and retry
#
while True:
tw = datetime.datetime.now() + datetime.timedelta(seconds=5)
token = totp.now()
if totp.verify(token, tw):
print "%s" % token
return 0
time.sleep(5) return 1 if __name__ == '__main__':
sys.exit(main(len(sys.argv), sys.argv))
  • 来自Terminal的Token:

  • 来自手机的Token:

由此可见,跟PyOTP计算出的Token码完全一致。于是,我们就可以利用Expect实现完全自动的VPN连接。例如: (注: 这里使用sexpect

  • autovpn.sh (完整的代码请戳这里
 #!/bin/bash

 function get_vpn_user
{
typeset user=${VPN_USER:-"$(id -un)"}
echo "$user"
return
} function get_vpn_password
{
typeset password=${VPN_PASSWORD:-"$(eval $($VPN_PASSWORD_HOOK))"}
echo "$password"
return
} function get_vpn_token
{
typeset f_py_cb=/tmp/.vpn_token.py
cat > $f_py_cb << EOF
#!/usr/bin/python
import sys
import datetime
import time def main(argc, argv):
if argc != :
sys.stderr.write("Usage: %s <token secret> <pyotp path>\\n" % argv[])
return token_secret = argv[]
pyotp_path = argv[] sys.path.append(pyotp_path)
import pyotp
totp = pyotp.TOTP(token_secret) #
# The token is expected to be valid in seconds,
# else sleep 5s and retry
#
while True:
tw = datetime.datetime.now() + datetime.timedelta(seconds=)
token = totp.now()
if totp.verify(token, tw):
print "%s" % token
return
time.sleep() return if __name__ == '__main__':
argv = sys.argv
argc = len(argv)
sys.exit(main(argc, argv))
EOF typeset pyotp_path=$VPN_PYOTP_PATH
typeset token_secret=$VPN_TOKEN_SECRET
if [[ -z $token_secret ]]; then
token_secret=$(eval $($VPN_TOKEN_SECRET_HOOK))
fi python $f_py_cb $token_secret $pyotp_path
typeset ret=$?
rm -f $f_py_cb
return $ret
} function get_vpn_conf
{
typeset conf=$VPN_CONF
echo "$conf"
return
} function check_sexpect
{
type sexpect >& | egrep 'not found' > /dev/null >&
(( $? != )) && return
return
} vpn_user=$(get_vpn_user)
(( $? != )) && exit
vpn_password=$(get_vpn_password)
(( $? != )) && exit
vpn_token=$(get_vpn_token)
(( $? != )) && exit
vpn_conf=$(get_vpn_conf)
(( $? != )) && exit check_sexpect || exit export SEXPECT_SOCKFILE=/tmp/sexpect-ssh-$$.sock
trap '{ sexpect close && sexpect wait; } > /dev/null 2>&1' EXIT sexpect spawn sudo openvpn --config $vpn_conf
sexpect set -timeout # XXX: 'set' should be invoked after server is running while :; do
sexpect expect -nocase -re "Username:|Password:"
ret=$?
if (( $ret == )); then
out=$(sexpect expect_out)
if [[ $out == *"Username:"* ]]; then
sexpect send -enter "$vpn_user"
elif [[ $out == *"Password:"* ]]; then
sexpect send -enter "$vpn_password$vpn_token"
break
else
echo "*** unknown catch: $out" >&
exit
fi
elif sexpect chkerr -errno $ret -is eof; then
sexpect wait
exit
elif sexpect chkerr -errno $ret -is timeout; then
sexpect close
sexpect wait
echo "*** timeout waiting for username/password prompt" >&
exit
else
echo "*** unknown error: $ret" >&
exit
fi
done sexpect interact
  • 运行autovpn.sh
huanli@ThinkPadT460:~$ ./autovpn.sh
Sat Aug :: OpenVPN 2.4. x86_64-redhat-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr
Sat Aug :: library versions: OpenSSL 1.1.0h-fips Mar , LZO 2.08
Enter Auth Username: huanli
Enter Auth Password: ****************
Sat Aug :: NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
...<snip>...
Sat Aug :: GID set to openvpn
Sat Aug :: UID set to openvpn
Sat Aug :: Initialization Sequence Completed

[Python学习笔记-003] 使用PyOTP获取基于OTOP算法的动态口令的更多相关文章

  1. #Python学习笔记:1-3章 (基于《python编程,从入门到实践)

    第1-3章 这个文档是记录我学习python时一些学习笔记以及一些想法也可以称作复习笔记 第一章:起步这一章主要是从第一个"hello world"程序到python环境的搭建与配 ...

  2. Python学习笔记6-异常捕获取

    #--encoding:utf-8-- try: float('abc') except Exception,e: print e try: float(1.2) except Exception,e ...

  3. Python学习笔记003

    windows环境配置 系统变量: Path: D:\Program Files\Python35\Scripts\; D:\ProgramFiles\Python35\; D:\Program Fi ...

  4. python学习笔记之装饰器、递归、算法(第四天)

    参考老师的博客: 金角:http://www.cnblogs.com/alex3714/articles/5161349.html 银角:http://www.cnblogs.com/wupeiqi/ ...

  5. 20180821 Python学习笔记:如何获取当前程序路径

    20180821 Python学习笔记:如何获取当前程序路径 启动的脚本的路径为:D:\WORK\gitbase\ShenzhenHouseInfoCrawler\main.py 当前脚本的路径为:D ...

  6. python 学习笔记 12 -- 写一个脚本获取城市天气信息

    近期在玩树莓派,前面写过一篇在树莓派上使用1602液晶显示屏,那么可以显示后最重要的就是显示什么的问题了. 最easy想到的就是显示时间啊,CPU利用率啊.IP地址之类的.那么我认为呢,假设可以显示当 ...

  7. Python学习笔记,day5

    Python学习笔记,day5 一.time & datetime模块 import本质为将要导入的模块,先解释一遍 #_*_coding:utf-8_*_ __author__ = 'Ale ...

  8. Deep learning with Python 学习笔记(10)

    生成式深度学习 机器学习模型能够对图像.音乐和故事的统计潜在空间(latent space)进行学习,然后从这个空间中采样(sample),创造出与模型在训练数据中所见到的艺术作品具有相似特征的新作品 ...

  9. Deep learning with Python 学习笔记(1)

    深度学习基础 Python 的 Keras 库来学习手写数字分类,将手写数字的灰度图像(28 像素 ×28 像素)划分到 10 个类别 中(0~9) 神经网络的核心组件是层(layer),它是一种数据 ...

随机推荐

  1. _ZNote_编程语言_Qt_信号槽实现_拖拽方式使用控件

    所谓的信号槽,实际上就是观察者模式. 当某个事件发生后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal). 这种发出信号是没有目的的,类似于广播.如果对象对这个信号感兴趣,它就会使 ...

  2. 修改linux swap空间的swappiness,降低对硬盘的缓存

    linux 会使用硬盘的一部分做为SWAP分区,用来进行进程调度--进程是正在运行的程序--把当前不用的进程调成‘等待(standby)‘,甚至‘睡眠(sleep)’,一旦要用,再调成‘活动(acti ...

  3. MFC坐标系

    MFC坐标系分为设备坐标系和逻辑坐标系两种.在设备坐标系中,一个像素表示一个单位长度,设备的原点(0,0)始终在显示平面的左上角位置,x轴正向向右,y轴正向向下. 逻辑坐标系中,原点可以放在任何一个位 ...

  4. iOS cell左滑出现多个功能按钮(IOS8以后支持)

    #import "ViewController.h" #import "Swift_OC-Swift.h" @interface ViewController ...

  5. Linux下替代grep高效文本搜索工具

    1.ack yum install ack 2.ag git clone https://github.com/ggreer/the_silver_searcher.git yum install a ...

  6. C# byte和10进制、16进制相互转换

    var SRMP = new byte[4]; Array.Copy(Encoding.UTF8.GetBytes(1.ToString("x2")), SRMP, Encodin ...

  7. Python3.5 学习十九 Django分模块讲解 MTV+URL

    本节内容概述: 表单提交的Method使用规则:get 获取数据 post提交数据 单选使用get 多选使用getlist request.POST.getlist("favor" ...

  8. 【文文殿下】[CEOI2004]锯木厂选址 题解

    题解 我们枚举建厂的位置,发现有个\(n^2\)的DP.随手搞个斜率优化到\(O(n)\). #include<bits/stdc++.h> using namespace std; ty ...

  9. 《Python自动化运维之路》 系统基础信息模块(一)

    系统性能收集模块Psutil 目录: 系统性能信息模块psutil 系统性能信息模块psutil psutil能够轻松实现获取系统运行的进程和系统利用率包括(CPU,内存,磁盘 和网络)等.主要用于系 ...

  10. Oracle11g手动创建数据库方法

    Oracle11g手动创建数据库方法 参考网页http://www.th7.cn/db/Oracle/201311/36926.shtml 安装路径 我的安装路径是:E:\app\admin\prod ...