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.
14from typing import Tuple
15
16from nacl import exceptions as exc
17from nacl._sodium import ffi, lib
18from nacl.exceptions import ensure
19
20
21__all__ = ["crypto_box_keypair", "crypto_box"]
22
23
24crypto_box_SECRETKEYBYTES: int = lib.crypto_box_secretkeybytes()
25crypto_box_PUBLICKEYBYTES: int = lib.crypto_box_publickeybytes()
26crypto_box_SEEDBYTES: int = lib.crypto_box_seedbytes()
27crypto_box_NONCEBYTES: int = lib.crypto_box_noncebytes()
28crypto_box_ZEROBYTES: int = lib.crypto_box_zerobytes()
29crypto_box_BOXZEROBYTES: int = lib.crypto_box_boxzerobytes()
30crypto_box_BEFORENMBYTES: int = lib.crypto_box_beforenmbytes()
31crypto_box_SEALBYTES: int = lib.crypto_box_sealbytes()
32crypto_box_MACBYTES: int = lib.crypto_box_macbytes()
33
34
35def crypto_box_keypair() -> Tuple[bytes, bytes]:
36 """
37 Returns a randomly generated public and secret key.
38
39 :rtype: (bytes(public_key), bytes(secret_key))
40 """
41 pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
42 sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)
43
44 rc = lib.crypto_box_keypair(pk, sk)
45 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
46
47 return (
48 ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
49 ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
50 )
51
52
53def crypto_box_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
54 """
55 Returns a (public, secret) key pair deterministically generated
56 from an input ``seed``.
57
58 .. warning:: The seed **must** be high-entropy; therefore,
59 its generator **must** be a cryptographic quality
60 random function like, for example, :func:`~nacl.utils.random`.
61
62 .. warning:: The seed **must** be protected and remain secret.
63 Anyone who knows the seed is really in possession of
64 the corresponding PrivateKey.
65
66
67 :param seed: bytes
68 :rtype: (bytes(public_key), bytes(secret_key))
69 """
70 ensure(isinstance(seed, bytes), "seed must be bytes", raising=TypeError)
71
72 if len(seed) != crypto_box_SEEDBYTES:
73 raise exc.ValueError("Invalid seed")
74
75 pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
76 sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)
77
78 rc = lib.crypto_box_seed_keypair(pk, sk, seed)
79 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
80
81 return (
82 ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
83 ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
84 )
85
86
87def crypto_box(message: bytes, nonce: bytes, pk: bytes, sk: bytes) -> bytes:
88 """
89 Encrypts and returns a message ``message`` using the secret key ``sk``,
90 public key ``pk``, and the nonce ``nonce``.
91
92 :param message: bytes
93 :param nonce: bytes
94 :param pk: bytes
95 :param sk: bytes
96 :rtype: bytes
97 """
98 if len(nonce) != crypto_box_NONCEBYTES:
99 raise exc.ValueError("Invalid nonce size")
100
101 if len(pk) != crypto_box_PUBLICKEYBYTES:
102 raise exc.ValueError("Invalid public key")
103
104 if len(sk) != crypto_box_SECRETKEYBYTES:
105 raise exc.ValueError("Invalid secret key")
106
107 padded = (b"\x00" * crypto_box_ZEROBYTES) + message
108 ciphertext = ffi.new("unsigned char[]", len(padded))
109
110 rc = lib.crypto_box(ciphertext, padded, len(padded), nonce, pk, sk)
111 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
112
113 return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
114
115
116def crypto_box_open(
117 ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes
118) -> bytes:
119 """
120 Decrypts and returns an encrypted message ``ciphertext``, using the secret
121 key ``sk``, public key ``pk``, and the nonce ``nonce``.
122
123 :param ciphertext: bytes
124 :param nonce: bytes
125 :param pk: bytes
126 :param sk: bytes
127 :rtype: bytes
128 """
129 if len(nonce) != crypto_box_NONCEBYTES:
130 raise exc.ValueError("Invalid nonce size")
131
132 if len(pk) != crypto_box_PUBLICKEYBYTES:
133 raise exc.ValueError("Invalid public key")
134
135 if len(sk) != crypto_box_SECRETKEYBYTES:
136 raise exc.ValueError("Invalid secret key")
137
138 padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
139 plaintext = ffi.new("unsigned char[]", len(padded))
140
141 res = lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk)
142 ensure(
143 res == 0,
144 "An error occurred trying to decrypt the message",
145 raising=exc.CryptoError,
146 )
147
148 return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
149
150
151def crypto_box_beforenm(pk: bytes, sk: bytes) -> bytes:
152 """
153 Computes and returns the shared key for the public key ``pk`` and the
154 secret key ``sk``. This can be used to speed up operations where the same
155 set of keys is going to be used multiple times.
156
157 :param pk: bytes
158 :param sk: bytes
159 :rtype: bytes
160 """
161 if len(pk) != crypto_box_PUBLICKEYBYTES:
162 raise exc.ValueError("Invalid public key")
163
164 if len(sk) != crypto_box_SECRETKEYBYTES:
165 raise exc.ValueError("Invalid secret key")
166
167 k = ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES)
168
169 rc = lib.crypto_box_beforenm(k, pk, sk)
170 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
171
172 return ffi.buffer(k, crypto_box_BEFORENMBYTES)[:]
173
174
175def crypto_box_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes:
176 """
177 Encrypts and returns the message ``message`` using the shared key ``k`` and
178 the nonce ``nonce``.
179
180 :param message: bytes
181 :param nonce: bytes
182 :param k: bytes
183 :rtype: bytes
184 """
185 if len(nonce) != crypto_box_NONCEBYTES:
186 raise exc.ValueError("Invalid nonce")
187
188 if len(k) != crypto_box_BEFORENMBYTES:
189 raise exc.ValueError("Invalid shared key")
190
191 padded = b"\x00" * crypto_box_ZEROBYTES + message
192 ciphertext = ffi.new("unsigned char[]", len(padded))
193
194 rc = lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k)
195 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
196
197 return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
198
199
200def crypto_box_open_afternm(
201 ciphertext: bytes, nonce: bytes, k: bytes
202) -> bytes:
203 """
204 Decrypts and returns the encrypted message ``ciphertext``, using the shared
205 key ``k`` and the nonce ``nonce``.
206
207 :param ciphertext: bytes
208 :param nonce: bytes
209 :param k: bytes
210 :rtype: bytes
211 """
212 if len(nonce) != crypto_box_NONCEBYTES:
213 raise exc.ValueError("Invalid nonce")
214
215 if len(k) != crypto_box_BEFORENMBYTES:
216 raise exc.ValueError("Invalid shared key")
217
218 padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
219 plaintext = ffi.new("unsigned char[]", len(padded))
220
221 res = lib.crypto_box_open_afternm(plaintext, padded, len(padded), nonce, k)
222 ensure(
223 res == 0,
224 "An error occurred trying to decrypt the message",
225 raising=exc.CryptoError,
226 )
227
228 return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
229
230
231def crypto_box_easy(
232 message: bytes, nonce: bytes, pk: bytes, sk: bytes
233) -> bytes:
234 """
235 Encrypts and returns a message ``message`` using the secret key ``sk``,
236 public key ``pk``, and the nonce ``nonce``.
237
238 :param message: bytes
239 :param nonce: bytes
240 :param pk: bytes
241 :param sk: bytes
242 :rtype: bytes
243 """
244 if len(nonce) != crypto_box_NONCEBYTES:
245 raise exc.ValueError("Invalid nonce size")
246
247 if len(pk) != crypto_box_PUBLICKEYBYTES:
248 raise exc.ValueError("Invalid public key")
249
250 if len(sk) != crypto_box_SECRETKEYBYTES:
251 raise exc.ValueError("Invalid secret key")
252
253 _mlen = len(message)
254 _clen = crypto_box_MACBYTES + _mlen
255
256 ciphertext = ffi.new("unsigned char[]", _clen)
257
258 rc = lib.crypto_box_easy(ciphertext, message, _mlen, nonce, pk, sk)
259 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
260
261 return ffi.buffer(ciphertext, _clen)[:]
262
263
264def crypto_box_open_easy(
265 ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes
266) -> bytes:
267 """
268 Decrypts and returns an encrypted message ``ciphertext``, using the secret
269 key ``sk``, public key ``pk``, and the nonce ``nonce``.
270
271 :param ciphertext: bytes
272 :param nonce: bytes
273 :param pk: bytes
274 :param sk: bytes
275 :rtype: bytes
276 """
277 if len(nonce) != crypto_box_NONCEBYTES:
278 raise exc.ValueError("Invalid nonce size")
279
280 if len(pk) != crypto_box_PUBLICKEYBYTES:
281 raise exc.ValueError("Invalid public key")
282
283 if len(sk) != crypto_box_SECRETKEYBYTES:
284 raise exc.ValueError("Invalid secret key")
285
286 _clen = len(ciphertext)
287
288 ensure(
289 _clen >= crypto_box_MACBYTES,
290 "Input ciphertext must be at least {} long".format(
291 crypto_box_MACBYTES
292 ),
293 raising=exc.TypeError,
294 )
295
296 _mlen = _clen - crypto_box_MACBYTES
297
298 plaintext = ffi.new("unsigned char[]", max(1, _mlen))
299
300 res = lib.crypto_box_open_easy(plaintext, ciphertext, _clen, nonce, pk, sk)
301 ensure(
302 res == 0,
303 "An error occurred trying to decrypt the message",
304 raising=exc.CryptoError,
305 )
306
307 return ffi.buffer(plaintext, _mlen)[:]
308
309
310def crypto_box_easy_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes:
311 """
312 Encrypts and returns the message ``message`` using the shared key ``k`` and
313 the nonce ``nonce``.
314
315 :param message: bytes
316 :param nonce: bytes
317 :param k: bytes
318 :rtype: bytes
319 """
320 if len(nonce) != crypto_box_NONCEBYTES:
321 raise exc.ValueError("Invalid nonce")
322
323 if len(k) != crypto_box_BEFORENMBYTES:
324 raise exc.ValueError("Invalid shared key")
325
326 _mlen = len(message)
327 _clen = crypto_box_MACBYTES + _mlen
328
329 ciphertext = ffi.new("unsigned char[]", _clen)
330
331 rc = lib.crypto_box_easy_afternm(ciphertext, message, _mlen, nonce, k)
332 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
333
334 return ffi.buffer(ciphertext, _clen)[:]
335
336
337def crypto_box_open_easy_afternm(
338 ciphertext: bytes, nonce: bytes, k: bytes
339) -> bytes:
340 """
341 Decrypts and returns the encrypted message ``ciphertext``, using the shared
342 key ``k`` and the nonce ``nonce``.
343
344 :param ciphertext: bytes
345 :param nonce: bytes
346 :param k: bytes
347 :rtype: bytes
348 """
349 if len(nonce) != crypto_box_NONCEBYTES:
350 raise exc.ValueError("Invalid nonce")
351
352 if len(k) != crypto_box_BEFORENMBYTES:
353 raise exc.ValueError("Invalid shared key")
354
355 _clen = len(ciphertext)
356
357 ensure(
358 _clen >= crypto_box_MACBYTES,
359 "Input ciphertext must be at least {} long".format(
360 crypto_box_MACBYTES
361 ),
362 raising=exc.TypeError,
363 )
364
365 _mlen = _clen - crypto_box_MACBYTES
366
367 plaintext = ffi.new("unsigned char[]", max(1, _mlen))
368
369 res = lib.crypto_box_open_easy_afternm(
370 plaintext, ciphertext, _clen, nonce, k
371 )
372 ensure(
373 res == 0,
374 "An error occurred trying to decrypt the message",
375 raising=exc.CryptoError,
376 )
377
378 return ffi.buffer(plaintext, _mlen)[:]
379
380
381def crypto_box_seal(message: bytes, pk: bytes) -> bytes:
382 """
383 Encrypts and returns a message ``message`` using an ephemeral secret key
384 and the public key ``pk``.
385 The ephemeral public key, which is embedded in the sealed box, is also
386 used, in combination with ``pk``, to derive the nonce needed for the
387 underlying box construct.
388
389 :param message: bytes
390 :param pk: bytes
391 :rtype: bytes
392
393 .. versionadded:: 1.2
394 """
395 ensure(
396 isinstance(message, bytes),
397 "input message must be bytes",
398 raising=TypeError,
399 )
400
401 ensure(
402 isinstance(pk, bytes), "public key must be bytes", raising=TypeError
403 )
404
405 if len(pk) != crypto_box_PUBLICKEYBYTES:
406 raise exc.ValueError("Invalid public key")
407
408 _mlen = len(message)
409 _clen = crypto_box_SEALBYTES + _mlen
410
411 ciphertext = ffi.new("unsigned char[]", _clen)
412
413 rc = lib.crypto_box_seal(ciphertext, message, _mlen, pk)
414 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
415
416 return ffi.buffer(ciphertext, _clen)[:]
417
418
419def crypto_box_seal_open(ciphertext: bytes, pk: bytes, sk: bytes) -> bytes:
420 """
421 Decrypts and returns an encrypted message ``ciphertext``, using the
422 recipent's secret key ``sk`` and the sender's ephemeral public key
423 embedded in the sealed box. The box construct nonce is derived from
424 the recipient's public key ``pk`` and the sender's public key.
425
426 :param ciphertext: bytes
427 :param pk: bytes
428 :param sk: bytes
429 :rtype: bytes
430
431 .. versionadded:: 1.2
432 """
433 ensure(
434 isinstance(ciphertext, bytes),
435 "input ciphertext must be bytes",
436 raising=TypeError,
437 )
438
439 ensure(
440 isinstance(pk, bytes), "public key must be bytes", raising=TypeError
441 )
442
443 ensure(
444 isinstance(sk, bytes), "secret key must be bytes", raising=TypeError
445 )
446
447 if len(pk) != crypto_box_PUBLICKEYBYTES:
448 raise exc.ValueError("Invalid public key")
449
450 if len(sk) != crypto_box_SECRETKEYBYTES:
451 raise exc.ValueError("Invalid secret key")
452
453 _clen = len(ciphertext)
454
455 ensure(
456 _clen >= crypto_box_SEALBYTES,
457 ("Input ciphertext must be at least {} long").format(
458 crypto_box_SEALBYTES
459 ),
460 raising=exc.TypeError,
461 )
462
463 _mlen = _clen - crypto_box_SEALBYTES
464
465 # zero-length malloc results are implementation.dependent
466 plaintext = ffi.new("unsigned char[]", max(1, _mlen))
467
468 res = lib.crypto_box_seal_open(plaintext, ciphertext, _clen, pk, sk)
469 ensure(
470 res == 0,
471 "An error occurred trying to decrypt the message",
472 raising=exc.CryptoError,
473 )
474
475 return ffi.buffer(plaintext, _mlen)[:]