You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
7.9 KiB

4 years ago
  1. # The MIT License (MIT)
  2. #
  3. # Copyright (c) 2014 Richard Moore
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. # THE SOFTWARE.
  22. from .aes import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation
  23. from .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable
  24. # First we inject three functions to each of the modes of operations
  25. #
  26. # _can_consume(size)
  27. # - Given a size, determine how many bytes could be consumed in
  28. # a single call to either the decrypt or encrypt method
  29. #
  30. # _final_encrypt(data, padding = PADDING_DEFAULT)
  31. # - call and return encrypt on this (last) chunk of data,
  32. # padding as necessary; this will always be at least 16
  33. # bytes unless the total incoming input was less than 16
  34. # bytes
  35. #
  36. # _final_decrypt(data, padding = PADDING_DEFAULT)
  37. # - same as _final_encrypt except for decrypt, for
  38. # stripping off padding
  39. #
  40. PADDING_NONE = 'none'
  41. PADDING_DEFAULT = 'default'
  42. # @TODO: Ciphertext stealing and explicit PKCS#7
  43. # PADDING_CIPHERTEXT_STEALING
  44. # PADDING_PKCS7
  45. # ECB and CBC are block-only ciphers
  46. def _block_can_consume(self, size):
  47. if size >= 16: return 16
  48. return 0
  49. # After padding, we may have more than one block
  50. def _block_final_encrypt(self, data, padding = PADDING_DEFAULT):
  51. if padding == PADDING_DEFAULT:
  52. data = append_PKCS7_padding(data)
  53. elif padding == PADDING_NONE:
  54. if len(data) != 16:
  55. raise Exception('invalid data length for final block')
  56. else:
  57. raise Exception('invalid padding option')
  58. if len(data) == 32:
  59. return self.encrypt(data[:16]) + self.encrypt(data[16:])
  60. return self.encrypt(data)
  61. def _block_final_decrypt(self, data, padding = PADDING_DEFAULT):
  62. if padding == PADDING_DEFAULT:
  63. return strip_PKCS7_padding(self.decrypt(data))
  64. if padding == PADDING_NONE:
  65. if len(data) != 16:
  66. raise Exception('invalid data length for final block')
  67. return self.decrypt(data)
  68. raise Exception('invalid padding option')
  69. AESBlockModeOfOperation._can_consume = _block_can_consume
  70. AESBlockModeOfOperation._final_encrypt = _block_final_encrypt
  71. AESBlockModeOfOperation._final_decrypt = _block_final_decrypt
  72. # CFB is a segment cipher
  73. def _segment_can_consume(self, size):
  74. return self.segment_bytes * int(size // self.segment_bytes)
  75. # CFB can handle a non-segment-sized block at the end using the remaining cipherblock
  76. def _segment_final_encrypt(self, data, padding = PADDING_DEFAULT):
  77. if padding != PADDING_DEFAULT:
  78. raise Exception('invalid padding option')
  79. faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
  80. padded = data + to_bufferable(faux_padding)
  81. return self.encrypt(padded)[:len(data)]
  82. # CFB can handle a non-segment-sized block at the end using the remaining cipherblock
  83. def _segment_final_decrypt(self, data, padding = PADDING_DEFAULT):
  84. if padding != PADDING_DEFAULT:
  85. raise Exception('invalid padding option')
  86. faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
  87. padded = data + to_bufferable(faux_padding)
  88. return self.decrypt(padded)[:len(data)]
  89. AESSegmentModeOfOperation._can_consume = _segment_can_consume
  90. AESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt
  91. AESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt
  92. # OFB and CTR are stream ciphers
  93. def _stream_can_consume(self, size):
  94. return size
  95. def _stream_final_encrypt(self, data, padding = PADDING_DEFAULT):
  96. if padding not in [PADDING_NONE, PADDING_DEFAULT]:
  97. raise Exception('invalid padding option')
  98. return self.encrypt(data)
  99. def _stream_final_decrypt(self, data, padding = PADDING_DEFAULT):
  100. if padding not in [PADDING_NONE, PADDING_DEFAULT]:
  101. raise Exception('invalid padding option')
  102. return self.decrypt(data)
  103. AESStreamModeOfOperation._can_consume = _stream_can_consume
  104. AESStreamModeOfOperation._final_encrypt = _stream_final_encrypt
  105. AESStreamModeOfOperation._final_decrypt = _stream_final_decrypt
  106. class BlockFeeder(object):
  107. '''The super-class for objects to handle chunking a stream of bytes
  108. into the appropriate block size for the underlying mode of operation
  109. and applying (or stripping) padding, as necessary.'''
  110. def __init__(self, mode, feed, final, padding = PADDING_DEFAULT):
  111. self._mode = mode
  112. self._feed = feed
  113. self._final = final
  114. self._buffer = to_bufferable("")
  115. self._padding = padding
  116. def feed(self, data = None):
  117. '''Provide bytes to encrypt (or decrypt), returning any bytes
  118. possible from this or any previous calls to feed.
  119. Call with None or an empty string to flush the mode of
  120. operation and return any final bytes; no further calls to
  121. feed may be made.'''
  122. if self._buffer is None:
  123. raise ValueError('already finished feeder')
  124. # Finalize; process the spare bytes we were keeping
  125. if data is None:
  126. result = self._final(self._buffer, self._padding)
  127. self._buffer = None
  128. return result
  129. self._buffer += to_bufferable(data)
  130. # We keep 16 bytes around so we can determine padding
  131. result = to_bufferable('')
  132. while len(self._buffer) > 16:
  133. can_consume = self._mode._can_consume(len(self._buffer) - 16)
  134. if can_consume == 0: break
  135. result += self._feed(self._buffer[:can_consume])
  136. self._buffer = self._buffer[can_consume:]
  137. return result
  138. class Encrypter(BlockFeeder):
  139. 'Accepts bytes of plaintext and returns encrypted ciphertext.'
  140. def __init__(self, mode, padding = PADDING_DEFAULT):
  141. BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding)
  142. class Decrypter(BlockFeeder):
  143. 'Accepts bytes of ciphertext and returns decrypted plaintext.'
  144. def __init__(self, mode, padding = PADDING_DEFAULT):
  145. BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding)
  146. # 8kb blocks
  147. BLOCK_SIZE = (1 << 13)
  148. def _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE):
  149. 'Uses feeder to read and convert from in_stream and write to out_stream.'
  150. while True:
  151. chunk = in_stream.read(block_size)
  152. if not chunk:
  153. break
  154. converted = feeder.feed(chunk)
  155. out_stream.write(converted)
  156. converted = feeder.feed()
  157. out_stream.write(converted)
  158. def encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
  159. 'Encrypts a stream of bytes from in_stream to out_stream using mode.'
  160. encrypter = Encrypter(mode, padding = padding)
  161. _feed_stream(encrypter, in_stream, out_stream, block_size)
  162. def decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
  163. 'Decrypts a stream of bytes from in_stream to out_stream using mode.'
  164. decrypter = Decrypter(mode, padding = padding)
  165. _feed_stream(decrypter, in_stream, out_stream, block_size)