secret.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. # Copyright 2013 Donald Stufft and individual contributors
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import ClassVar, Optional
  15. import nacl.bindings
  16. from nacl import encoding
  17. from nacl import exceptions as exc
  18. from nacl.utils import EncryptedMessage, StringFixer, random
  19. class SecretBox(encoding.Encodable, StringFixer):
  20. """
  21. The SecretBox class encrypts and decrypts messages using the given secret
  22. key.
  23. The ciphertexts generated by :class:`~nacl.secret.Secretbox` include a 16
  24. byte authenticator which is checked as part of the decryption. An invalid
  25. authenticator will cause the decrypt function to raise an exception. The
  26. authenticator is not a signature. Once you've decrypted the message you've
  27. demonstrated the ability to create arbitrary valid message, so messages you
  28. send are repudiable. For non-repudiable messages, sign them after
  29. encryption.
  30. Encryption is done using `XSalsa20-Poly1305`_, and there are no practical
  31. limits on the number or size of messages (up to 2⁶⁴ messages, each up to 2⁶⁴
  32. bytes).
  33. .. _XSalsa20-Poly1305: https://doc.libsodium.org/secret-key_cryptography/secretbox#algorithm-details
  34. :param key: The secret key used to encrypt and decrypt messages
  35. :param encoder: The encoder class used to decode the given key
  36. :cvar KEY_SIZE: The size that the key is required to be.
  37. :cvar NONCE_SIZE: The size that the nonce is required to be.
  38. :cvar MACBYTES: The size of the authentication MAC tag in bytes.
  39. :cvar MESSAGEBYTES_MAX: The maximum size of a message which can be
  40. safely encrypted with a single key/nonce
  41. pair.
  42. """
  43. KEY_SIZE: ClassVar[int] = nacl.bindings.crypto_secretbox_KEYBYTES
  44. NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_secretbox_NONCEBYTES
  45. MACBYTES: ClassVar[int] = nacl.bindings.crypto_secretbox_MACBYTES
  46. MESSAGEBYTES_MAX: ClassVar[
  47. int
  48. ] = nacl.bindings.crypto_secretbox_MESSAGEBYTES_MAX
  49. def __init__(
  50. self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
  51. ):
  52. key = encoder.decode(key)
  53. if not isinstance(key, bytes):
  54. raise exc.TypeError("SecretBox must be created from 32 bytes")
  55. if len(key) != self.KEY_SIZE:
  56. raise exc.ValueError(
  57. "The key must be exactly %s bytes long" % self.KEY_SIZE,
  58. )
  59. self._key = key
  60. def __bytes__(self) -> bytes:
  61. return self._key
  62. def encrypt(
  63. self,
  64. plaintext: bytes,
  65. nonce: Optional[bytes] = None,
  66. encoder: encoding.Encoder = encoding.RawEncoder,
  67. ) -> EncryptedMessage:
  68. """
  69. Encrypts the plaintext message using the given `nonce` (or generates
  70. one randomly if omitted) and returns the ciphertext encoded with the
  71. encoder.
  72. .. warning:: It is **VITALLY** important that the nonce is a nonce,
  73. i.e. it is a number used only once for any given key. If you fail
  74. to do this, you compromise the privacy of the messages encrypted.
  75. Give your nonces a different prefix, or have one side use an odd
  76. counter and one an even counter. Just make sure they are different.
  77. :param plaintext: [:class:`bytes`] The plaintext message to encrypt
  78. :param nonce: [:class:`bytes`] The nonce to use in the encryption
  79. :param encoder: The encoder to use to encode the ciphertext
  80. :rtype: [:class:`nacl.utils.EncryptedMessage`]
  81. """
  82. if nonce is None:
  83. nonce = random(self.NONCE_SIZE)
  84. if len(nonce) != self.NONCE_SIZE:
  85. raise exc.ValueError(
  86. "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
  87. )
  88. ciphertext = nacl.bindings.crypto_secretbox(
  89. plaintext, nonce, self._key
  90. )
  91. encoded_nonce = encoder.encode(nonce)
  92. encoded_ciphertext = encoder.encode(ciphertext)
  93. return EncryptedMessage._from_parts(
  94. encoded_nonce,
  95. encoded_ciphertext,
  96. encoder.encode(nonce + ciphertext),
  97. )
  98. def decrypt(
  99. self,
  100. ciphertext: bytes,
  101. nonce: Optional[bytes] = None,
  102. encoder: encoding.Encoder = encoding.RawEncoder,
  103. ) -> bytes:
  104. """
  105. Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
  106. parameter or implicitly, when omitted, as part of the ciphertext) and
  107. returns the plaintext message.
  108. :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
  109. :param nonce: [:class:`bytes`] The nonce used when encrypting the
  110. ciphertext
  111. :param encoder: The encoder used to decode the ciphertext.
  112. :rtype: [:class:`bytes`]
  113. """
  114. # Decode our ciphertext
  115. ciphertext = encoder.decode(ciphertext)
  116. if nonce is None:
  117. # If we were given the nonce and ciphertext combined, split them.
  118. nonce = ciphertext[: self.NONCE_SIZE]
  119. ciphertext = ciphertext[self.NONCE_SIZE :]
  120. if len(nonce) != self.NONCE_SIZE:
  121. raise exc.ValueError(
  122. "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
  123. )
  124. plaintext = nacl.bindings.crypto_secretbox_open(
  125. ciphertext, nonce, self._key
  126. )
  127. return plaintext
  128. class Aead(encoding.Encodable, StringFixer):
  129. """
  130. The AEAD class encrypts and decrypts messages using the given secret key.
  131. Unlike :class:`~nacl.secret.SecretBox`, AEAD supports authenticating
  132. non-confidential data received alongside the message, such as a length
  133. or type tag.
  134. Like :class:`~nacl.secret.Secretbox`, this class provides authenticated
  135. encryption. An inauthentic message will cause the decrypt function to raise
  136. an exception.
  137. Likewise, the authenticator should not be mistaken for a (public-key)
  138. signature: recipients (with the ability to decrypt messages) are capable of
  139. creating arbitrary valid message; in particular, this means AEAD messages
  140. are repudiable. For non-repudiable messages, sign them after encryption.
  141. The cryptosystem used is `XChacha20-Poly1305`_ as specified for
  142. `standardization`_. There are `no practical limits`_ to how much can safely
  143. be encrypted under a given key (up to 2⁶⁴ messages each containing up
  144. to 2⁶⁴ bytes).
  145. .. _standardization: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
  146. .. _XChacha20-Poly1305: https://doc.libsodium.org/secret-key_cryptography/aead#xchacha-20-poly1305
  147. .. _no practical limits: https://doc.libsodium.org/secret-key_cryptography/aead#limitations
  148. :param key: The secret key used to encrypt and decrypt messages
  149. :param encoder: The encoder class used to decode the given key
  150. :cvar KEY_SIZE: The size that the key is required to be.
  151. :cvar NONCE_SIZE: The size that the nonce is required to be.
  152. :cvar MACBYTES: The size of the authentication MAC tag in bytes.
  153. :cvar MESSAGEBYTES_MAX: The maximum size of a message which can be
  154. safely encrypted with a single key/nonce
  155. pair.
  156. """
  157. KEY_SIZE = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_KEYBYTES
  158. NONCE_SIZE = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
  159. MACBYTES = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_ABYTES
  160. MESSAGEBYTES_MAX = (
  161. nacl.bindings.crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
  162. )
  163. def __init__(
  164. self,
  165. key: bytes,
  166. encoder: encoding.Encoder = encoding.RawEncoder,
  167. ):
  168. key = encoder.decode(key)
  169. if not isinstance(key, bytes):
  170. raise exc.TypeError("AEAD must be created from 32 bytes")
  171. if len(key) != self.KEY_SIZE:
  172. raise exc.ValueError(
  173. "The key must be exactly %s bytes long" % self.KEY_SIZE,
  174. )
  175. self._key = key
  176. def __bytes__(self) -> bytes:
  177. return self._key
  178. def encrypt(
  179. self,
  180. plaintext: bytes,
  181. aad: bytes = b"",
  182. nonce: Optional[bytes] = None,
  183. encoder: encoding.Encoder = encoding.RawEncoder,
  184. ) -> EncryptedMessage:
  185. """
  186. Encrypts the plaintext message using the given `nonce` (or generates
  187. one randomly if omitted) and returns the ciphertext encoded with the
  188. encoder.
  189. .. warning:: It is vitally important for :param nonce: to be unique.
  190. By default, it is generated randomly; [:class:`Aead`] uses XChacha20
  191. for extended (192b) nonce size, so the risk of reusing random nonces
  192. is negligible. It is *strongly recommended* to keep this behaviour,
  193. as nonce reuse will compromise the privacy of encrypted messages.
  194. Should implicit nonces be inadequate for your application, the
  195. second best option is using split counters; e.g. if sending messages
  196. encrypted under a shared key between 2 users, each user can use the
  197. number of messages it sent so far, prefixed or suffixed with a 1bit
  198. user id. Note that the counter must **never** be rolled back (due
  199. to overflow, on-disk state being rolled back to an earlier backup,
  200. ...)
  201. :param plaintext: [:class:`bytes`] The plaintext message to encrypt
  202. :param nonce: [:class:`bytes`] The nonce to use in the encryption
  203. :param encoder: The encoder to use to encode the ciphertext
  204. :rtype: [:class:`nacl.utils.EncryptedMessage`]
  205. """
  206. if nonce is None:
  207. nonce = random(self.NONCE_SIZE)
  208. if len(nonce) != self.NONCE_SIZE:
  209. raise exc.ValueError(
  210. "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
  211. )
  212. ciphertext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_encrypt(
  213. plaintext, aad, nonce, self._key
  214. )
  215. encoded_nonce = encoder.encode(nonce)
  216. encoded_ciphertext = encoder.encode(ciphertext)
  217. return EncryptedMessage._from_parts(
  218. encoded_nonce,
  219. encoded_ciphertext,
  220. encoder.encode(nonce + ciphertext),
  221. )
  222. def decrypt(
  223. self,
  224. ciphertext: bytes,
  225. aad: bytes = b"",
  226. nonce: Optional[bytes] = None,
  227. encoder: encoding.Encoder = encoding.RawEncoder,
  228. ) -> bytes:
  229. """
  230. Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
  231. parameter or implicitly, when omitted, as part of the ciphertext) and
  232. returns the plaintext message.
  233. :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
  234. :param nonce: [:class:`bytes`] The nonce used when encrypting the
  235. ciphertext
  236. :param encoder: The encoder used to decode the ciphertext.
  237. :rtype: [:class:`bytes`]
  238. """
  239. # Decode our ciphertext
  240. ciphertext = encoder.decode(ciphertext)
  241. if nonce is None:
  242. # If we were given the nonce and ciphertext combined, split them.
  243. nonce = ciphertext[: self.NONCE_SIZE]
  244. ciphertext = ciphertext[self.NONCE_SIZE :]
  245. if len(nonce) != self.NONCE_SIZE:
  246. raise exc.ValueError(
  247. "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
  248. )
  249. plaintext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(
  250. ciphertext, aad, nonce, self._key
  251. )
  252. return plaintext