padding.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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 abc
  5. import typing
  6. from cryptography import utils
  7. from cryptography.exceptions import AlreadyFinalized
  8. from cryptography.hazmat.bindings._rust import (
  9. check_ansix923_padding,
  10. check_pkcs7_padding,
  11. )
  12. class PaddingContext(metaclass=abc.ABCMeta):
  13. @abc.abstractmethod
  14. def update(self, data: bytes) -> bytes:
  15. """
  16. Pads the provided bytes and returns any available data as bytes.
  17. """
  18. @abc.abstractmethod
  19. def finalize(self) -> bytes:
  20. """
  21. Finalize the padding, returns bytes.
  22. """
  23. def _byte_padding_check(block_size: int) -> None:
  24. if not (0 <= block_size <= 2040):
  25. raise ValueError("block_size must be in range(0, 2041).")
  26. if block_size % 8 != 0:
  27. raise ValueError("block_size must be a multiple of 8.")
  28. def _byte_padding_update(
  29. buffer_: typing.Optional[bytes], data: bytes, block_size: int
  30. ) -> typing.Tuple[bytes, bytes]:
  31. if buffer_ is None:
  32. raise AlreadyFinalized("Context was already finalized.")
  33. utils._check_byteslike("data", data)
  34. buffer_ += bytes(data)
  35. finished_blocks = len(buffer_) // (block_size // 8)
  36. result = buffer_[: finished_blocks * (block_size // 8)]
  37. buffer_ = buffer_[finished_blocks * (block_size // 8) :]
  38. return buffer_, result
  39. def _byte_padding_pad(
  40. buffer_: typing.Optional[bytes],
  41. block_size: int,
  42. paddingfn: typing.Callable[[int], bytes],
  43. ) -> bytes:
  44. if buffer_ is None:
  45. raise AlreadyFinalized("Context was already finalized.")
  46. pad_size = block_size // 8 - len(buffer_)
  47. return buffer_ + paddingfn(pad_size)
  48. def _byte_unpadding_update(
  49. buffer_: typing.Optional[bytes], data: bytes, block_size: int
  50. ) -> typing.Tuple[bytes, bytes]:
  51. if buffer_ is None:
  52. raise AlreadyFinalized("Context was already finalized.")
  53. utils._check_byteslike("data", data)
  54. buffer_ += bytes(data)
  55. finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0)
  56. result = buffer_[: finished_blocks * (block_size // 8)]
  57. buffer_ = buffer_[finished_blocks * (block_size // 8) :]
  58. return buffer_, result
  59. def _byte_unpadding_check(
  60. buffer_: typing.Optional[bytes],
  61. block_size: int,
  62. checkfn: typing.Callable[[bytes], int],
  63. ) -> bytes:
  64. if buffer_ is None:
  65. raise AlreadyFinalized("Context was already finalized.")
  66. if len(buffer_) != block_size // 8:
  67. raise ValueError("Invalid padding bytes.")
  68. valid = checkfn(buffer_)
  69. if not valid:
  70. raise ValueError("Invalid padding bytes.")
  71. pad_size = buffer_[-1]
  72. return buffer_[:-pad_size]
  73. class PKCS7:
  74. def __init__(self, block_size: int):
  75. _byte_padding_check(block_size)
  76. self.block_size = block_size
  77. def padder(self) -> PaddingContext:
  78. return _PKCS7PaddingContext(self.block_size)
  79. def unpadder(self) -> PaddingContext:
  80. return _PKCS7UnpaddingContext(self.block_size)
  81. class _PKCS7PaddingContext(PaddingContext):
  82. _buffer: typing.Optional[bytes]
  83. def __init__(self, block_size: int):
  84. self.block_size = block_size
  85. # TODO: more copies than necessary, we should use zero-buffer (#193)
  86. self._buffer = b""
  87. def update(self, data: bytes) -> bytes:
  88. self._buffer, result = _byte_padding_update(
  89. self._buffer, data, self.block_size
  90. )
  91. return result
  92. def _padding(self, size: int) -> bytes:
  93. return bytes([size]) * size
  94. def finalize(self) -> bytes:
  95. result = _byte_padding_pad(
  96. self._buffer, self.block_size, self._padding
  97. )
  98. self._buffer = None
  99. return result
  100. class _PKCS7UnpaddingContext(PaddingContext):
  101. _buffer: typing.Optional[bytes]
  102. def __init__(self, block_size: int):
  103. self.block_size = block_size
  104. # TODO: more copies than necessary, we should use zero-buffer (#193)
  105. self._buffer = b""
  106. def update(self, data: bytes) -> bytes:
  107. self._buffer, result = _byte_unpadding_update(
  108. self._buffer, data, self.block_size
  109. )
  110. return result
  111. def finalize(self) -> bytes:
  112. result = _byte_unpadding_check(
  113. self._buffer, self.block_size, check_pkcs7_padding
  114. )
  115. self._buffer = None
  116. return result
  117. class ANSIX923:
  118. def __init__(self, block_size: int):
  119. _byte_padding_check(block_size)
  120. self.block_size = block_size
  121. def padder(self) -> PaddingContext:
  122. return _ANSIX923PaddingContext(self.block_size)
  123. def unpadder(self) -> PaddingContext:
  124. return _ANSIX923UnpaddingContext(self.block_size)
  125. class _ANSIX923PaddingContext(PaddingContext):
  126. _buffer: typing.Optional[bytes]
  127. def __init__(self, block_size: int):
  128. self.block_size = block_size
  129. # TODO: more copies than necessary, we should use zero-buffer (#193)
  130. self._buffer = b""
  131. def update(self, data: bytes) -> bytes:
  132. self._buffer, result = _byte_padding_update(
  133. self._buffer, data, self.block_size
  134. )
  135. return result
  136. def _padding(self, size: int) -> bytes:
  137. return bytes([0]) * (size - 1) + bytes([size])
  138. def finalize(self) -> bytes:
  139. result = _byte_padding_pad(
  140. self._buffer, self.block_size, self._padding
  141. )
  142. self._buffer = None
  143. return result
  144. class _ANSIX923UnpaddingContext(PaddingContext):
  145. _buffer: typing.Optional[bytes]
  146. def __init__(self, block_size: int):
  147. self.block_size = block_size
  148. # TODO: more copies than necessary, we should use zero-buffer (#193)
  149. self._buffer = b""
  150. def update(self, data: bytes) -> bytes:
  151. self._buffer, result = _byte_unpadding_update(
  152. self._buffer, data, self.block_size
  153. )
  154. return result
  155. def finalize(self) -> bytes:
  156. result = _byte_unpadding_check(
  157. self._buffer,
  158. self.block_size,
  159. check_ansix923_padding,
  160. )
  161. self._buffer = None
  162. return result