ChaCha20.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. # ===================================================================
  2. #
  3. # Copyright (c) 2014, Legrandin <helderijs@gmail.com>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. #
  10. # 1. Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # 2. Redistributions in binary form must reproduce the above copyright
  13. # notice, this list of conditions and the following disclaimer in
  14. # the documentation and/or other materials provided with the
  15. # distribution.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  20. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  21. # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  22. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  23. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  25. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  26. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  27. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  28. # POSSIBILITY OF SUCH DAMAGE.
  29. # ===================================================================
  30. from Cryptodome.Random import get_random_bytes
  31. from Cryptodome.Util.py3compat import _copy_bytes
  32. from Cryptodome.Util._raw_api import (load_pycryptodome_raw_lib,
  33. create_string_buffer,
  34. get_raw_buffer, VoidPointer,
  35. SmartPointer, c_size_t,
  36. c_uint8_ptr, c_ulong,
  37. is_writeable_buffer)
  38. _raw_chacha20_lib = load_pycryptodome_raw_lib("Cryptodome.Cipher._chacha20",
  39. """
  40. int chacha20_init(void **pState,
  41. const uint8_t *key,
  42. size_t keySize,
  43. const uint8_t *nonce,
  44. size_t nonceSize);
  45. int chacha20_destroy(void *state);
  46. int chacha20_encrypt(void *state,
  47. const uint8_t in[],
  48. uint8_t out[],
  49. size_t len);
  50. int chacha20_seek(void *state,
  51. unsigned long block_high,
  52. unsigned long block_low,
  53. unsigned offset);
  54. """)
  55. class ChaCha20Cipher(object):
  56. """ChaCha20 cipher object. Do not create it directly. Use :py:func:`new` instead.
  57. :var nonce: The nonce with length 8 or 12
  58. :vartype nonce: bytes
  59. """
  60. block_size = 1
  61. def __init__(self, key, nonce):
  62. """Initialize a ChaCha20 cipher object
  63. See also `new()` at the module level."""
  64. self.nonce = _copy_bytes(None, None, nonce)
  65. self._next = ( self.encrypt, self.decrypt )
  66. self._state = VoidPointer()
  67. result = _raw_chacha20_lib.chacha20_init(
  68. self._state.address_of(),
  69. c_uint8_ptr(key),
  70. c_size_t(len(key)),
  71. self.nonce,
  72. c_size_t(len(nonce)))
  73. if result:
  74. raise ValueError("Error %d instantiating a ChaCha20 cipher")
  75. self._state = SmartPointer(self._state.get(),
  76. _raw_chacha20_lib.chacha20_destroy)
  77. def encrypt(self, plaintext, output=None):
  78. """Encrypt a piece of data.
  79. Args:
  80. plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
  81. Keyword Args:
  82. output(bytes/bytearray/memoryview): The location where the ciphertext
  83. is written to. If ``None``, the ciphertext is returned.
  84. Returns:
  85. If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
  86. Otherwise, ``None``.
  87. """
  88. if self.encrypt not in self._next:
  89. raise TypeError("Cipher object can only be used for decryption")
  90. self._next = ( self.encrypt, )
  91. return self._encrypt(plaintext, output)
  92. def _encrypt(self, plaintext, output):
  93. """Encrypt without FSM checks"""
  94. if output is None:
  95. ciphertext = create_string_buffer(len(plaintext))
  96. else:
  97. ciphertext = output
  98. if not is_writeable_buffer(output):
  99. raise TypeError("output must be a bytearray or a writeable memoryview")
  100. if len(plaintext) != len(output):
  101. raise ValueError("output must have the same length as the input"
  102. " (%d bytes)" % len(plaintext))
  103. result = _raw_chacha20_lib.chacha20_encrypt(
  104. self._state.get(),
  105. c_uint8_ptr(plaintext),
  106. c_uint8_ptr(ciphertext),
  107. c_size_t(len(plaintext)))
  108. if result:
  109. raise ValueError("Error %d while encrypting with ChaCha20" % result)
  110. if output is None:
  111. return get_raw_buffer(ciphertext)
  112. else:
  113. return None
  114. def decrypt(self, ciphertext, output=None):
  115. """Decrypt a piece of data.
  116. Args:
  117. ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
  118. Keyword Args:
  119. output(bytes/bytearray/memoryview): The location where the plaintext
  120. is written to. If ``None``, the plaintext is returned.
  121. Returns:
  122. If ``output`` is ``None``, the plaintext is returned as ``bytes``.
  123. Otherwise, ``None``.
  124. """
  125. if self.decrypt not in self._next:
  126. raise TypeError("Cipher object can only be used for encryption")
  127. self._next = ( self.decrypt, )
  128. try:
  129. return self._encrypt(ciphertext, output)
  130. except ValueError as e:
  131. raise ValueError(str(e).replace("enc", "dec"))
  132. def seek(self, position):
  133. """Seek to a certain position in the key stream.
  134. Args:
  135. position (integer):
  136. The absolute position within the key stream, in bytes.
  137. """
  138. position, offset = divmod(position, 64)
  139. block_low = position & 0xFFFFFFFF
  140. block_high = position >> 32
  141. result = _raw_chacha20_lib.chacha20_seek(
  142. self._state.get(),
  143. c_ulong(block_high),
  144. c_ulong(block_low),
  145. offset
  146. )
  147. if result:
  148. raise ValueError("Error %d while seeking with ChaCha20" % result)
  149. def _derive_Poly1305_key_pair(key, nonce):
  150. """Derive a tuple (r, s, nonce) for a Poly1305 MAC.
  151. If nonce is ``None``, a new 12-byte nonce is generated.
  152. """
  153. if len(key) != 32:
  154. raise ValueError("Poly1305 with ChaCha20 requires a 32-byte key")
  155. if nonce is None:
  156. padded_nonce = nonce = get_random_bytes(12)
  157. elif len(nonce) == 8:
  158. # See RFC7538, 2.6: [...] ChaCha20 as specified here requires a 96-bit
  159. # nonce. So if the provided nonce is only 64-bit, then the first 32
  160. # bits of the nonce will be set to a constant number.
  161. # This will usually be zero, but for protocols with multiple senders it may be
  162. # different for each sender, but should be the same for all
  163. # invocations of the function with the same key by a particular
  164. # sender.
  165. padded_nonce = b'\x00\x00\x00\x00' + nonce
  166. elif len(nonce) == 12:
  167. padded_nonce = nonce
  168. else:
  169. raise ValueError("Poly1305 with ChaCha20 requires an 8- or 12-byte nonce")
  170. rs = new(key=key, nonce=padded_nonce).encrypt(b'\x00' * 32)
  171. return rs[:16], rs[16:], nonce
  172. def new(**kwargs):
  173. """Create a new ChaCha20 cipher
  174. Keyword Args:
  175. key (bytes/bytearray/memoryview): The secret key to use.
  176. It must be 32 bytes long.
  177. nonce (bytes/bytearray/memoryview): A mandatory value that
  178. must never be reused for any other encryption
  179. done with this key. It must be 8 or 12 bytes long.
  180. If not provided, 8 bytes will be randomly generated
  181. (you can find them back in the ``nonce`` attribute).
  182. :Return: a :class:`Cryptodome.Cipher.ChaCha20.ChaCha20Cipher` object
  183. """
  184. try:
  185. key = kwargs.pop("key")
  186. except KeyError as e:
  187. raise TypeError("Missing parameter %s" % e)
  188. nonce = kwargs.pop("nonce", None)
  189. if nonce is None:
  190. nonce = get_random_bytes(8)
  191. if len(key) != 32:
  192. raise ValueError("ChaCha20 key must be 32 bytes long")
  193. if len(nonce) not in (8, 12):
  194. raise ValueError("ChaCha20 nonce must be 8 or 12 bytes long")
  195. if kwargs:
  196. raise TypeError("Unknown parameters: " + str(kwargs))
  197. return ChaCha20Cipher(key, nonce)
  198. # Size of a data block (in bytes)
  199. block_size = 1
  200. # Size of a key (in bytes)
  201. key_size = 32