最近很多云服务都开启了两步验证方式,其中使用基于 RFC 6238 标准的 TOTP(基于时间的一次性密码) 的服务非常多。当然标准都是开放的,也可以自己写一个玩玩啦~
查看基于 Python 的算法和 Google Authenticator 截图,请狂击:这里
验证客户端首选 Google Authenticator 咯,非常好用的客户端,效果见下:
P.S. 什么时候能支持 iPhone 5 的高分屏幕就更好了……
程序在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | #!/usr/bin/env python # Implementation demo of Google Authenticator in Python # Bill Haofei Gong @ billgong.com # Download Google Authenticator to your mobile: # iOS: https://itunes.apple.com/us/app/id388497605?mt=8 # Android: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en # Other mobile please refers to Google Code project: https://code.google.com/p/google-authenticator/ # * HOTP(RFC 4226) counter and 16-character secret is stored in file 'oauth.dat'. # * TOTP(RFC 6238) need to be synced with time. # * Secret is case SENSITIVE! # An example of 2-line preference file 'oauth.dat': # # MZXW633PN5XW6MZX # 6 # # Which will give HOTP token 581333 as it's 6th token of secret 'MZXW633PN5XW6MZX'. TOTP token is dynamic according to time. # Version History: # 12 April 2013 Friday: First version # P.S. HOTP integrity check value is... I don't know. I will figure out sometime later... import sys, time, base64, struct, hmac, hashlib # 从配置文件里读取密钥和 counter try: file = open("oauth.dat", "r").readlines() except (IOError): print 'Error: Cant\'t read preference file' exit() secret = file[0].rstrip('\n') # HOTP 使用16字符长的密钥,区分大小写 if len(secret) != 16: print 'Error: Preference: Illegal secret length!' exit() try: hotp_counter = int(file[1]) except (ValueError): print 'Error: Preference: Illegal counter of HOTP!' exit() # 精华在这里 def get_hotp_token(secret, interval): basedSecret = base64.b32decode(secret, True) structSecret = struct.pack(">Q", interval) hmacSecret = hmac.new(basedSecret, structSecret, hashlib.sha1).digest() ordSecret = ord(hmacSecret[19]) & 15 tokenSecret = (struct.unpack(">I", hmacSecret[ordSecret:ordSecret+4])[0] & 0x7fffffff) % 1000000 return tokenSecret # TOTP 就是使用时间作为 HOTP 的 interval def get_totp_token(secret): return get_hotp_token(secret, int(time.time())//30) # HOTP 每个密码用完后需要在 counter 加 1,并将 counter 写入配置文件 def increase_hotp_counter(): file[1] = str(hotp_counter + 1) try: open("oauth.dat", "w").writelines(file) except (IOError): print 'Error: Cant\'t write preference file' exit() def appinfo(): return "usage: totp.py [TOTP|HOTP] [TOKEN]\n\nSecret used: " + secret + "\n\nUse following URI to generate QRCode or open in Google Authenticator:\notpauth://hotp/Python:HOTP?secret=" + secret + "\notpauth://totp/Python:TOTP?secret=" + secret try: tokenType = sys.argv[1] tokenInput = int(sys.argv[2]) except (IndexError): print 'Error: Console: Insufficient parameters!' print appinfo() exit() except (ValueError): print 'Error: Console: Given token is illegal!' print appinfo() exit() # 当 token 作为 string 输出时,记得 padding 至 6 位数字 if tokenType in ('hotp', 'HOTP'): hotp_token = get_hotp_token(secret, hotp_counter) if hotp_token == tokenInput: print 'HOTP token is correct!\nCurrent token will become invalid.' increase_hotp_counter() else: print 'HOTP token is invalid!\nToken should be ' + str(hotp_token).zfill(6) elif tokenType in ('totp', 'TOTP'): totp_token = get_totp_token(secret) if totp_token == tokenInput: print 'TOTP token is correct!' else: print 'TOTP token is invalid!\nToken should be ' + str(totp_token).zfill(6) else: print 'Error: Console: Illegal token type!' print appinfo() |
运行前需要一个配置文件,命名为“oauth.dat”,放置在同一目录下,例如:
1 2 | MZXW633PN5XW6MZX 10 |
格式为:第一行是密钥,第二行是 HOTP counter
运行示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | $ ./oauth.py Error: Console: Insufficient parameters! usage: totp.py [TOTP|HOTP] [TOKEN] Secret used: MZXW633PN5XW6MZX Use following URI to generate QRCode or open in Google Authenticator: otpauth://hotp/Python:HOTP?secret=MZXW633PN5XW6MZX otpauth://totp/Python:TOTP?secret=MZXW633PN5XW6MZX # TOTP with correct token $ ./oauth.py totp 879472 TOTP token is correct! # TOTP with incorrect token $ ./oauth.py totp 123123 TOTP token is invalid! Token should be 879472 # HOTP with incorrect token $ ./oauth.py hotp 123123 HOTP token is invalid! Token should be 171710 # HOTP with correct token $ ./oauth.py hotp 171710 HOTP token is correct! Current token will become invalid. # HOTP with used token $ ./oauth.py hotp 171710 HOTP token is invalid! Token should be 930616 |
Google Authenticator 可以使用 QRCode 添加密钥,下面的 QRCode 含有 TOTP,密钥为 AAAABBBBCCCCDDDD 的 URI:
最后吐槽一句:这东西怎么放 Java 下面写就那么麻烦……看来我还是太笨了
2 Comments
请问有客户端实现吗……
算法都是公开的,详情见对应的RFC文档,Port到你自己的Project里面就行了。