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