1+ import base64
2+ import secrets
3+ import hmac
4+ import hashlib
5+ import struct
6+ import time
7+ import urllib .parse
8+
9+
10+ def generate_base32_secret (byte_length = 20 ):
11+ random_bytes = secrets .token_bytes (byte_length )
12+ secret = base64 .b32encode (random_bytes ).decode ("utf-8" )
13+ return secret .rstrip ("=" )
14+
15+
16+ def build_otpauth_uri (secret , account_name , issuer_name ):
17+ label = issuer_name + ":" + account_name
18+
19+ params = {
20+ "secret" : secret ,
21+ "issuer" : issuer_name ,
22+ "algorithm" : "SHA1" ,
23+ "digits" : "6" ,
24+ "period" : "30"
25+ }
26+
27+ uri = "otpauth://totp/" + urllib .parse .quote (label )
28+ uri += "?" + urllib .parse .urlencode (params )
29+ return uri
30+
31+
32+ def base32_decode_no_padding (secret ):
33+ secret = secret .strip ().replace (" " , "" ).upper ()
34+ missing_padding = len (secret ) % 8
35+
36+ if missing_padding :
37+ secret += "=" * (8 - missing_padding )
38+
39+ return base64 .b32decode (secret )
40+
41+
42+ def generate_totp (secret , interval = 30 , digits = 6 ):
43+ key = base32_decode_no_padding (secret )
44+ counter = int (time .time () // interval )
45+ counter_bytes = struct .pack (">Q" , counter )
46+
47+ hmac_hash = hmac .new (
48+ key ,
49+ counter_bytes ,
50+ hashlib .sha1
51+ ).digest ()
52+
53+ offset = hmac_hash [- 1 ] & 0x0F
54+
55+ code_int = struct .unpack (">I" , hmac_hash [offset :offset + 4 ])[0 ]
56+ code_int = code_int & 0x7FFFFFFF
57+
58+ code = code_int % (10 ** digits )
59+ return str (code ).zfill (digits )
60+
61+
62+ def verify_totp (secret , user_code , interval = 30 , digits = 6 , window = 1 ):
63+ current_time = int (time .time ())
64+ key = base32_decode_no_padding (secret )
65+
66+ for offset in range (- window , window + 1 ):
67+ test_time = current_time + offset * interval
68+ counter = int (test_time // interval )
69+ counter_bytes = struct .pack (">Q" , counter )
70+
71+ hmac_hash = hmac .new (
72+ key ,
73+ counter_bytes ,
74+ hashlib .sha1
75+ ).digest ()
76+
77+ dynamic_offset = hmac_hash [- 1 ] & 0x0F
78+
79+ code_int = struct .unpack (">I" , hmac_hash [dynamic_offset :dynamic_offset + 4 ])[0 ]
80+ code_int = code_int & 0x7FFFFFFF
81+
82+ expected_code = str (code_int % (10 ** digits )).zfill (digits )
83+
84+ if expected_code == user_code :
85+ return True
86+
87+ return False
88+
89+
90+ def main ():
91+ print ("======================================" )
92+ print (" Google Authenticator 密钥生成器" )
93+ print (" 手机 Python IDE 纯 Python 版" )
94+ print ("======================================" )
95+
96+ issuer_name = input ("请输入服务名称,例如 MyApp:" ).strip ()
97+ account_name = input ("请输入账号名称,例如 ding@example.com:" ).strip ()
98+
99+ if issuer_name == "" :
100+ issuer_name = "MyApp"
101+
102+ if account_name == "" :
103+ account_name = "user@example.com"
104+
105+ secret = generate_base32_secret ()
106+ uri = build_otpauth_uri (secret , account_name , issuer_name )
107+
108+ print ("" )
109+ print ("生成完成:" )
110+ print ("--------------------------------------" )
111+ print ("服务名称:" , issuer_name )
112+ print ("账号名称:" , account_name )
113+ print ("TOTP 密钥:" )
114+ print (secret )
115+ print ("--------------------------------------" )
116+ print ("Google Authenticator 链接:" )
117+ print (uri )
118+ print ("--------------------------------------" )
119+
120+ print ("" )
121+ print ("使用方法:" )
122+ print ("1. 打开 Google Authenticator" )
123+ print ("2. 选择添加代码" )
124+ print ("3. 选择输入设置密钥" )
125+ print ("4. 账号名称填写上面的账号名称" )
126+ print ("5. 密钥填写上面的 TOTP 密钥" )
127+ print ("6. 密钥类型选择基于时间" )
128+
129+ current_code = generate_totp (secret )
130+ print ("" )
131+ print ("当前这台设备根据密钥算出的 6 位验证码是:" )
132+ print (current_code )
133+ print ("这个数字应该和 Google Authenticator 里显示的数字一致。" )
134+
135+ while True :
136+ print ("" )
137+ user_code = input ("输入 Google Authenticator 当前显示的 6 位验证码测试,直接回车退出:" ).strip ()
138+
139+ if user_code == "" :
140+ print ("已退出。请妥善保存密钥。" )
141+ break
142+
143+ if not user_code .isdigit () or len (user_code ) != 6 :
144+ print ("请输入 6 位数字。" )
145+ continue
146+
147+ if verify_totp (secret , user_code ):
148+ print ("验证成功,这个密钥可以正常使用。" )
149+ break
150+ else :
151+ print ("验证失败。请检查手机时间是否准确,或等待下一个 30 秒验证码。" )
152+
153+ print ("" )
154+ print ("重要提醒:" )
155+ print ("TOTP 密钥不要发给别人。" )
156+ print ("谁拿到密钥,谁就能生成同样的验证码。" )
157+
158+
159+ main ()
0 commit comments