totp.py 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import hmac
  4. import struct
  5. import time
  6. # 160 bits, as recommended by HOTP RFC 4226, section 4, R6.
  7. # Google Auth uses 80 bits by default but supports 160.
  8. TOTP_SECRET_SIZE = 160
  9. # The algorithm (and key URI format) allows customising these parameters but
  10. # google authenticator doesn't support it
  11. # https://github.com/google/google-authenticator/wiki/Key-Uri-Format
  12. ALGORITHM = 'sha1'
  13. DIGITS = 6
  14. TIMESTEP = 30
  15. class TOTP:
  16. def __init__(self, key):
  17. self._key = key
  18. def match(self, code, t=None, window=TIMESTEP, timestep=TIMESTEP):
  19. """
  20. :param code: authenticator code to check against this key
  21. :param int t: current timestamp (seconds)
  22. :param int window: fuzz window to account for slow fingers, network
  23. latency, desynchronised clocks, ..., every code
  24. valid between t-window an t+window is considered
  25. valid
  26. """
  27. if t is None:
  28. t = time.time()
  29. low = int((t - window) / timestep)
  30. high = int((t + window) / timestep) + 1
  31. return next((
  32. counter for counter in range(low, high)
  33. if hotp(self._key, counter) == code
  34. ), None)
  35. def hotp(secret, counter):
  36. # C is the 64b counter encoded in big-endian
  37. C = struct.pack(">Q", counter)
  38. mac = hmac.new(secret, msg=C, digestmod=ALGORITHM).digest()
  39. # the data offset is the last nibble of the hash
  40. offset = mac[-1] & 0xF
  41. # code is the 4 bytes at the offset interpreted as a 31b big-endian uint
  42. # (31b to avoid sign concerns). This effectively limits digits to 9 and
  43. # hard-limits it to 10: each digit is normally worth 3.32 bits but the
  44. # 10th is only worth 1.1 (9 digits encode 29.9 bits).
  45. code = struct.unpack_from('>I', mac, offset)[0] & 0x7FFFFFFF
  46. r = code % (10 ** DIGITS)
  47. # NOTE: use text / bytes instead of int?
  48. return r