totp.py
1 import base64 2 import hashlib 3 import hmac 4 import random 5 import time 6 7 8 class TOTP: 9 def __init__(self, secret: str): 10 self.secret = secret 11 12 def now(self) -> str: 13 return self.at(int(time.time() / 30)) 14 15 def at(self, for_time: int) -> str: 16 secret = base64.b32decode(self.secret) 17 hmac_hash = hmac.HMAC(secret, for_time.to_bytes(8, 'big'), hashlib.sha1).digest() 18 offset = hmac_hash[-1] & 0xf 19 code = ((hmac_hash[offset] & 0x7f) << 24 | 20 (hmac_hash[offset + 1] & 0xff) << 16 | 21 (hmac_hash[offset + 2] & 0xff) << 8 | 22 (hmac_hash[offset + 3] & 0xff)) 23 out = str(code % 10 ** 6) 24 return "0" * (6 - len(out)) + out 25 26 def verify(self, code: str, valid_window: int = 0) -> bool: 27 for_time = int(time.time() / 30) 28 for i in range(-valid_window, valid_window + 1): 29 if self.at(for_time + i) == code.replace(" ", ""): 30 return True 31 return False 32 33 def generate_uri(self, user: str, issuer: str) -> str: 34 return "otpauth://totp/%s?secret=%s&issuer=%s" % (user, self.secret.lower(), issuer) 35 36 @staticmethod 37 def generate_random(): 38 return TOTP("".join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567") for _ in range(16)]))