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

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

PyOTP是一套开源的函数库,可用来计算基于OTOP算法的Token。有关OTOP算法,这里不做介绍,请参考这里

1. 下载PyOTP

huanli@ThinkPadT460:tmp$ git clone https://github.com/pyotp/pyotp.git
Cloning into 'pyotp'...
remote: Counting objects: 601, done.
remote: Total 601 (delta 0), reused 0 (delta 0), pack-reused 601
Receiving objects: 100% (601/601), 165.02 KiB | 207.00 KiB/s, done.
Resolving deltas: 100% (297/297), 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

1 directory, 6 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非常容易。下面给出完整的Python脚本:

  • vpn_token.py
 1 #!/usr/bin/python
 2 import sys
 3 import datetime
 4 import time
 5 
 6 def main(argc, argv):
 7     if argc != 3:
 8         sys.stderr.write("Usage: %s <token secret> <pyotp path>\n" % argv[0])
 9         return 1
10 
11     token_secret = argv[1]
12     pyotp_path = argv[2]
13 
14     sys.path.append(pyotp_path)
15     import pyotp
16     totp = pyotp.TOTP(token_secret)
17 
18     #
19     # The token is expected to be valid in 5 seconds,
20     # else sleep 5s and retry
21     #
22     while True:
23         tw = datetime.datetime.now() + datetime.timedelta(seconds=5)
24         token = totp.now()
25         if totp.verify(token, tw):
26             print "%s" % token
27             return 0
28         time.sleep(5)
29 
30     return 1
31 
32 if __name__ == '__main__':
33     sys.exit(main(len(sys.argv), sys.argv))
  • 来自Terminal的Token  : 797907

  • 来自手机的Token

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

  • autovpn.sh
  1 #!/bin/bash
  2 
  3 #
  4 # This script is to connect VPN without manually inputting password + token.
  5 #
  6 # Note it requires two utilities, PyOTP [1] and sexpect [2].
  7 #
  8 # [1] PyOTP is a Python Python library for generating and verifying one-time
  9 #     passwords. Here is an example to use it,
 10 #
 11 #     $ git clone https://github.com/pyotp/pyotp.git /tmp/pyotp
 12 #     $
 13 #     $ export PYTHONPATH=/tmp/pyotp/src:$PYTHONPATH
 14 #     $ python
 15 #     >>> import pyotp
 16 #     >>> import base64
 17 #     >>> token_secret = base64.b32encode('Hello world')
 18 #     >>> totp = pyotp.TOTP(token_secret)
 19 #     >>> token = totp.now()
 20 #     >>> print token
 21 #     265040
 22 #     >>>
 23 #
 24 # [2] sexpect is another Expect implementation designed in the client/server
 25 #     model which also supports attach/detach (like GNU screen).
 26 #     To use it in bash, you have to build it on your system, e.g.
 27 #
 28 #     $ git clone https://github.com/clarkwang/sexpect.git /tmp/sexpect
 29 #     $ cd /tmp/sexpect
 30 #     $ make
 31 #     $ export PATH=/tmp/sexpect:$PATH
 32 #     $ which sexpect
 33 #     /tmp/sexpect/sexpect
 34 #
 35 # And the environment variables in the following should be set in your bashrc
 36 # as well,
 37 #
 38 #     o VPN_CONF,           e.g. /etc/vpn/ovpn-pek2-tcp.conf
 39 #     o VPN_PASSWORD or VPN_PASSWORD_HOOK,
 40 #                           e.g. '123456789'
 41 #                             or ~/.vpn/passwd_hook
 42 #     o VPN_PYOTP_PATH,     e.g. /tmp/pyotp/src
 43 #     o VPN_TOKEN_SECRET or VPN_TOKEN_SECRET_HOOK,
 44 #                           e.g. 'CDOrzXyzOrzXyzOrzXyzOrzXyzOrzXyz'
 45 #                             or ~/.vpn/tokensec_hook
 46 #
 47 
 48 function get_vpn_token
 49 {
 50         typeset f_py_cb=/tmp/.vpn_token.py
 51         cat > $f_py_cb << EOF
 52 #!/usr/bin/python
 53 import sys
 54 import datetime
 55 import time
 56 
 57 def main(argc, argv):
 58     if argc != 3:
 59         sys.stderr.write("Usage: %s <token secret> <pyotp path>\\n" % argv[0])
 60         return 1
 61 
 62     token_secret = argv[1]
 63     pyotp_path = argv[2]
 64 
 65     sys.path.append(pyotp_path)
 66     import pyotp
 67     totp = pyotp.TOTP(token_secret)
 68 
 69     #
 70     # The token is expected to be valid in 5 seconds,
 71     # else sleep 5s and retry
 72     #
 73     while True:
 74         tw = datetime.datetime.now() + datetime.timedelta(seconds=5)
 75         token = totp.now()
 76         if totp.verify(token, tw):
 77             print "%s" % token
 78             return 0
 79         time.sleep(5)
 80 
 81     return 1
 82 
 83 if __name__ == '__main__':
 84     argv = sys.argv
 85     argc = len(argv)
 86     sys.exit(main(argc, argv))
 87 EOF
 88 
 89         typeset pyotp_path=$VPN_PYOTP_PATH
 90         typeset token_secret=$VPN_TOKEN_SECRET
 91         if [[ -z $token_secret ]]; then
 92                 token_secret=$(eval $($VPN_TOKEN_SECRET_HOOK))
 93         fi
 94 
 95         python $f_py_cb $token_secret $pyotp_path
 96         typeset ret=$?
 97         rm -f $f_py_cb
 98         return $ret
 99 }
