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()
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) keypair 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_seal(message: bytes, pk: bytes) -> bytes:
231 """
232 Encrypts and returns a message ``message`` using an ephemeral secret key
233 and the public key ``pk``.
234 The ephemeral public key, which is embedded in the sealed box, is also
235 used, in combination with ``pk``, to derive the nonce needed for the
236 underlying box construct.
237
238 :param message: bytes
239 :param pk: bytes
240 :rtype: bytes
241
242 .. versionadded:: 1.2
243 """
244 ensure(
245 isinstance(message, bytes),
246 "input message must be bytes",
247 raising=TypeError,
248 )
249
250 ensure(
251 isinstance(pk, bytes), "public key must be bytes", raising=TypeError
252 )
253
254 if len(pk) != crypto_box_PUBLICKEYBYTES:
255 raise exc.ValueError("Invalid public key")
256
257 _mlen = len(message)
258 _clen = crypto_box_SEALBYTES + _mlen
259
260 ciphertext = ffi.new("unsigned char[]", _clen)
261
262 rc = lib.crypto_box_seal(ciphertext, message, _mlen, pk)
263 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
264
265 return ffi.buffer(ciphertext, _clen)[:]
266
267
268def crypto_box_seal_open(ciphertext: bytes, pk: bytes, sk: bytes) -> bytes:
269 """
270 Decrypts and returns an encrypted message ``ciphertext``, using the
271 recipent's secret key ``sk`` and the sender's ephemeral public key
272 embedded in the sealed box. The box contruct nonce is derived from
273 the recipient's public key ``pk`` and the sender's public key.
274
275 :param ciphertext: bytes
276 :param pk: bytes
277 :param sk: bytes
278 :rtype: bytes
279
280 .. versionadded:: 1.2
281 """
282 ensure(
283 isinstance(ciphertext, bytes),
284 "input ciphertext must be bytes",
285 raising=TypeError,
286 )
287
288 ensure(
289 isinstance(pk, bytes), "public key must be bytes", raising=TypeError
290 )
291
292 ensure(
293 isinstance(sk, bytes), "secret key must be bytes", raising=TypeError
294 )
295
296 if len(pk) != crypto_box_PUBLICKEYBYTES:
297 raise exc.ValueError("Invalid public key")
298
299 if len(sk) != crypto_box_SECRETKEYBYTES:
300 raise exc.ValueError("Invalid secret key")
301
302 _clen = len(ciphertext)
303
304 ensure(
305 _clen >= crypto_box_SEALBYTES,
306 ("Input cyphertext must be at least {} long").format(
307 crypto_box_SEALBYTES
308 ),
309 raising=exc.TypeError,
310 )
311
312 _mlen = _clen - crypto_box_SEALBYTES
313
314 # zero-length malloc results are implementation.dependent
315 plaintext = ffi.new("unsigned char[]", max(1, _mlen))
316
317 res = lib.crypto_box_seal_open(plaintext, ciphertext, _clen, pk, sk)
318 ensure(
319 res == 0,
320 "An error occurred trying to decrypt the message",
321 raising=exc.CryptoError,
322 )
323
324 return ffi.buffer(plaintext, _mlen)[:]