hotp.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. from __future__ import absolute_import, division, print_function
  5. import struct
  6. import six
  7. from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
  8. from cryptography.hazmat.backends import _get_backend
  9. from cryptography.hazmat.backends.interfaces import HMACBackend
  10. from cryptography.hazmat.primitives import constant_time, hmac
  11. from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512
  12. from cryptography.hazmat.primitives.twofactor import InvalidToken
  13. from cryptography.hazmat.primitives.twofactor.utils import _generate_uri
  14. class HOTP(object):
  15. def __init__(
  16. self, key, length, algorithm, backend=None, enforce_key_length=True
  17. ):
  18. backend = _get_backend(backend)
  19. if not isinstance(backend, HMACBackend):
  20. raise UnsupportedAlgorithm(
  21. "Backend object does not implement HMACBackend.",
  22. _Reasons.BACKEND_MISSING_INTERFACE,
  23. )
  24. if len(key) < 16 and enforce_key_length is True:
  25. raise ValueError("Key length has to be at least 128 bits.")
  26. if not isinstance(length, six.integer_types):
  27. raise TypeError("Length parameter must be an integer type.")
  28. if length < 6 or length > 8:
  29. raise ValueError("Length of HOTP has to be between 6 to 8.")
  30. if not isinstance(algorithm, (SHA1, SHA256, SHA512)):
  31. raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.")
  32. self._key = key
  33. self._length = length
  34. self._algorithm = algorithm
  35. self._backend = backend
  36. def generate(self, counter):
  37. truncated_value = self._dynamic_truncate(counter)
  38. hotp = truncated_value % (10 ** self._length)
  39. return "{0:0{1}}".format(hotp, self._length).encode()
  40. def verify(self, hotp, counter):
  41. if not constant_time.bytes_eq(self.generate(counter), hotp):
  42. raise InvalidToken("Supplied HOTP value does not match.")
  43. def _dynamic_truncate(self, counter):
  44. ctx = hmac.HMAC(self._key, self._algorithm, self._backend)
  45. ctx.update(struct.pack(">Q", counter))
  46. hmac_value = ctx.finalize()
  47. offset = six.indexbytes(hmac_value, len(hmac_value) - 1) & 0b1111
  48. p = hmac_value[offset : offset + 4]
  49. return struct.unpack(">I", p)[0] & 0x7FFFFFFF
  50. def get_provisioning_uri(self, account_name, counter, issuer):
  51. return _generate_uri(
  52. self, "hotp", account_name, issuer, [("counter", int(counter))]
  53. )