100 
101 function get_vpn_user
102 {
103         typeset user=${VPN_USER:-"$(id -un)"}
104         echo "$user"
105 }
106 
107 function get_vpn_password
108 {
109         typeset token=$1
110         typeset password=${VPN_PASSWORD:-"$(eval $($VPN_PASSWORD_HOOK))"}
111         echo "$password$token"
112 }
113 
114 function get_vpn_conf
115 {
116         typeset conf=$VPN_CONF
117         echo "$conf"
118 }
119 
120 vpn_token=$(get_vpn_token)
121 vpn_user=$(get_vpn_user)
122 vpn_password=$(get_vpn_password $vpn_token)
123 vpn_conf=$(get_vpn_conf)
124 
125 export SEXPECT_SOCKFILE=/tmp/sexpect-ssh-$$.sock
126 trap '{ sexpect close && sexpect wait; } > /dev/null 2>&1' EXIT
127 
128 sexpect spawn sudo openvpn --config $vpn_conf
129 sexpect set -timeout 60 # XXX: 'set' should be invoked after server is running
130 
131 while :; do
132         sexpect expect -nocase -re "Username:|Password:"
133         ret=$?
134         if (( $ret == 0 )); then
135                 out=$(sexpect expect_out)
136                 if [[ $out == *"Username:"* ]]; then
137                         sexpect send -enter "$vpn_user"
138                 elif [[ $out == *"Password:"* ]]; then
139                         sexpect send -enter "$vpn_password"
140                         break
141                 else
142                         echo "*** unknown catch: $out" >&2
143                         exit 1
144                 fi
145         elif sexpect chkerr -errno $ret -is eof; then
146                 sexpect wait
147                 exit 0
148         elif sexpect chkerr -errno $ret -is timeout; then
149                 sexpect close
150                 sexpect wait
151                 echo "*** timeout waiting for password prompt" >&2
152                 exit 1
153         else
154                 echo "*** unknown error: $ret" >&2
155                 exit 1
156         fi
157 done
158 
159 sexpect interact
  • 运行autovpn.sh
huanli@ThinkPadT460:~$ ./autovpn.sh 
Sat Aug 11 22:32:17 2018 OpenVPN 2.4.6 x86_64-redhat-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr 26 2018
Sat Aug 11 22:32:17 2018 library versions: OpenSSL 1.1.0h-fips  27 Mar 2018, LZO 2.08
Enter Auth Username: huanli
Enter Auth Password: ****************
Sat Aug 11 22:32:17 2018 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
...<snip>...
Sat Aug 11 22:32:20 2018 GID set to openvpn
Sat Aug 11 22:32:20 2018 UID set to openvpn
Sat Aug 11 22:32:20 2018 Initialization Sequence Completed

猜你喜欢

转载自www.cnblogs.com/idorax/p/9461175.html