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