/ python-oneliners / TOTP / totp.py
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)]))