forked from MicroPyramid/django-mfa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathotp.py
67 lines (59 loc) · 2.23 KB
/
otp.py
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
from __future__ import print_function, unicode_literals, division, absolute_import
import base64
import hashlib
import hmac
class OTP(object):
def __init__(self, s, digits=6, digest=hashlib.sha1):
"""
@param [String] secret in the form of base32
@option options digits [Integer] (6)
Number of integers in the OTP
Google Authenticate only supports 6 currently
@option options digest [Callable] (hashlib.sha1)
Digest used in the HMAC
Google Authenticate only supports 'sha1' currently
@returns [OTP] OTP instantiation
"""
self.digits = digits
self.digest = digest
self.secret = s
def generate_otp(self, input):
"""
@param [Integer] input the number used seed the HMAC
Usually either the counter, or the computed integer
based on the Unix timestamp
"""
hmac_hash = hmac.new(
self.byte_secret(),
self.int_to_bytestring(input),
self.digest,
).digest()
hmac_hash = bytearray(hmac_hash)
offset = hmac_hash[-1] & 0xf
code = ((hmac_hash[offset] & 0x7f) << 24 |
(hmac_hash[offset + 1] & 0xff) << 16 |
(hmac_hash[offset + 2] & 0xff) << 8 |
(hmac_hash[offset + 3] & 0xff))
str_code = str(code % 10 ** self.digits)
while len(str_code) < self.digits:
str_code = '0' + str_code
return str_code
def byte_secret(self):
missing_padding = len(self.secret) % 8
if missing_padding != 0:
self.secret += '=' * (8 - missing_padding)
return base64.b32decode(self.secret, casefold=True)
@staticmethod
def int_to_bytestring(i, padding=8):
"""
Turns an integer to the OATH specified
bytestring, which is fed to the HMAC
along with the secret
"""
result = bytearray()
while i != 0:
result.append(i & 0xFF)
i >>= 8
# It's necessary to convert the final result from bytearray to bytes because
# the hmac functions in python 2.6 and 3.3 don't work with bytearray
return bytes(bytearray(reversed(result)).rjust(padding, b'\0'))