Last active
September 28, 2018 17:33
-
-
Save sr105/17910113360bdfd877f9c5d6bbb7e915 to your computer and use it in GitHub Desktop.
TOTP Authenticator Python Implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import time | |
import urllib.parse | |
import base64 | |
import hmac | |
import types | |
# https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm | |
# https://github.com/google/google-authenticator/wiki/Key-Uri-Format | |
# https://git.coolaj86.com/coolaj86/botp.js/src/branch/master/index.js | |
# https://authenticator.ppl.family/ | |
# https://git.coolaj86.com/coolaj86/browser-authenticator.js | |
# https://www.ietf.org/rfc/rfc6238.txt | |
def hotp_gen(options, counter): | |
"""Generate HOTP code for key and counter.""" | |
# key is stored Base32 encoded | |
# counter is converted to a 64-bit unsigned integer (MSB) | |
digest = hmac.new(key=base64.b32decode(options.secret), | |
msg=counter.to_bytes(8, 'big'), | |
digestmod=options.algorithm) \ | |
.digest() | |
# Take the last 4 bits of the digest and use it as a byte offset. | |
offset = digest[-1] & 0xf | |
# Make an unsigned 32-bit integer (MSB) from the 4 bytes at offset, | |
# masking the top bit. | |
v = int.from_bytes(digest[offset:offset + 4], 'big') & 0x7fffffff | |
# Return the last N base-10 digits | |
return str(v)[-options.digits:] | |
def hotp_verify(token, options, counter): | |
"""Returns time period offset if valid, else None""" | |
# 0, -1, 1, -2, 2, ... | |
for i in sum(([-i, i] for i in range(1, options.window + 1)), [0]): | |
if hotp_gen(options, counter + i) == token: | |
return i | |
return None | |
def totp_counter(options): | |
"""Return current number of time periods since the epoch.""" | |
return int(time.time()) // options.period | |
def totp_gen(options): | |
"""Returns HOTP code for key and current time period count.""" | |
return hotp_gen(options, totp_counter(options)) | |
def totp_verify(token, options, window=6): | |
"""Returns time period offset if valid, else None""" | |
return hotp_verify(token, options, totp_counter(options)) | |
def decode_otpauth_url(url): | |
"""Decodes OTP Auth URL into parameters.""" | |
pr = urllib.parse.urlparse(url) | |
options = dict(urllib.parse.parse_qsl(pr.query)) | |
options.setdefault('period', 30) | |
options.setdefault('digits', 6) | |
options.setdefault('window', 6) | |
options.setdefault('algorithm', 'sha1') | |
# options.setdefault('algorithm', 'sha256') | |
# options.setdefault('algorithm', 'sha512') | |
options['period'] = int(options['period']) | |
options['digits'] = int(options['digits']) | |
return types.SimpleNamespace(**options) | |
url = 'otpauth://totp/ACME%20Co:[email protected]?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30' | |
options = decode_otpauth_url(url) | |
z = totp_gen(options) | |
# time.sleep(45) | |
j = totp_verify(z, options) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment