ciphers.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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 cryptography import utils
  5. from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons
  6. from cryptography.hazmat.primitives import ciphers
  7. from cryptography.hazmat.primitives.ciphers import modes
  8. @utils.register_interface(ciphers.CipherContext)
  9. @utils.register_interface(ciphers.AEADCipherContext)
  10. @utils.register_interface(ciphers.AEADEncryptionContext)
  11. @utils.register_interface(ciphers.AEADDecryptionContext)
  12. class _CipherContext(object):
  13. _ENCRYPT = 1
  14. _DECRYPT = 0
  15. _MAX_CHUNK_SIZE = 2 ** 30 - 1
  16. def __init__(self, backend, cipher, mode, operation):
  17. self._backend = backend
  18. self._cipher = cipher
  19. self._mode = mode
  20. self._operation = operation
  21. self._tag = None
  22. if isinstance(self._cipher, ciphers.BlockCipherAlgorithm):
  23. self._block_size_bytes = self._cipher.block_size // 8
  24. else:
  25. self._block_size_bytes = 1
  26. ctx = self._backend._lib.EVP_CIPHER_CTX_new()
  27. ctx = self._backend._ffi.gc(
  28. ctx, self._backend._lib.EVP_CIPHER_CTX_free
  29. )
  30. registry = self._backend._cipher_registry
  31. try:
  32. adapter = registry[type(cipher), type(mode)]
  33. except KeyError:
  34. raise UnsupportedAlgorithm(
  35. "cipher {} in {} mode is not supported "
  36. "by this backend.".format(
  37. cipher.name, mode.name if mode else mode
  38. ),
  39. _Reasons.UNSUPPORTED_CIPHER,
  40. )
  41. evp_cipher = adapter(self._backend, cipher, mode)
  42. if evp_cipher == self._backend._ffi.NULL:
  43. msg = "cipher {0.name} ".format(cipher)
  44. if mode is not None:
  45. msg += "in {0.name} mode ".format(mode)
  46. msg += (
  47. "is not supported by this backend (Your version of OpenSSL "
  48. "may be too old. Current version: {}.)"
  49. ).format(self._backend.openssl_version_text())
  50. raise UnsupportedAlgorithm(msg, _Reasons.UNSUPPORTED_CIPHER)
  51. if isinstance(mode, modes.ModeWithInitializationVector):
  52. iv_nonce = self._backend._ffi.from_buffer(
  53. mode.initialization_vector
  54. )
  55. elif isinstance(mode, modes.ModeWithTweak):
  56. iv_nonce = self._backend._ffi.from_buffer(mode.tweak)
  57. elif isinstance(mode, modes.ModeWithNonce):
  58. iv_nonce = self._backend._ffi.from_buffer(mode.nonce)
  59. elif isinstance(cipher, modes.ModeWithNonce):
  60. iv_nonce = self._backend._ffi.from_buffer(cipher.nonce)
  61. else:
  62. iv_nonce = self._backend._ffi.NULL
  63. # begin init with cipher and operation type
  64. res = self._backend._lib.EVP_CipherInit_ex(
  65. ctx,
  66. evp_cipher,
  67. self._backend._ffi.NULL,
  68. self._backend._ffi.NULL,
  69. self._backend._ffi.NULL,
  70. operation,
  71. )
  72. self._backend.openssl_assert(res != 0)
  73. # set the key length to handle variable key ciphers
  74. res = self._backend._lib.EVP_CIPHER_CTX_set_key_length(
  75. ctx, len(cipher.key)
  76. )
  77. self._backend.openssl_assert(res != 0)
  78. if isinstance(mode, modes.GCM):
  79. res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
  80. ctx,
  81. self._backend._lib.EVP_CTRL_AEAD_SET_IVLEN,
  82. len(iv_nonce),
  83. self._backend._ffi.NULL,
  84. )
  85. self._backend.openssl_assert(res != 0)
  86. if mode.tag is not None:
  87. res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
  88. ctx,
  89. self._backend._lib.EVP_CTRL_AEAD_SET_TAG,
  90. len(mode.tag),
  91. mode.tag,
  92. )
  93. self._backend.openssl_assert(res != 0)
  94. self._tag = mode.tag
  95. # pass key/iv
  96. res = self._backend._lib.EVP_CipherInit_ex(
  97. ctx,
  98. self._backend._ffi.NULL,
  99. self._backend._ffi.NULL,
  100. self._backend._ffi.from_buffer(cipher.key),
  101. iv_nonce,
  102. operation,
  103. )
  104. self._backend.openssl_assert(res != 0)
  105. # We purposely disable padding here as it's handled higher up in the
  106. # API.
  107. self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
  108. self._ctx = ctx
  109. def update(self, data: bytes) -> bytes:
  110. buf = bytearray(len(data) + self._block_size_bytes - 1)
  111. n = self.update_into(data, buf)
  112. return bytes(buf[:n])
  113. def update_into(self, data: bytes, buf) -> int:
  114. total_data_len = len(data)
  115. if len(buf) < (total_data_len + self._block_size_bytes - 1):
  116. raise ValueError(
  117. "buffer must be at least {} bytes for this "
  118. "payload".format(len(data) + self._block_size_bytes - 1)
  119. )
  120. data_processed = 0
  121. total_out = 0
  122. outlen = self._backend._ffi.new("int *")
  123. baseoutbuf = self._backend._ffi.from_buffer(buf)
  124. baseinbuf = self._backend._ffi.from_buffer(data)
  125. while data_processed != total_data_len:
  126. outbuf = baseoutbuf + total_out
  127. inbuf = baseinbuf + data_processed
  128. inlen = min(self._MAX_CHUNK_SIZE, total_data_len - data_processed)
  129. res = self._backend._lib.EVP_CipherUpdate(
  130. self._ctx, outbuf, outlen, inbuf, inlen
  131. )
  132. self._backend.openssl_assert(res != 0)
  133. data_processed += inlen
  134. total_out += outlen[0]
  135. return total_out
  136. def finalize(self) -> bytes:
  137. if (
  138. self._operation == self._DECRYPT
  139. and isinstance(self._mode, modes.ModeWithAuthenticationTag)
  140. and self.tag is None
  141. ):
  142. raise ValueError(
  143. "Authentication tag must be provided when decrypting."
  144. )
  145. buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes)
  146. outlen = self._backend._ffi.new("int *")
  147. res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
  148. if res == 0:
  149. errors = self._backend._consume_errors()
  150. if not errors and isinstance(self._mode, modes.GCM):
  151. raise InvalidTag
  152. self._backend.openssl_assert(
  153. errors[0]._lib_reason_match(
  154. self._backend._lib.ERR_LIB_EVP,
  155. self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH,
  156. ),
  157. errors=errors,
  158. )
  159. raise ValueError(
  160. "The length of the provided data is not a multiple of "
  161. "the block length."
  162. )
  163. if (
  164. isinstance(self._mode, modes.GCM)
  165. and self._operation == self._ENCRYPT
  166. ):
  167. tag_buf = self._backend._ffi.new(
  168. "unsigned char[]", self._block_size_bytes
  169. )
  170. res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
  171. self._ctx,
  172. self._backend._lib.EVP_CTRL_AEAD_GET_TAG,
  173. self._block_size_bytes,
  174. tag_buf,
  175. )
  176. self._backend.openssl_assert(res != 0)
  177. self._tag = self._backend._ffi.buffer(tag_buf)[:]
  178. res = self._backend._lib.EVP_CIPHER_CTX_reset(self._ctx)
  179. self._backend.openssl_assert(res == 1)
  180. return self._backend._ffi.buffer(buf)[: outlen[0]]
  181. def finalize_with_tag(self, tag: bytes) -> bytes:
  182. if len(tag) < self._mode._min_tag_length:
  183. raise ValueError(
  184. "Authentication tag must be {} bytes or longer.".format(
  185. self._mode._min_tag_length
  186. )
  187. )
  188. res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
  189. self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag
  190. )
  191. self._backend.openssl_assert(res != 0)
  192. self._tag = tag
  193. return self.finalize()
  194. def authenticate_additional_data(self, data: bytes) -> None:
  195. outlen = self._backend._ffi.new("int *")
  196. res = self._backend._lib.EVP_CipherUpdate(
  197. self._ctx,
  198. self._backend._ffi.NULL,
  199. outlen,
  200. self._backend._ffi.from_buffer(data),
  201. len(data),
  202. )
  203. self._backend.openssl_assert(res != 0)
  204. tag = utils.read_only_property("_tag")