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
15from __future__ import annotations
16
17import sys
18
19import nacl.exceptions as exc
20from nacl._sodium import ffi, lib
21from nacl.exceptions import ensure
22
23has_crypto_pwhash_scryptsalsa208sha256 = bool(
24 lib.PYNACL_HAS_CRYPTO_PWHASH_SCRYPTSALSA208SHA256
25)
26
27crypto_pwhash_scryptsalsa208sha256_STRPREFIX = b""
28crypto_pwhash_scryptsalsa208sha256_SALTBYTES = 0
29crypto_pwhash_scryptsalsa208sha256_STRBYTES = 0
30crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = 0
31crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = 0
32crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = 0
33crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = 0
34crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = 0
35crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = 0
36crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = 0
37crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = 0
38crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = 0
39crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = 0
40crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = 0
41crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = 0
42
43if has_crypto_pwhash_scryptsalsa208sha256:
44 crypto_pwhash_scryptsalsa208sha256_STRPREFIX = ffi.string(
45 ffi.cast("char *", lib.crypto_pwhash_scryptsalsa208sha256_strprefix())
46 )[:]
47 crypto_pwhash_scryptsalsa208sha256_SALTBYTES = (
48 lib.crypto_pwhash_scryptsalsa208sha256_saltbytes()
49 )
50 crypto_pwhash_scryptsalsa208sha256_STRBYTES = (
51 lib.crypto_pwhash_scryptsalsa208sha256_strbytes()
52 )
53 crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = (
54 lib.crypto_pwhash_scryptsalsa208sha256_passwd_min()
55 )
56 crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = (
57 lib.crypto_pwhash_scryptsalsa208sha256_passwd_max()
58 )
59 crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = (
60 lib.crypto_pwhash_scryptsalsa208sha256_bytes_min()
61 )
62 crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = (
63 lib.crypto_pwhash_scryptsalsa208sha256_bytes_max()
64 )
65 crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = (
66 lib.crypto_pwhash_scryptsalsa208sha256_memlimit_min()
67 )
68 crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = (
69 lib.crypto_pwhash_scryptsalsa208sha256_memlimit_max()
70 )
71 crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = (
72 lib.crypto_pwhash_scryptsalsa208sha256_opslimit_min()
73 )
74 crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = (
75 lib.crypto_pwhash_scryptsalsa208sha256_opslimit_max()
76 )
77 crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = (
78 lib.crypto_pwhash_scryptsalsa208sha256_opslimit_interactive()
79 )
80 crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = (
81 lib.crypto_pwhash_scryptsalsa208sha256_memlimit_interactive()
82 )
83 crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = (
84 lib.crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive()
85 )
86 crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = (
87 lib.crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive()
88 )
89
90crypto_pwhash_ALG_ARGON2I13: int = lib.crypto_pwhash_alg_argon2i13()
91crypto_pwhash_ALG_ARGON2ID13: int = lib.crypto_pwhash_alg_argon2id13()
92crypto_pwhash_ALG_DEFAULT: int = lib.crypto_pwhash_alg_default()
93
94crypto_pwhash_SALTBYTES: int = lib.crypto_pwhash_saltbytes()
95crypto_pwhash_STRBYTES: int = lib.crypto_pwhash_strbytes()
96
97crypto_pwhash_PASSWD_MIN: int = lib.crypto_pwhash_passwd_min()
98crypto_pwhash_PASSWD_MAX: int = lib.crypto_pwhash_passwd_max()
99crypto_pwhash_BYTES_MIN: int = lib.crypto_pwhash_bytes_min()
100crypto_pwhash_BYTES_MAX: int = lib.crypto_pwhash_bytes_max()
101
102crypto_pwhash_argon2i_STRPREFIX: bytes = ffi.string(
103 ffi.cast("char *", lib.crypto_pwhash_argon2i_strprefix())
104)[:]
105crypto_pwhash_argon2i_MEMLIMIT_MIN: int = (
106 lib.crypto_pwhash_argon2i_memlimit_min()
107)
108crypto_pwhash_argon2i_MEMLIMIT_MAX: int = (
109 lib.crypto_pwhash_argon2i_memlimit_max()
110)
111crypto_pwhash_argon2i_OPSLIMIT_MIN: int = (
112 lib.crypto_pwhash_argon2i_opslimit_min()
113)
114crypto_pwhash_argon2i_OPSLIMIT_MAX: int = (
115 lib.crypto_pwhash_argon2i_opslimit_max()
116)
117crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE: int = (
118 lib.crypto_pwhash_argon2i_opslimit_interactive()
119)
120crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE: int = (
121 lib.crypto_pwhash_argon2i_memlimit_interactive()
122)
123crypto_pwhash_argon2i_OPSLIMIT_MODERATE: int = (
124 lib.crypto_pwhash_argon2i_opslimit_moderate()
125)
126crypto_pwhash_argon2i_MEMLIMIT_MODERATE: int = (
127 lib.crypto_pwhash_argon2i_memlimit_moderate()
128)
129crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE: int = (
130 lib.crypto_pwhash_argon2i_opslimit_sensitive()
131)
132crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE: int = (
133 lib.crypto_pwhash_argon2i_memlimit_sensitive()
134)
135
136crypto_pwhash_argon2id_STRPREFIX: bytes = ffi.string(
137 ffi.cast("char *", lib.crypto_pwhash_argon2id_strprefix())
138)[:]
139crypto_pwhash_argon2id_MEMLIMIT_MIN: int = (
140 lib.crypto_pwhash_argon2id_memlimit_min()
141)
142crypto_pwhash_argon2id_MEMLIMIT_MAX: int = (
143 lib.crypto_pwhash_argon2id_memlimit_max()
144)
145crypto_pwhash_argon2id_OPSLIMIT_MIN: int = (
146 lib.crypto_pwhash_argon2id_opslimit_min()
147)
148crypto_pwhash_argon2id_OPSLIMIT_MAX: int = (
149 lib.crypto_pwhash_argon2id_opslimit_max()
150)
151crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE: int = (
152 lib.crypto_pwhash_argon2id_opslimit_interactive()
153)
154crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE: int = (
155 lib.crypto_pwhash_argon2id_memlimit_interactive()
156)
157crypto_pwhash_argon2id_OPSLIMIT_MODERATE: int = (
158 lib.crypto_pwhash_argon2id_opslimit_moderate()
159)
160crypto_pwhash_argon2id_MEMLIMIT_MODERATE: int = (
161 lib.crypto_pwhash_argon2id_memlimit_moderate()
162)
163crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE: int = (
164 lib.crypto_pwhash_argon2id_opslimit_sensitive()
165)
166crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE: int = (
167 lib.crypto_pwhash_argon2id_memlimit_sensitive()
168)
169
170SCRYPT_OPSLIMIT_INTERACTIVE = (
171 crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
172)
173SCRYPT_MEMLIMIT_INTERACTIVE = (
174 crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
175)
176SCRYPT_OPSLIMIT_SENSITIVE = (
177 crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
178)
179SCRYPT_MEMLIMIT_SENSITIVE = (
180 crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
181)
182SCRYPT_SALTBYTES = crypto_pwhash_scryptsalsa208sha256_SALTBYTES
183SCRYPT_STRBYTES = crypto_pwhash_scryptsalsa208sha256_STRBYTES
184
185SCRYPT_PR_MAX = (1 << 30) - 1
186LOG2_UINT64_MAX = 63
187UINT64_MAX = (1 << 64) - 1
188SCRYPT_MAX_MEM = 32 * (1024 * 1024)
189
190
191def _check_memory_occupation(
192 n: int, r: int, p: int, maxmem: int = SCRYPT_MAX_MEM
193) -> None:
194 ensure(r != 0, "Invalid block size", raising=exc.ValueError)
195
196 ensure(p != 0, "Invalid parallelization factor", raising=exc.ValueError)
197
198 ensure(
199 (n & (n - 1)) == 0,
200 "Cost factor must be a power of 2",
201 raising=exc.ValueError,
202 )
203
204 ensure(n > 1, "Cost factor must be at least 2", raising=exc.ValueError)
205
206 ensure(
207 p <= SCRYPT_PR_MAX / r,
208 f"p*r is greater than {SCRYPT_PR_MAX}",
209 raising=exc.ValueError,
210 )
211
212 ensure(n < (1 << (16 * r)), raising=exc.ValueError)
213
214 Blen = p * 128 * r
215
216 i = UINT64_MAX / 128
217
218 ensure(n + 2 <= i / r, raising=exc.ValueError)
219
220 Vlen = 32 * r * (n + 2) * 4
221
222 ensure(Blen <= UINT64_MAX - Vlen, raising=exc.ValueError)
223
224 ensure(Blen <= sys.maxsize - Vlen, raising=exc.ValueError)
225
226 ensure(
227 Blen + Vlen <= maxmem,
228 "Memory limit would be exceeded with the chosen n, r, p",
229 raising=exc.ValueError,
230 )
231
232
233def nacl_bindings_pick_scrypt_params(
234 opslimit: int, memlimit: int
235) -> tuple[int, int, int]:
236 """Python implementation of libsodium's pickparams"""
237
238 opslimit = max(opslimit, 32768)
239
240 r = 8
241
242 if opslimit < (memlimit // 32):
243 p = 1
244 maxn = opslimit // (4 * r)
245 for n_log2 in range(1, 63): # pragma: no branch
246 if (2**n_log2) > (maxn // 2):
247 break
248 else:
249 maxn = memlimit // (r * 128)
250 for n_log2 in range(1, 63): # pragma: no branch
251 if (2**n_log2) > maxn // 2:
252 break
253
254 maxrp = (opslimit // 4) // (2**n_log2)
255
256 maxrp = min(maxrp, 0x3FFFFFFF)
257
258 p = maxrp // r
259
260 return n_log2, r, p
261
262
263def crypto_pwhash_scryptsalsa208sha256_ll(
264 passwd: bytes,
265 salt: bytes,
266 n: int,
267 r: int,
268 p: int,
269 dklen: int = 64,
270 maxmem: int = SCRYPT_MAX_MEM,
271) -> bytes:
272 """
273 Derive a cryptographic key using the ``passwd`` and ``salt``
274 given as input.
275
276 The work factor can be tuned by by picking different
277 values for the parameters
278
279 :param bytes passwd:
280 :param bytes salt:
281 :param bytes salt: *must* be *exactly* :py:const:`.SALTBYTES` long
282 :param int dklen:
283 :param int opslimit:
284 :param int n:
285 :param int r: block size,
286 :param int p: the parallelism factor
287 :param int maxmem: the maximum available memory available for scrypt's
288 operations
289 :rtype: bytes
290 :raises nacl.exceptions.UnavailableError: If called when using a
291 minimal build of libsodium.
292 """
293 ensure(
294 has_crypto_pwhash_scryptsalsa208sha256,
295 "Not available in minimal build",
296 raising=exc.UnavailableError,
297 )
298
299 ensure(isinstance(n, int), raising=TypeError)
300 ensure(isinstance(r, int), raising=TypeError)
301 ensure(isinstance(p, int), raising=TypeError)
302
303 ensure(isinstance(passwd, bytes), raising=TypeError)
304 ensure(isinstance(salt, bytes), raising=TypeError)
305
306 _check_memory_occupation(n, r, p, maxmem)
307
308 buf = ffi.new("uint8_t[]", dklen)
309
310 ret = lib.crypto_pwhash_scryptsalsa208sha256_ll(
311 passwd, len(passwd), salt, len(salt), n, r, p, buf, dklen
312 )
313
314 ensure(
315 ret == 0,
316 "Unexpected failure in key derivation",
317 raising=exc.RuntimeError,
318 )
319
320 return ffi.buffer(ffi.cast("char *", buf), dklen)[:]
321
322
323def crypto_pwhash_scryptsalsa208sha256_str(
324 passwd: bytes,
325 opslimit: int = SCRYPT_OPSLIMIT_INTERACTIVE,
326 memlimit: int = SCRYPT_MEMLIMIT_INTERACTIVE,
327) -> bytes:
328 """
329 Derive a cryptographic key using the ``passwd`` and ``salt``
330 given as input, returning a string representation which includes
331 the salt and the tuning parameters.
332
333 The returned string can be directly stored as a password hash.
334
335 See :py:func:`.crypto_pwhash_scryptsalsa208sha256` for a short
336 discussion about ``opslimit`` and ``memlimit`` values.
337
338 :param bytes passwd:
339 :param int opslimit:
340 :param int memlimit:
341 :return: serialized key hash, including salt and tuning parameters
342 :rtype: bytes
343 :raises nacl.exceptions.UnavailableError: If called when using a
344 minimal build of libsodium.
345 """
346 ensure(
347 has_crypto_pwhash_scryptsalsa208sha256,
348 "Not available in minimal build",
349 raising=exc.UnavailableError,
350 )
351
352 buf = ffi.new("char[]", SCRYPT_STRBYTES)
353
354 ret = lib.crypto_pwhash_scryptsalsa208sha256_str(
355 buf, passwd, len(passwd), opslimit, memlimit
356 )
357
358 ensure(
359 ret == 0,
360 "Unexpected failure in password hashing",
361 raising=exc.RuntimeError,
362 )
363
364 return ffi.string(buf)
365
366
367def crypto_pwhash_scryptsalsa208sha256_str_verify(
368 passwd_hash: bytes, passwd: bytes
369) -> bool:
370 """
371 Verifies the ``passwd`` against the ``passwd_hash`` that was generated.
372 Returns True or False depending on the success
373
374 :param passwd_hash: bytes
375 :param passwd: bytes
376 :rtype: boolean
377 :raises nacl.exceptions.UnavailableError: If called when using a
378 minimal build of libsodium.
379 """
380 ensure(
381 has_crypto_pwhash_scryptsalsa208sha256,
382 "Not available in minimal build",
383 raising=exc.UnavailableError,
384 )
385
386 ensure(
387 len(passwd_hash) == SCRYPT_STRBYTES - 1,
388 "Invalid password hash",
389 raising=exc.ValueError,
390 )
391
392 ret = lib.crypto_pwhash_scryptsalsa208sha256_str_verify(
393 passwd_hash, passwd, len(passwd)
394 )
395 ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
396 # all went well, therefore:
397 return True
398
399
400def _check_argon2_limits_alg(opslimit: int, memlimit: int, alg: int) -> None:
401 if alg == crypto_pwhash_ALG_ARGON2I13:
402 if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN:
403 raise exc.ValueError(
404 f"memlimit must be at least {crypto_pwhash_argon2i_MEMLIMIT_MIN} bytes"
405 )
406 elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX:
407 raise exc.ValueError(
408 f"memlimit must be at most {crypto_pwhash_argon2i_MEMLIMIT_MAX} bytes"
409 )
410 if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN:
411 raise exc.ValueError(
412 f"opslimit must be at least {crypto_pwhash_argon2i_OPSLIMIT_MIN}"
413 )
414 elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX:
415 raise exc.ValueError(
416 f"opslimit must be at most {crypto_pwhash_argon2i_OPSLIMIT_MAX}"
417 )
418
419 elif alg == crypto_pwhash_ALG_ARGON2ID13:
420 if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN:
421 raise exc.ValueError(
422 f"memlimit must be at least {crypto_pwhash_argon2id_MEMLIMIT_MIN} bytes"
423 )
424 elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX:
425 raise exc.ValueError(
426 f"memlimit must be at most {crypto_pwhash_argon2id_MEMLIMIT_MAX} bytes"
427 )
428 if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN:
429 raise exc.ValueError(
430 f"opslimit must be at least {crypto_pwhash_argon2id_OPSLIMIT_MIN}"
431 )
432 elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX:
433 raise exc.ValueError(
434 f"opslimit must be at most {crypto_pwhash_argon2id_OPSLIMIT_MAX}"
435 )
436 else:
437 raise exc.TypeError("Unsupported algorithm")
438
439
440def crypto_pwhash_alg(
441 outlen: int,
442 passwd: bytes,
443 salt: bytes,
444 opslimit: int,
445 memlimit: int,
446 alg: int,
447) -> bytes:
448 """
449 Derive a raw cryptographic key using the ``passwd`` and the ``salt``
450 given as input to the ``alg`` algorithm.
451
452 :param outlen: the length of the derived key
453 :type outlen: int
454 :param passwd: The input password
455 :type passwd: bytes
456 :param salt:
457 :type salt: bytes
458 :param opslimit: computational cost
459 :type opslimit: int
460 :param memlimit: memory cost
461 :type memlimit: int
462 :param alg: algorithm identifier
463 :type alg: int
464 :return: derived key
465 :rtype: bytes
466 """
467 ensure(isinstance(outlen, int), raising=exc.TypeError)
468 ensure(isinstance(opslimit, int), raising=exc.TypeError)
469 ensure(isinstance(memlimit, int), raising=exc.TypeError)
470 ensure(isinstance(alg, int), raising=exc.TypeError)
471 ensure(isinstance(passwd, bytes), raising=exc.TypeError)
472
473 if len(salt) != crypto_pwhash_SALTBYTES:
474 raise exc.ValueError(
475 f"salt must be exactly {crypto_pwhash_SALTBYTES} bytes long"
476 )
477
478 if outlen < crypto_pwhash_BYTES_MIN:
479 raise exc.ValueError(
480 f"derived key must be at least {crypto_pwhash_BYTES_MIN} bytes long"
481 )
482
483 elif outlen > crypto_pwhash_BYTES_MAX:
484 raise exc.ValueError(
485 f"derived key must be at most {crypto_pwhash_BYTES_MAX} bytes long"
486 )
487
488 _check_argon2_limits_alg(opslimit, memlimit, alg)
489
490 outbuf = ffi.new("unsigned char[]", outlen)
491
492 ret = lib.crypto_pwhash(
493 outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg
494 )
495
496 ensure(
497 ret == 0,
498 "Unexpected failure in key derivation",
499 raising=exc.RuntimeError,
500 )
501
502 return ffi.buffer(outbuf, outlen)[:]
503
504
505def crypto_pwhash_str_alg(
506 passwd: bytes,
507 opslimit: int,
508 memlimit: int,
509 alg: int,
510) -> bytes:
511 """
512 Derive a cryptographic key using the ``passwd`` given as input
513 and a random salt, returning a string representation which
514 includes the salt, the tuning parameters and the used algorithm.
515
516 :param passwd: The input password
517 :type passwd: bytes
518 :param opslimit: computational cost
519 :type opslimit: int
520 :param memlimit: memory cost
521 :type memlimit: int
522 :param alg: The algorithm to use
523 :type alg: int
524 :return: serialized derived key and parameters
525 :rtype: bytes
526 """
527 ensure(isinstance(opslimit, int), raising=TypeError)
528 ensure(isinstance(memlimit, int), raising=TypeError)
529 ensure(isinstance(passwd, bytes), raising=TypeError)
530
531 _check_argon2_limits_alg(opslimit, memlimit, alg)
532
533 outbuf = ffi.new("char[]", 128)
534
535 ret = lib.crypto_pwhash_str_alg(
536 outbuf, passwd, len(passwd), opslimit, memlimit, alg
537 )
538
539 ensure(
540 ret == 0,
541 "Unexpected failure in key derivation",
542 raising=exc.RuntimeError,
543 )
544
545 return ffi.string(outbuf)
546
547
548def crypto_pwhash_str_verify(passwd_hash: bytes, passwd: bytes) -> bool:
549 """
550 Verifies the ``passwd`` against a given password hash.
551
552 Returns True on success, raises InvalidkeyError on failure
553 :param passwd_hash: saved password hash
554 :type passwd_hash: bytes
555 :param passwd: password to be checked
556 :type passwd: bytes
557 :return: success
558 :rtype: boolean
559 """
560 ensure(isinstance(passwd_hash, bytes), raising=TypeError)
561 ensure(isinstance(passwd, bytes), raising=TypeError)
562 ensure(
563 len(passwd_hash) <= 127,
564 "Hash must be at most 127 bytes long",
565 raising=exc.ValueError,
566 )
567
568 ret = lib.crypto_pwhash_str_verify(passwd_hash, passwd, len(passwd))
569
570 ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
571 # all went well, therefore:
572 return True
573
574
575crypto_pwhash_argon2i_str_verify = crypto_pwhash_str_verify