summaryrefslogtreecommitdiffstats
path: root/libpwman/otp.py
blob: 17907f06c807829abb0c32116d3209ad70243ca8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# -*- coding: utf-8 -*-
"""
# HOTP/TOTP support
# Copyright (c) 2019 Michael Buesch <m@bues.ch>
# Licensed under the GNU/GPL version 2 or later.
"""

import time
from base64 import b32decode
import binascii
import hmac
import hashlib

__all__ = [
	"OtpError",
	"hotp",
	"totp",
]

class OtpError(Exception):
	pass

def hotp(key, counter, nrDigits=6, hmacHash="SHA1"):
	"""HOTP: An HMAC-Based One-Time Password Algorithm.
	"""
	if isinstance(key, str):
		try:
			key = b32decode(key.encode("UTF-8"), casefold=True)
		except (binascii.Error, UnicodeError):
			raise OtpError("Invalid key.")
	if not (0 <= counter <= (2 ** 64) - 1):
		raise OtpError("Invalid counter.")
	if not (1 <= nrDigits <= 8):
		raise OtpError("Invalid number of digits.")
	try:
		{
			"SHA1"   : hashlib.sha1,
			"SHA256" : hashlib.sha256,
			"SHA512" : hashlib.sha512,
		}[hmacHash.upper().strip()]
	except KeyError:
		raise OtpError("Invalid HMAC hash type.")

	counter = bytes(((counter >> 56) & 0xFF,
			 (counter >> 48) & 0xFF,
			 (counter >> 40) & 0xFF,
			 (counter >> 32) & 0xFF,
			 (counter >> 24) & 0xFF,
			 (counter >> 16) & 0xFF,
			 (counter >> 8) & 0xFF,
			 (counter >> 0) & 0xFF))
	h = hmac.new(key, counter, hmacHash).digest()
	offset = h[19] & 0xF
	hSlice = (((h[offset + 0] & 0x7F) << 24) |
		  ((h[offset + 1] & 0xFF) << 16) |
		  ((h[offset + 2] & 0xFF) << 8) |
		  ((h[offset + 3] & 0xFF) << 0))
	otp = hSlice % (10 ** nrDigits)
	fmt = "%0" + str(nrDigits) + "d"
	return fmt % otp

def totp(key, nrDigits=6, hmacHash="SHA1", t=None):
	"""TOTP: Time-Based One-Time Password Algorithm.
	"""
	if t is None:
		t = time.time()
	t = (int(round(t)) // 30) - 1
	return hotp(key, t, nrDigits, hmacHash)
bues.ch cgit interface