crypto_secretstream.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. # Copyright 2013-2018 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 ByteString, Optional, Tuple, cast
  15. from nacl import exceptions as exc
  16. from nacl._sodium import ffi, lib
  17. from nacl.exceptions import ensure
  18. crypto_secretstream_xchacha20poly1305_ABYTES: int = (
  19. lib.crypto_secretstream_xchacha20poly1305_abytes()
  20. )
  21. crypto_secretstream_xchacha20poly1305_HEADERBYTES: int = (
  22. lib.crypto_secretstream_xchacha20poly1305_headerbytes()
  23. )
  24. crypto_secretstream_xchacha20poly1305_KEYBYTES: int = (
  25. lib.crypto_secretstream_xchacha20poly1305_keybytes()
  26. )
  27. crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: int = (
  28. lib.crypto_secretstream_xchacha20poly1305_messagebytes_max()
  29. )
  30. crypto_secretstream_xchacha20poly1305_STATEBYTES: int = (
  31. lib.crypto_secretstream_xchacha20poly1305_statebytes()
  32. )
  33. crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: int = (
  34. lib.crypto_secretstream_xchacha20poly1305_tag_message()
  35. )
  36. crypto_secretstream_xchacha20poly1305_TAG_PUSH: int = (
  37. lib.crypto_secretstream_xchacha20poly1305_tag_push()
  38. )
  39. crypto_secretstream_xchacha20poly1305_TAG_REKEY: int = (
  40. lib.crypto_secretstream_xchacha20poly1305_tag_rekey()
  41. )
  42. crypto_secretstream_xchacha20poly1305_TAG_FINAL: int = (
  43. lib.crypto_secretstream_xchacha20poly1305_tag_final()
  44. )
  45. def crypto_secretstream_xchacha20poly1305_keygen() -> bytes:
  46. """
  47. Generate a key for use with
  48. :func:`.crypto_secretstream_xchacha20poly1305_init_push`.
  49. """
  50. keybuf = ffi.new(
  51. "unsigned char[]",
  52. crypto_secretstream_xchacha20poly1305_KEYBYTES,
  53. )
  54. lib.crypto_secretstream_xchacha20poly1305_keygen(keybuf)
  55. return ffi.buffer(keybuf)[:]
  56. class crypto_secretstream_xchacha20poly1305_state:
  57. """
  58. An object wrapping the crypto_secretstream_xchacha20poly1305 state.
  59. """
  60. __slots__ = ["statebuf", "rawbuf", "tagbuf"]
  61. def __init__(self) -> None:
  62. """Initialize a clean state object."""
  63. self.statebuf: ByteString = ffi.new(
  64. "unsigned char[]",
  65. crypto_secretstream_xchacha20poly1305_STATEBYTES,
  66. )
  67. self.rawbuf: Optional[ByteString] = None
  68. self.tagbuf: Optional[ByteString] = None
  69. def crypto_secretstream_xchacha20poly1305_init_push(
  70. state: crypto_secretstream_xchacha20poly1305_state, key: bytes
  71. ) -> bytes:
  72. """
  73. Initialize a crypto_secretstream_xchacha20poly1305 encryption buffer.
  74. :param state: a secretstream state object
  75. :type state: crypto_secretstream_xchacha20poly1305_state
  76. :param key: must be
  77. :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
  78. :type key: bytes
  79. :return: header
  80. :rtype: bytes
  81. """
  82. ensure(
  83. isinstance(state, crypto_secretstream_xchacha20poly1305_state),
  84. "State must be a crypto_secretstream_xchacha20poly1305_state object",
  85. raising=exc.TypeError,
  86. )
  87. ensure(
  88. isinstance(key, bytes),
  89. "Key must be a bytes sequence",
  90. raising=exc.TypeError,
  91. )
  92. ensure(
  93. len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
  94. "Invalid key length",
  95. raising=exc.ValueError,
  96. )
  97. headerbuf = ffi.new(
  98. "unsigned char []",
  99. crypto_secretstream_xchacha20poly1305_HEADERBYTES,
  100. )
  101. rc = lib.crypto_secretstream_xchacha20poly1305_init_push(
  102. state.statebuf, headerbuf, key
  103. )
  104. ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
  105. return ffi.buffer(headerbuf)[:]
  106. def crypto_secretstream_xchacha20poly1305_push(
  107. state: crypto_secretstream_xchacha20poly1305_state,
  108. m: bytes,
  109. ad: Optional[bytes] = None,
  110. tag: int = crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
  111. ) -> bytes:
  112. """
  113. Add an encrypted message to the secret stream.
  114. :param state: a secretstream state object
  115. :type state: crypto_secretstream_xchacha20poly1305_state
  116. :param m: the message to encrypt, the maximum length of an individual
  117. message is
  118. :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX`.
  119. :type m: bytes
  120. :param ad: additional data to include in the authentication tag
  121. :type ad: bytes or None
  122. :param tag: the message tag, usually
  123. :data:`.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE` or
  124. :data:`.crypto_secretstream_xchacha20poly1305_TAG_FINAL`.
  125. :type tag: int
  126. :return: ciphertext
  127. :rtype: bytes
  128. """
  129. ensure(
  130. isinstance(state, crypto_secretstream_xchacha20poly1305_state),
  131. "State must be a crypto_secretstream_xchacha20poly1305_state object",
  132. raising=exc.TypeError,
  133. )
  134. ensure(isinstance(m, bytes), "Message is not bytes", raising=exc.TypeError)
  135. ensure(
  136. len(m) <= crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX,
  137. "Message is too long",
  138. raising=exc.ValueError,
  139. )
  140. ensure(
  141. ad is None or isinstance(ad, bytes),
  142. "Additional data must be bytes or None",
  143. raising=exc.TypeError,
  144. )
  145. clen = len(m) + crypto_secretstream_xchacha20poly1305_ABYTES
  146. if state.rawbuf is None or len(state.rawbuf) < clen:
  147. state.rawbuf = ffi.new("unsigned char[]", clen)
  148. if ad is None:
  149. ad = ffi.NULL
  150. adlen = 0
  151. else:
  152. adlen = len(ad)
  153. rc = lib.crypto_secretstream_xchacha20poly1305_push(
  154. state.statebuf,
  155. state.rawbuf,
  156. ffi.NULL,
  157. m,
  158. len(m),
  159. ad,
  160. adlen,
  161. tag,
  162. )
  163. ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
  164. return ffi.buffer(state.rawbuf, clen)[:]
  165. def crypto_secretstream_xchacha20poly1305_init_pull(
  166. state: crypto_secretstream_xchacha20poly1305_state,
  167. header: bytes,
  168. key: bytes,
  169. ) -> None:
  170. """
  171. Initialize a crypto_secretstream_xchacha20poly1305 decryption buffer.
  172. :param state: a secretstream state object
  173. :type state: crypto_secretstream_xchacha20poly1305_state
  174. :param header: must be
  175. :data:`.crypto_secretstream_xchacha20poly1305_HEADERBYTES` long
  176. :type header: bytes
  177. :param key: must be
  178. :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
  179. :type key: bytes
  180. """
  181. ensure(
  182. isinstance(state, crypto_secretstream_xchacha20poly1305_state),
  183. "State must be a crypto_secretstream_xchacha20poly1305_state object",
  184. raising=exc.TypeError,
  185. )
  186. ensure(
  187. isinstance(header, bytes),
  188. "Header must be a bytes sequence",
  189. raising=exc.TypeError,
  190. )
  191. ensure(
  192. len(header) == crypto_secretstream_xchacha20poly1305_HEADERBYTES,
  193. "Invalid header length",
  194. raising=exc.ValueError,
  195. )
  196. ensure(
  197. isinstance(key, bytes),
  198. "Key must be a bytes sequence",
  199. raising=exc.TypeError,
  200. )
  201. ensure(
  202. len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
  203. "Invalid key length",
  204. raising=exc.ValueError,
  205. )
  206. if state.tagbuf is None:
  207. state.tagbuf = ffi.new("unsigned char *")
  208. rc = lib.crypto_secretstream_xchacha20poly1305_init_pull(
  209. state.statebuf, header, key
  210. )
  211. ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
  212. def crypto_secretstream_xchacha20poly1305_pull(
  213. state: crypto_secretstream_xchacha20poly1305_state,
  214. c: bytes,
  215. ad: Optional[bytes] = None,
  216. ) -> Tuple[bytes, int]:
  217. """
  218. Read a decrypted message from the secret stream.
  219. :param state: a secretstream state object
  220. :type state: crypto_secretstream_xchacha20poly1305_state
  221. :param c: the ciphertext to decrypt, the maximum length of an individual
  222. ciphertext is
  223. :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX` +
  224. :data:`.crypto_secretstream_xchacha20poly1305_ABYTES`.
  225. :type c: bytes
  226. :param ad: additional data to include in the authentication tag
  227. :type ad: bytes or None
  228. :return: (message, tag)
  229. :rtype: (bytes, int)
  230. """
  231. ensure(
  232. isinstance(state, crypto_secretstream_xchacha20poly1305_state),
  233. "State must be a crypto_secretstream_xchacha20poly1305_state object",
  234. raising=exc.TypeError,
  235. )
  236. ensure(
  237. state.tagbuf is not None,
  238. (
  239. "State must be initialized using "
  240. "crypto_secretstream_xchacha20poly1305_init_pull"
  241. ),
  242. raising=exc.ValueError,
  243. )
  244. ensure(
  245. isinstance(c, bytes),
  246. "Ciphertext is not bytes",
  247. raising=exc.TypeError,
  248. )
  249. ensure(
  250. len(c) >= crypto_secretstream_xchacha20poly1305_ABYTES,
  251. "Ciphertext is too short",
  252. raising=exc.ValueError,
  253. )
  254. ensure(
  255. len(c)
  256. <= (
  257. crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX
  258. + crypto_secretstream_xchacha20poly1305_ABYTES
  259. ),
  260. "Ciphertext is too long",
  261. raising=exc.ValueError,
  262. )
  263. ensure(
  264. ad is None or isinstance(ad, bytes),
  265. "Additional data must be bytes or None",
  266. raising=exc.TypeError,
  267. )
  268. mlen = len(c) - crypto_secretstream_xchacha20poly1305_ABYTES
  269. if state.rawbuf is None or len(state.rawbuf) < mlen:
  270. state.rawbuf = ffi.new("unsigned char[]", mlen)
  271. if ad is None:
  272. ad = ffi.NULL
  273. adlen = 0
  274. else:
  275. adlen = len(ad)
  276. rc = lib.crypto_secretstream_xchacha20poly1305_pull(
  277. state.statebuf,
  278. state.rawbuf,
  279. ffi.NULL,
  280. state.tagbuf,
  281. c,
  282. len(c),
  283. ad,
  284. adlen,
  285. )
  286. ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
  287. # Cast safety: we `ensure` above that `state.tagbuf is not None`.
  288. return (
  289. ffi.buffer(state.rawbuf, mlen)[:],
  290. int(cast(bytes, state.tagbuf)[0]),
  291. )
  292. def crypto_secretstream_xchacha20poly1305_rekey(
  293. state: crypto_secretstream_xchacha20poly1305_state,
  294. ) -> None:
  295. """
  296. Explicitly change the encryption key in the stream.
  297. Normally the stream is re-keyed as needed or an explicit ``tag`` of
  298. :data:`.crypto_secretstream_xchacha20poly1305_TAG_REKEY` is added to a
  299. message to ensure forward secrecy, but this method can be used instead
  300. if the re-keying is controlled without adding the tag.
  301. :param state: a secretstream state object
  302. :type state: crypto_secretstream_xchacha20poly1305_state
  303. """
  304. ensure(
  305. isinstance(state, crypto_secretstream_xchacha20poly1305_state),
  306. "State must be a crypto_secretstream_xchacha20poly1305_state object",
  307. raising=exc.TypeError,
  308. )
  309. lib.crypto_secretstream_xchacha20poly1305_rekey(state.statebuf)