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
15import sys
16from typing import Tuple
17
18import nacl.exceptions as exc
19from nacl._sodium import ffi, lib
20from nacl.exceptions import ensure
21
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 "p*r is greater than {}".format(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 if opslimit < 32768:
239 opslimit = 32768
240
241 r = 8
242
243 if opslimit < (memlimit // 32):
244 p = 1
245 maxn = opslimit // (4 * r)
246 for n_log2 in range(1, 63): # pragma: no branch
247 if (2**n_log2) > (maxn // 2):
248 break
249 else:
250 maxn = memlimit // (r * 128)
251 for n_log2 in range(1, 63): # pragma: no branch
252 if (2**n_log2) > maxn // 2:
253 break
254
255 maxrp = (opslimit // 4) // (2**n_log2)
256
257 if maxrp > 0x3FFFFFFF: # pragma: no cover
258 maxrp = 0x3FFFFFFF
259
260 p = maxrp // r
261
262 return n_log2, r, p
263
264
265def crypto_pwhash_scryptsalsa208sha256_ll(
266 passwd: bytes,
267 salt: bytes,
268 n: int,
269 r: int,
270 p: int,
271 dklen: int = 64,
272 maxmem: int = SCRYPT_MAX_MEM,
273) -> bytes:
274 """
275 Derive a cryptographic key using the ``passwd`` and ``salt``
276 given as input.
277
278 The work factor can be tuned by by picking different
279 values for the parameters
280
281 :param bytes passwd:
282 :param bytes salt:
283 :param bytes salt: *must* be *exactly* :py:const:`.SALTBYTES` long
284 :param int dklen:
285 :param int opslimit:
286 :param int n:
287 :param int r: block size,
288 :param int p: the parallelism factor
289 :param int maxmem: the maximum available memory available for scrypt's
290 operations
291 :rtype: bytes
292 :raises nacl.exceptions.UnavailableError: If called when using a
293 minimal build of libsodium.
294 """
295 ensure(
296 has_crypto_pwhash_scryptsalsa208sha256,
297 "Not available in minimal build",
298 raising=exc.UnavailableError,
299 )
300
301 ensure(isinstance(n, int), raising=TypeError)
302 ensure(isinstance(r, int), raising=TypeError)
303 ensure(isinstance(p, int), raising=TypeError)
304
305 ensure(isinstance(passwd, bytes), raising=TypeError)
306 ensure(isinstance(salt, bytes), raising=TypeError)
307
308 _check_memory_occupation(n, r, p, maxmem)
309
310 buf = ffi.new("uint8_t[]", dklen)
311
312 ret = lib.crypto_pwhash_scryptsalsa208sha256_ll(
313 passwd, len(passwd), salt, len(salt), n, r, p, buf, dklen
314 )
315
316 ensure(
317 ret == 0,
318 "Unexpected failure in key derivation",
319 raising=exc.RuntimeError,
320 )
321
322 return ffi.buffer(ffi.cast("char *", buf), dklen)[:]
323
324
325def crypto_pwhash_scryptsalsa208sha256_str(
326 passwd: bytes,
327 opslimit: int = SCRYPT_OPSLIMIT_INTERACTIVE,
328 memlimit: int = SCRYPT_MEMLIMIT_INTERACTIVE,
329) -> bytes:
330 """
331 Derive a cryptographic key using the ``passwd`` and ``salt``
332 given as input, returning a string representation which includes
333 the salt and the tuning parameters.
334
335 The returned string can be directly stored as a password hash.
336
337 See :py:func:`.crypto_pwhash_scryptsalsa208sha256` for a short
338 discussion about ``opslimit`` and ``memlimit`` values.
339
340 :param bytes passwd:
341 :param int opslimit:
342 :param int memlimit:
343 :return: serialized key hash, including salt and tuning parameters
344 :rtype: bytes
345 :raises nacl.exceptions.UnavailableError: If called when using a
346 minimal build of libsodium.
347 """
348 ensure(
349 has_crypto_pwhash_scryptsalsa208sha256,
350 "Not available in minimal build",
351 raising=exc.UnavailableError,
352 )
353
354 buf = ffi.new("char[]", SCRYPT_STRBYTES)
355
356 ret = lib.crypto_pwhash_scryptsalsa208sha256_str(
357 buf, passwd, len(passwd), opslimit, memlimit
358 )
359
360 ensure(
361 ret == 0,
362 "Unexpected failure in password hashing",
363 raising=exc.RuntimeError,
364 )
365
366 return ffi.string(buf)
367
368
369def crypto_pwhash_scryptsalsa208sha256_str_verify(
370 passwd_hash: bytes, passwd: bytes
371) -> bool:
372 """
373 Verifies the ``passwd`` against the ``passwd_hash`` that was generated.
374 Returns True or False depending on the success
375
376 :param passwd_hash: bytes
377 :param passwd: bytes
378 :rtype: boolean
379 :raises nacl.exceptions.UnavailableError: If called when using a
380 minimal build of libsodium.
381 """
382 ensure(
383 has_crypto_pwhash_scryptsalsa208sha256,
384 "Not available in minimal build",
385 raising=exc.UnavailableError,
386 )
387
388 ensure(
389 len(passwd_hash) == SCRYPT_STRBYTES - 1,
390 "Invalid password hash",
391 raising=exc.ValueError,
392 )
393
394 ret = lib.crypto_pwhash_scryptsalsa208sha256_str_verify(
395 passwd_hash, passwd, len(passwd)
396 )
397 ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
398 # all went well, therefore:
399 return True
400
401
402def _check_argon2_limits_alg(opslimit: int, memlimit: int, alg: int) -> None:
403 if alg == crypto_pwhash_ALG_ARGON2I13:
404 if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN:
405 raise exc.ValueError(
406 "memlimit must be at least {} bytes".format(
407 crypto_pwhash_argon2i_MEMLIMIT_MIN
408 )
409 )
410 elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX:
411 raise exc.ValueError(
412 "memlimit must be at most {} bytes".format(
413 crypto_pwhash_argon2i_MEMLIMIT_MAX
414 )
415 )
416 if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN:
417 raise exc.ValueError(
418 "opslimit must be at least {}".format(
419 crypto_pwhash_argon2i_OPSLIMIT_MIN
420 )
421 )
422 elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX:
423 raise exc.ValueError(
424 "opslimit must be at most {}".format(
425 crypto_pwhash_argon2i_OPSLIMIT_MAX
426 )
427 )
428
429 elif alg == crypto_pwhash_ALG_ARGON2ID13:
430 if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN:
431 raise exc.ValueError(
432 "memlimit must be at least {} bytes".format(
433 crypto_pwhash_argon2id_MEMLIMIT_MIN
434 )
435 )
436 elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX:
437 raise exc.ValueError(
438 "memlimit must be at most {} bytes".format(
439 crypto_pwhash_argon2id_MEMLIMIT_MAX
440 )
441 )
442 if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN:
443 raise exc.ValueError(
444 "opslimit must be at least {}".format(
445 crypto_pwhash_argon2id_OPSLIMIT_MIN
446 )
447 )
448 elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX:
449 raise exc.ValueError(
450 "opslimit must be at most {}".format(
451 crypto_pwhash_argon2id_OPSLIMIT_MAX
452 )
453 )
454 else:
455 raise exc.TypeError("Unsupported algorithm")
456
457
458def crypto_pwhash_alg(
459 outlen: int,
460 passwd: bytes,
461 salt: bytes,
462 opslimit: int,
463 memlimit: int,
464 alg: int,
465) -> bytes:
466 """
467 Derive a raw cryptographic key using the ``passwd`` and the ``salt``
468 given as input to the ``alg`` algorithm.
469
470 :param outlen: the length of the derived key
471 :type outlen: int
472 :param passwd: The input password
473 :type passwd: bytes
474 :param salt:
475 :type salt: bytes
476 :param opslimit: computational cost
477 :type opslimit: int
478 :param memlimit: memory cost
479 :type memlimit: int
480 :param alg: algorithm identifier
481 :type alg: int
482 :return: derived key
483 :rtype: bytes
484 """
485 ensure(isinstance(outlen, int), raising=exc.TypeError)
486 ensure(isinstance(opslimit, int), raising=exc.TypeError)
487 ensure(isinstance(memlimit, int), raising=exc.TypeError)
488 ensure(isinstance(alg, int), raising=exc.TypeError)
489 ensure(isinstance(passwd, bytes), raising=exc.TypeError)
490
491 if len(salt) != crypto_pwhash_SALTBYTES:
492 raise exc.ValueError(
493 "salt must be exactly {} bytes long".format(
494 crypto_pwhash_SALTBYTES
495 )
496 )
497
498 if outlen < crypto_pwhash_BYTES_MIN:
499 raise exc.ValueError(
500 "derived key must be at least {} bytes long".format(
501 crypto_pwhash_BYTES_MIN
502 )
503 )
504
505 elif outlen > crypto_pwhash_BYTES_MAX:
506 raise exc.ValueError(
507 "derived key must be at most {} bytes long".format(
508 crypto_pwhash_BYTES_MAX
509 )
510 )
511
512 _check_argon2_limits_alg(opslimit, memlimit, alg)
513
514 outbuf = ffi.new("unsigned char[]", outlen)
515
516 ret = lib.crypto_pwhash(
517 outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg
518 )
519
520 ensure(
521 ret == 0,
522 "Unexpected failure in key derivation",
523 raising=exc.RuntimeError,
524 )
525
526 return ffi.buffer(outbuf, outlen)[:]
527
528
529def crypto_pwhash_str_alg(
530 passwd: bytes,
531 opslimit: int,
532 memlimit: int,
533 alg: int,
534) -> bytes:
535 """
536 Derive a cryptographic key using the ``passwd`` given as input
537 and a random salt, returning a string representation which
538 includes the salt, the tuning parameters and the used algorithm.
539
540 :param passwd: The input password
541 :type passwd: bytes
542 :param opslimit: computational cost
543 :type opslimit: int
544 :param memlimit: memory cost
545 :type memlimit: int
546 :param alg: The algorithm to use
547 :type alg: int
548 :return: serialized derived key and parameters
549 :rtype: bytes
550 """
551 ensure(isinstance(opslimit, int), raising=TypeError)
552 ensure(isinstance(memlimit, int), raising=TypeError)
553 ensure(isinstance(passwd, bytes), raising=TypeError)
554
555 _check_argon2_limits_alg(opslimit, memlimit, alg)
556
557 outbuf = ffi.new("char[]", 128)
558
559 ret = lib.crypto_pwhash_str_alg(
560 outbuf, passwd, len(passwd), opslimit, memlimit, alg
561 )
562
563 ensure(
564 ret == 0,
565 "Unexpected failure in key derivation",
566 raising=exc.RuntimeError,
567 )
568
569 return ffi.string(outbuf)
570
571
572def crypto_pwhash_str_verify(passwd_hash: bytes, passwd: bytes) -> bool:
573 """
574 Verifies the ``passwd`` against a given password hash.
575
576 Returns True on success, raises InvalidkeyError on failure
577 :param passwd_hash: saved password hash
578 :type passwd_hash: bytes
579 :param passwd: password to be checked
580 :type passwd: bytes
581 :return: success
582 :rtype: boolean
583 """
584 ensure(isinstance(passwd_hash, bytes), raising=TypeError)
585 ensure(isinstance(passwd, bytes), raising=TypeError)
586 ensure(
587 len(passwd_hash) <= 127,
588 "Hash must be at most 127 bytes long",
589 raising=exc.ValueError,
590 )
591
592 ret = lib.crypto_pwhash_str_verify(passwd_hash, passwd, len(passwd))
593
594 ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
595 # all went well, therefore:
596 return True
597
598
599crypto_pwhash_argon2i_str_verify = crypto_pwhash_str_verify