crypto_box.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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 Tuple
  15. from nacl import exceptions as exc
  16. from nacl._sodium import ffi, lib
  17. from nacl.exceptions import ensure
  18. __all__ = ["crypto_box_keypair", "crypto_box"]
  19. crypto_box_SECRETKEYBYTES: int = lib.crypto_box_secretkeybytes()
  20. crypto_box_PUBLICKEYBYTES: int = lib.crypto_box_publickeybytes()
  21. crypto_box_SEEDBYTES: int = lib.crypto_box_seedbytes()
  22. crypto_box_NONCEBYTES: int = lib.crypto_box_noncebytes()
  23. crypto_box_ZEROBYTES: int = lib.crypto_box_zerobytes()
  24. crypto_box_BOXZEROBYTES: int = lib.crypto_box_boxzerobytes()
  25. crypto_box_BEFORENMBYTES: int = lib.crypto_box_beforenmbytes()
  26. crypto_box_SEALBYTES: int = lib.crypto_box_sealbytes()
  27. def crypto_box_keypair() -> Tuple[bytes, bytes]:
  28. """
  29. Returns a randomly generated public and secret key.
  30. :rtype: (bytes(public_key), bytes(secret_key))
  31. """
  32. pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
  33. sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)
  34. rc = lib.crypto_box_keypair(pk, sk)
  35. ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
  36. return (
  37. ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
  38. ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
  39. )
  40. def crypto_box_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
  41. """
  42. Returns a (public, secret) keypair deterministically generated
  43. from an input ``seed``.
  44. .. warning:: The seed **must** be high-entropy; therefore,
  45. its generator **must** be a cryptographic quality
  46. random function like, for example, :func:`~nacl.utils.random`.
  47. .. warning:: The seed **must** be protected and remain secret.
  48. Anyone who knows the seed is really in possession of
  49. the corresponding PrivateKey.
  50. :param seed: bytes
  51. :rtype: (bytes(public_key), bytes(secret_key))
  52. """
  53. ensure(isinstance(seed, bytes), "seed must be bytes", raising=TypeError)
  54. if len(seed) != crypto_box_SEEDBYTES:
  55. raise exc.ValueError("Invalid seed")
  56. pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
  57. sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)
  58. rc = lib.crypto_box_seed_keypair(pk, sk, seed)
  59. ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
  60. return (
  61. ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
  62. ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
  63. )
  64. def crypto_box(message: bytes, nonce: bytes, pk: bytes, sk: bytes) -> bytes:
  65. """
  66. Encrypts and returns a message ``message`` using the secret key ``sk``,
  67. public key ``pk``, and the nonce ``nonce``.
  68. :param message: bytes
  69. :param nonce: bytes
  70. :param pk: bytes
  71. :param sk: bytes
  72. :rtype: bytes
  73. """
  74. if len(nonce) != crypto_box_NONCEBYTES:
  75. raise exc.ValueError("Invalid nonce size")
  76. if len(pk) != crypto_box_PUBLICKEYBYTES:
  77. raise exc.ValueError("Invalid public key")
  78. if len(sk) != crypto_box_SECRETKEYBYTES:
  79. raise exc.ValueError("Invalid secret key")
  80. padded = (b"\x00" * crypto_box_ZEROBYTES) + message
  81. ciphertext = ffi.new("unsigned char[]", len(padded))
  82. rc = lib.crypto_box(ciphertext, padded, len(padded), nonce, pk, sk)
  83. ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
  84. return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
  85. def crypto_box_open(
  86. ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes
  87. ) -> bytes:
  88. """
  89. Decrypts and returns an encrypted message ``ciphertext``, using the secret
  90. key ``sk``, public key ``pk``, and the nonce ``nonce``.
  91. :param ciphertext: bytes
  92. :param nonce: bytes
  93. :param pk: bytes
  94. :param sk: bytes
  95. :rtype: bytes
  96. """
  97. if len(nonce) != crypto_box_NONCEBYTES:
  98. raise exc.ValueError("Invalid nonce size")
  99. if len(pk) != crypto_box_PUBLICKEYBYTES:
  100. raise exc.ValueError("Invalid public key")
  101. if len(sk) != crypto_box_SECRETKEYBYTES:
  102. raise exc.ValueError("Invalid secret key")
  103. padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
  104. plaintext = ffi.new("unsigned char[]", len(padded))
  105. res = lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk)
  106. ensure(
  107. res == 0,
  108. "An error occurred trying to decrypt the message",
  109. raising=exc.CryptoError,
  110. )
  111. return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
  112. def crypto_box_beforenm(pk: bytes, sk: bytes) -> bytes:
  113. """
  114. Computes and returns the shared key for the public key ``pk`` and the
  115. secret key ``sk``. This can be used to speed up operations where the same
  116. set of keys is going to be used multiple times.
  117. :param pk: bytes
  118. :param sk: bytes
  119. :rtype: bytes
  120. """
  121. if len(pk) != crypto_box_PUBLICKEYBYTES:
  122. raise exc.ValueError("Invalid public key")
  123. if len(sk) != crypto_box_SECRETKEYBYTES:
  124. raise exc.ValueError("Invalid secret key")
  125. k = ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES)
  126. rc = lib.crypto_box_beforenm(k, pk, sk)
  127. ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
  128. return ffi.buffer(k, crypto_box_BEFORENMBYTES)[:]
  129. def crypto_box_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes:
  130. """
  131. Encrypts and returns the message ``message`` using the shared key ``k`` and
  132. the nonce ``nonce``.
  133. :param message: bytes
  134. :param nonce: bytes
  135. :param k: bytes
  136. :rtype: bytes
  137. """
  138. if len(nonce) != crypto_box_NONCEBYTES:
  139. raise exc.ValueError("Invalid nonce")
  140. if len(k) != crypto_box_BEFORENMBYTES:
  141. raise exc.ValueError("Invalid shared key")
  142. padded = b"\x00" * crypto_box_ZEROBYTES + message
  143. ciphertext = ffi.new("unsigned char[]", len(padded))
  144. rc = lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k)
  145. ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
  146. return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
  147. def crypto_box_open_afternm(
  148. ciphertext: bytes, nonce: bytes, k: bytes
  149. ) -> bytes:
  150. """
  151. Decrypts and returns the encrypted message ``ciphertext``, using the shared
  152. key ``k`` and the nonce ``nonce``.
  153. :param ciphertext: bytes
  154. :param nonce: bytes
  155. :param k: bytes
  156. :rtype: bytes
  157. """
  158. if len(nonce) != crypto_box_NONCEBYTES:
  159. raise exc.ValueError("Invalid nonce")
  160. if len(k) != crypto_box_BEFORENMBYTES:
  161. raise exc.ValueError("Invalid shared key")
  162. padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
  163. plaintext = ffi.new("unsigned char[]", len(padded))
  164. res = lib.crypto_box_open_afternm(plaintext, padded, len(padded), nonce, k)
  165. ensure(
  166. res == 0,
  167. "An error occurred trying to decrypt the message",
  168. raising=exc.CryptoError,
  169. )
  170. return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
  171. def crypto_box_seal(message: bytes, pk: bytes) -> bytes:
  172. """
  173. Encrypts and returns a message ``message`` using an ephemeral secret key
  174. and the public key ``pk``.
  175. The ephemeral public key, which is embedded in the sealed box, is also
  176. used, in combination with ``pk``, to derive the nonce needed for the
  177. underlying box construct.
  178. :param message: bytes
  179. :param pk: bytes
  180. :rtype: bytes
  181. .. versionadded:: 1.2
  182. """
  183. ensure(
  184. isinstance(message, bytes),
  185. "input message must be bytes",
  186. raising=TypeError,
  187. )
  188. ensure(
  189. isinstance(pk, bytes), "public key must be bytes", raising=TypeError
  190. )
  191. if len(pk) != crypto_box_PUBLICKEYBYTES:
  192. raise exc.ValueError("Invalid public key")
  193. _mlen = len(message)
  194. _clen = crypto_box_SEALBYTES + _mlen
  195. ciphertext = ffi.new("unsigned char[]", _clen)
  196. rc = lib.crypto_box_seal(ciphertext, message, _mlen, pk)
  197. ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
  198. return ffi.buffer(ciphertext, _clen)[:]
  199. def crypto_box_seal_open(ciphertext: bytes, pk: bytes, sk: bytes) -> bytes:
  200. """
  201. Decrypts and returns an encrypted message ``ciphertext``, using the
  202. recipent's secret key ``sk`` and the sender's ephemeral public key
  203. embedded in the sealed box. The box contruct nonce is derived from
  204. the recipient's public key ``pk`` and the sender's public key.
  205. :param ciphertext: bytes
  206. :param pk: bytes
  207. :param sk: bytes
  208. :rtype: bytes
  209. .. versionadded:: 1.2
  210. """
  211. ensure(
  212. isinstance(ciphertext, bytes),
  213. "input ciphertext must be bytes",
  214. raising=TypeError,
  215. )
  216. ensure(
  217. isinstance(pk, bytes), "public key must be bytes", raising=TypeError
  218. )
  219. ensure(
  220. isinstance(sk, bytes), "secret key must be bytes", raising=TypeError
  221. )
  222. if len(pk) != crypto_box_PUBLICKEYBYTES:
  223. raise exc.ValueError("Invalid public key")
  224. if len(sk) != crypto_box_SECRETKEYBYTES:
  225. raise exc.ValueError("Invalid secret key")
  226. _clen = len(ciphertext)
  227. ensure(
  228. _clen >= crypto_box_SEALBYTES,
  229. ("Input cyphertext must be at least {} long").format(
  230. crypto_box_SEALBYTES
  231. ),
  232. raising=exc.TypeError,
  233. )
  234. _mlen = _clen - crypto_box_SEALBYTES
  235. # zero-length malloc results are implementation.dependent
  236. plaintext = ffi.new("unsigned char[]", max(1, _mlen))
  237. res = lib.crypto_box_seal_open(plaintext, ciphertext, _clen, pk, sk)
  238. ensure(
  239. res == 0,
  240. "An error occurred trying to decrypt the message",
  241. raising=exc.CryptoError,
  242. )
  243. return ffi.buffer(plaintext, _mlen)[:]