| from __future__ import absolute_import, division, print_function, unicode_literals |
|
|
| import base64 |
| import hashlib |
| import hmac |
| from .compat import str |
|
|
| class OTP(object): |
| """ |
| Base class for OTP handlers. |
| """ |
| def __init__(self, s, digits=6, digest=hashlib.sha1): |
| """ |
| :param s: secret in base32 format |
| :type s: str |
| :param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more. |
| :type digits: int |
| :param digest: digest function to use in the HMAC (expected to be sha1) |
| :type digest: callable |
| """ |
| self.digits = digits |
| self.digest = digest |
| self.secret = s |
|
|
| def generate_otp(self, input): |
| """ |
| :param input: the HMAC counter value to use as the OTP input. |
| Usually either the counter, or the computed integer based on the Unix timestamp |
| :type input: int |
| """ |
| if input < 0: |
| raise ValueError('input must be positive integer') |
| hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest) |
| hmac_hash = bytearray(hasher.digest()) |
| 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 |
| |
| |
| |
| return bytes(bytearray(reversed(result)).rjust(padding, b'\0')) |
|
|