ChaCha20.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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 Crypto.Random import get_random_bytes
  31. from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
  32. create_string_buffer,
  33. get_raw_buffer, VoidPointer,
  34. SmartPointer, c_size_t,
  35. expect_byte_string, c_ulong)
  36. _raw_chacha20_lib = load_pycryptodome_raw_lib("Crypto.Cipher._chacha20",
  37. """
  38. int chacha20_init(void **pState,
  39. const uint8_t *key,
  40. size_t keySize,
  41. const uint8_t *nonce,
  42. size_t nonceSize);
  43. int chacha20_destroy(void *state);
  44. int chacha20_encrypt(void *state,
  45. const uint8_t in[],
  46. uint8_t out[],
  47. size_t len);
  48. int chacha20_seek(void *state,
  49. unsigned long block_high,
  50. unsigned long block_low,
  51. unsigned offset);
  52. """)
  53. class ChaCha20Cipher:
  54. """ChaCha20 cipher object. Do not create it directly. Use :py:func:`new` instead.
  55. :var nonce: The nonce with length 8
  56. :vartype nonce: byte string
  57. """
  58. block_size = 1
  59. def __init__(self, key, nonce):
  60. """Initialize a ChaCha20 cipher object
  61. See also `new()` at the module level."""
  62. expect_byte_string(key)
  63. expect_byte_string(nonce)
  64. self.nonce = 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. key,
  70. c_size_t(len(key)),
  71. 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):
  78. """Encrypt a piece of data.
  79. :param plaintext: The data to encrypt, of any size.
  80. :type plaintext: byte string
  81. :returns: the encrypted byte string, of equal length as the
  82. plaintext.
  83. """
  84. if self.encrypt not in self._next:
  85. raise TypeError("Cipher object can only be used for decryption")
  86. self._next = ( self.encrypt, )
  87. return self._encrypt(plaintext)
  88. def _encrypt(self, plaintext):
  89. """Encrypt without FSM checks"""
  90. expect_byte_string(plaintext)
  91. ciphertext = create_string_buffer(len(plaintext))
  92. result = _raw_chacha20_lib.chacha20_encrypt(
  93. self._state.get(),
  94. plaintext,
  95. ciphertext,
  96. c_size_t(len(plaintext)))
  97. if result:
  98. raise ValueError("Error %d while encrypting with ChaCha20" % result)
  99. return get_raw_buffer(ciphertext)
  100. def decrypt(self, ciphertext):
  101. """Decrypt a piece of data.
  102. :param ciphertext: The data to decrypt, of any size.
  103. :type ciphertext: byte string
  104. :returns: the decrypted byte string, of equal length as the
  105. ciphertext.
  106. """
  107. if self.decrypt not in self._next:
  108. raise TypeError("Cipher object can only be used for encryption")
  109. self._next = ( self.decrypt, )
  110. try:
  111. return self._encrypt(ciphertext)
  112. except ValueError, e:
  113. raise ValueError(str(e).replace("enc", "dec"))
  114. def seek(self, position):
  115. """Seek to a certain position in the key stream.
  116. :param integer position:
  117. The absolute position within the key stream, in bytes.
  118. """
  119. offset = position & 0x3f
  120. position >>= 6
  121. block_low = position & 0xFFFFFFFF
  122. block_high = position >> 32
  123. result = _raw_chacha20_lib.chacha20_seek(
  124. self._state.get(),
  125. c_ulong(block_high),
  126. c_ulong(block_low),
  127. offset
  128. )
  129. if result:
  130. raise ValueError("Error %d while seeking with ChaCha20" % result)
  131. def new(**kwargs):
  132. """Create a new ChaCha20 cipher
  133. :keyword key: The secret key to use. It must be 32 bytes long.
  134. :type key: byte string
  135. :keyword nonce:
  136. A mandatory value that must never be reused for any other encryption
  137. done with this key. It must be 8 bytes long.
  138. If not provided, a random byte string will be generated (you can read
  139. it back via the ``nonce`` attribute of the returned object).
  140. :type nonce: byte string
  141. :Return: a :class:`Crypto.Cipher.ChaCha20.ChaCha20Cipher` object
  142. """
  143. try:
  144. key = kwargs.pop("key")
  145. except KeyError, e:
  146. raise TypeError("Missing parameter %s" % e)
  147. nonce = kwargs.pop("nonce", None)
  148. if nonce is None:
  149. nonce = get_random_bytes(8)
  150. if len(key) != 32:
  151. raise ValueError("ChaCha20 key must be 32 bytes long")
  152. if len(nonce) != 8:
  153. raise ValueError("ChaCha20 nonce must be 8 bytes long")
  154. if kwargs:
  155. raise TypeError("Unknown parameters: " + str(kwargs))
  156. return ChaCha20Cipher(key, nonce)
  157. # Size of a data block (in bytes)
  158. block_size = 1
  159. # Size of a key (in bytes)
  160. key_size = 32