[Python学习笔记-003] 使用PyOTP获取基于OTOP算法的动态口令
建立安全的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算法的动态口令的更多相关文章
- #Python学习笔记:1-3章 (基于《python编程,从入门到实践)
第1-3章 这个文档是记录我学习python时一些学习笔记以及一些想法也可以称作复习笔记 第一章:起步这一章主要是从第一个"hello world"程序到python环境的搭建与配 ...
- Python学习笔记6-异常捕获取
#--encoding:utf-8-- try: float('abc') except Exception,e: print e try: float(1.2) except Exception,e ...
- Python学习笔记003
windows环境配置 系统变量: Path: D:\Program Files\Python35\Scripts\; D:\ProgramFiles\Python35\; D:\Program Fi ...
- python学习笔记之装饰器、递归、算法(第四天)
参考老师的博客: 金角:http://www.cnblogs.com/alex3714/articles/5161349.html 银角:http://www.cnblogs.com/wupeiqi/ ...
- 20180821 Python学习笔记:如何获取当前程序路径
20180821 Python学习笔记:如何获取当前程序路径 启动的脚本的路径为:D:\WORK\gitbase\ShenzhenHouseInfoCrawler\main.py 当前脚本的路径为:D ...
- python 学习笔记 12 -- 写一个脚本获取城市天气信息
近期在玩树莓派,前面写过一篇在树莓派上使用1602液晶显示屏,那么可以显示后最重要的就是显示什么的问题了. 最easy想到的就是显示时间啊,CPU利用率啊.IP地址之类的.那么我认为呢,假设可以显示当 ...
- Python学习笔记,day5
Python学习笔记,day5 一.time & datetime模块 import本质为将要导入的模块,先解释一遍 #_*_coding:utf-8_*_ __author__ = 'Ale ...
- Deep learning with Python 学习笔记(10)
生成式深度学习 机器学习模型能够对图像.音乐和故事的统计潜在空间(latent space)进行学习,然后从这个空间中采样(sample),创造出与模型在训练数据中所见到的艺术作品具有相似特征的新作品 ...
- Deep learning with Python 学习笔记(1)
深度学习基础 Python 的 Keras 库来学习手写数字分类,将手写数字的灰度图像(28 像素 ×28 像素)划分到 10 个类别 中(0~9) 神经网络的核心组件是层(layer),它是一种数据 ...
随机推荐
- Codeforces816A Karen and Morning 2017-06-27 15:11 43人阅读 评论(0) 收藏
A. Karen and Morning time limit per test 2 seconds memory limit per test 512 megabytes input standar ...
- Python自动化开发 - AJAX
一 AJAX预备知识:json进阶 1.1 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. JSON是用字符串来表示Javascript对象 json ...
- 8.ajax与django后台json数据的交互
1新建django项目名为json_ajax,应用名为app,在templates模板中新建ajax.html文件 ajax.html <!DOCTYPE html> <html l ...
- 【BZOJ1053】 反素数ant
BZOJ1053 反素数ant 我们先考虑唯一分解定理求出约数个数: \(x=a_1^{p_1}a_2^{p_2}a_3^{p_3}...a_k^{p_k}\) 然后\(num=\Pi_{i=1}^k ...
- 基于tkinter的九型人格测试系统介绍
基于tkinter的九型人格测试系统介绍 一.程序代码地址,GitHub 二.程序介绍 1.login.py 登录界面: 注册界面: 2.mainWindow.py 登录成功之后的界面: 3.doTe ...
- 14_python 匿名函数,递归函数
一.匿名函数 语法: 函数名 = lambda 参数: 返回值 # lambda x,y,z=1:x+y+z 注意: 1.函数的参数可以有多个. 多个参数之间⽤逗号隔开 2.匿名函数不管多复杂 ...
- 设计模式《JAVA与模式》之备忘录模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述备忘录(Memento)模式的: 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式. 备忘录 ...
- .net core Error -4090 EADDRNOTAVAIL address not available”
问题原因:IP地址错误或者网络未开
- ES6之Array.from()方法
Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组. 那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象. 1.将类数组对象转换为真正数 ...
- iOS开发手记-iOS8中使用定位服务解决方案
问题描述: 在iOS8之前,app第一次开始定位服务时,系统会弹出一个提示框来让用户选择是否允许使用定位信息.但iOS8后,app将不会出现这个弹窗.第一次运行之后,在设置->隐私->定位 ...