hkdf.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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. import typing
  5. from cryptography import utils
  6. from cryptography.exceptions import (
  7. AlreadyFinalized,
  8. InvalidKey,
  9. UnsupportedAlgorithm,
  10. _Reasons,
  11. )
  12. from cryptography.hazmat.backends import _get_backend
  13. from cryptography.hazmat.backends.interfaces import Backend, HMACBackend
  14. from cryptography.hazmat.primitives import constant_time, hashes, hmac
  15. from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
  16. class HKDF(KeyDerivationFunction):
  17. def __init__(
  18. self,
  19. algorithm: hashes.HashAlgorithm,
  20. length: int,
  21. salt: typing.Optional[bytes],
  22. info: typing.Optional[bytes],
  23. backend: typing.Optional[Backend] = None,
  24. ):
  25. backend = _get_backend(backend)
  26. if not isinstance(backend, HMACBackend):
  27. raise UnsupportedAlgorithm(
  28. "Backend object does not implement HMACBackend.",
  29. _Reasons.BACKEND_MISSING_INTERFACE,
  30. )
  31. self._algorithm = algorithm
  32. if salt is None:
  33. salt = b"\x00" * self._algorithm.digest_size
  34. else:
  35. utils._check_bytes("salt", salt)
  36. self._salt = salt
  37. self._backend = backend
  38. self._hkdf_expand = HKDFExpand(self._algorithm, length, info, backend)
  39. def _extract(self, key_material: bytes) -> bytes:
  40. h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend)
  41. h.update(key_material)
  42. return h.finalize()
  43. def derive(self, key_material: bytes) -> bytes:
  44. utils._check_byteslike("key_material", key_material)
  45. return self._hkdf_expand.derive(self._extract(key_material))
  46. def verify(self, key_material: bytes, expected_key: bytes) -> None:
  47. if not constant_time.bytes_eq(self.derive(key_material), expected_key):
  48. raise InvalidKey
  49. class HKDFExpand(KeyDerivationFunction):
  50. def __init__(
  51. self,
  52. algorithm: hashes.HashAlgorithm,
  53. length: int,
  54. info: typing.Optional[bytes],
  55. backend: typing.Optional[Backend] = None,
  56. ):
  57. backend = _get_backend(backend)
  58. if not isinstance(backend, HMACBackend):
  59. raise UnsupportedAlgorithm(
  60. "Backend object does not implement HMACBackend.",
  61. _Reasons.BACKEND_MISSING_INTERFACE,
  62. )
  63. self._algorithm = algorithm
  64. self._backend = backend
  65. max_length = 255 * algorithm.digest_size
  66. if length > max_length:
  67. raise ValueError(
  68. "Cannot derive keys larger than {} octets.".format(max_length)
  69. )
  70. self._length = length
  71. if info is None:
  72. info = b""
  73. else:
  74. utils._check_bytes("info", info)
  75. self._info = info
  76. self._used = False
  77. def _expand(self, key_material: bytes) -> bytes:
  78. output = [b""]
  79. counter = 1
  80. while self._algorithm.digest_size * (len(output) - 1) < self._length:
  81. h = hmac.HMAC(key_material, self._algorithm, backend=self._backend)
  82. h.update(output[-1])
  83. h.update(self._info)
  84. h.update(bytes([counter]))
  85. output.append(h.finalize())
  86. counter += 1
  87. return b"".join(output)[: self._length]
  88. def derive(self, key_material: bytes) -> bytes:
  89. utils._check_byteslike("key_material", key_material)
  90. if self._used:
  91. raise AlreadyFinalized
  92. self._used = True
  93. return self._expand(key_material)
  94. def verify(self, key_material: bytes, expected_key: bytes) -> None:
  95. if not constant_time.bytes_eq(self.derive(key_material), expected_key):
  96. raise InvalidKey