57 lines
2.0 KiB
Python
57 lines
2.0 KiB
Python
|
# -*- 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
|