建立安全的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. android sqlite 数据唯一性

    sqlite在遇到数据重复的时候要做判断在插入是不是有点太麻烦了?一个好的数据库设计就可以搞定了. 当要控制唯一性的数据是主键的时候可以设置 CONSTRAINT [] PRIMARY KEY ([Q ...

  2. c#运用反射获取属性和设置属性值

    /// <summary> /// 获取类中的属性值 /// </summary> /// <param name="FieldName">&l ...

  3. spring boot学习笔记2

    开场知识: spring 容器注入bean,时容器初始化的一些接口以及接口调用的时间先后顺序: 1)BeanFactoryPostProcessor 容器初始化的回调方法 * BeanFactoryP ...

  4. 《principal component analysis based cataract grading and classification》学习笔记

    Abstract A cataract is lens opacification caused by protein denaturation which leads to a decrease i ...

  5. [auto-download-app] 如何使用 javascript 实现 app 自动下载

    // 在访客跳转进入的页面中,执行下面使用下面 invoke function (function(){ var str_downloadUrl = "<!--{$apkUrl}--& ...

  6. cnn公式推导

    CNN公式推导 1 前言 在看此blog之前,请确保已经看懂我的前两篇blog[深度学习笔记1(卷积神经网络)]和[BP算法与公式推导].并且已经看过文献[1]的论文[Notes on Convolu ...

  7. Entity Framework Core 生成跟踪列-阴影属性

    摘自:https://www.cnblogs.com/tdfblog/p/entity-framework-core-generate-tracking-columns.html Ef Core 官方 ...

  8. 背水一战 Windows 10 (59) - 控件(媒体类): Image, MediaElement

    [源码下载] 背水一战 Windows 10 (59) - 控件(媒体类): Image, MediaElement 作者:webabcd 介绍背水一战 Windows 10 之 控件(媒体类) Im ...

  9. 27_网络编程-初识socket

    一.C/S B/S 架构         1.定义             (1)C/S结构,即Client/Server(客户机/服务器)结构,是大家熟知的软件系统体系结构,通过将任务合理分配到Cl ...

  10. Java Listener中Spring接口注入的使用

    在项目中使用Spring通常使用他的依赖注入可以很好的处理,接口与实现类之间的耦合性,但是通常的应用场景中都是Service层和DAO层,或者web层的话, 也是与Strust2来整合,那么如何在Li ...