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 choosen 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
404 if alg == crypto_pwhash_ALG_ARGON2I13:
405 if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN:
406 raise exc.ValueError(
407 "memlimit must be at least {} bytes".format(
408 crypto_pwhash_argon2i_MEMLIMIT_MIN
409 )
410 )
411 elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX:
412 raise exc.ValueError(
413 "memlimit must be at most {} bytes".format(
414 crypto_pwhash_argon2i_MEMLIMIT_MAX
415 )
416 )
417 if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN:
418 raise exc.ValueError(
419 "opslimit must be at least {}".format(
420 crypto_pwhash_argon2i_OPSLIMIT_MIN
421 )
422 )
423 elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX:
424 raise exc.ValueError(
425 "opslimit must be at most {}".format(
426 crypto_pwhash_argon2i_OPSLIMIT_MAX
427 )
428 )
429
430 elif alg == crypto_pwhash_ALG_ARGON2ID13:
431 if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN:
432 raise exc.ValueError(
433 "memlimit must be at least {} bytes".format(
434 crypto_pwhash_argon2id_MEMLIMIT_MIN
435 )
436 )
437 elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX:
438 raise exc.ValueError(
439 "memlimit must be at most {} bytes".format(
440 crypto_pwhash_argon2id_MEMLIMIT_MAX
441 )
442 )
443 if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN:
444 raise exc.ValueError(
445 "opslimit must be at least {}".format(
446 crypto_pwhash_argon2id_OPSLIMIT_MIN
447 )
448 )
449 elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX:
450 raise exc.ValueError(
451 "opslimit must be at most {}".format(
452 crypto_pwhash_argon2id_OPSLIMIT_MAX
453 )
454 )
455 else:
456 raise exc.TypeError("Unsupported algorithm")
457
458
459def crypto_pwhash_alg(
460 outlen: int,
461 passwd: bytes,
462 salt: bytes,
463 opslimit: int,
464 memlimit: int,
465 alg: int,
466) -> bytes:
467 """
468 Derive a raw cryptographic key using the ``passwd`` and the ``salt``
469 given as input to the ``alg`` algorithm.
470
471 :param outlen: the length of the derived key
472 :type outlen: int
473 :param passwd: The input password
474 :type passwd: bytes
475 :param salt:
476 :type salt: bytes
477 :param opslimit: computational cost
478 :type opslimit: int
479 :param memlimit: memory cost
480 :type memlimit: int
481 :param alg: algorithm identifier
482 :type alg: int
483 :return: derived key
484 :rtype: bytes
485 """
486 ensure(isinstance(outlen, int), raising=exc.TypeError)
487 ensure(isinstance(opslimit, int), raising=exc.TypeError)
488 ensure(isinstance(memlimit, int), raising=exc.TypeError)
489 ensure(isinstance(alg, int), raising=exc.TypeError)
490 ensure(isinstance(passwd, bytes), raising=exc.TypeError)
491
492 if len(salt) != crypto_pwhash_SALTBYTES:
493 raise exc.ValueError(
494 "salt must be exactly {} bytes long".format(
495 crypto_pwhash_SALTBYTES
496 )
497 )
498
499 if outlen < crypto_pwhash_BYTES_MIN:
500 raise exc.ValueError(
501 "derived key must be at least {} bytes long".format(
502 crypto_pwhash_BYTES_MIN
503 )
504 )
505
506 elif outlen > crypto_pwhash_BYTES_MAX:
507 raise exc.ValueError(
508 "derived key must be at most {} bytes long".format(
509 crypto_pwhash_BYTES_MAX
510 )
511 )
512
513 _check_argon2_limits_alg(opslimit, memlimit, alg)
514
515 outbuf = ffi.new("unsigned char[]", outlen)
516
517 ret = lib.crypto_pwhash(
518 outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg
519 )
520
521 ensure(
522 ret == 0,
523 "Unexpected failure in key derivation",
524 raising=exc.RuntimeError,
525 )
526
527 return ffi.buffer(outbuf, outlen)[:]
528
529
530def crypto_pwhash_str_alg(
531 passwd: bytes,
532 opslimit: int,
533 memlimit: int,
534 alg: int,
535) -> bytes:
536 """
537 Derive a cryptographic key using the ``passwd`` given as input
538 and a random salt, returning a string representation which
539 includes the salt, the tuning parameters and the used algorithm.
540
541 :param passwd: The input password
542 :type passwd: bytes
543 :param opslimit: computational cost
544 :type opslimit: int
545 :param memlimit: memory cost
546 :type memlimit: int
547 :param alg: The algorithm to use
548 :type alg: int
549 :return: serialized derived key and parameters
550 :rtype: bytes
551 """
552 ensure(isinstance(opslimit, int), raising=TypeError)
553 ensure(isinstance(memlimit, int), raising=TypeError)
554 ensure(isinstance(passwd, bytes), raising=TypeError)
555
556 _check_argon2_limits_alg(opslimit, memlimit, alg)
557
558 outbuf = ffi.new("char[]", 128)
559
560 ret = lib.crypto_pwhash_str_alg(
561 outbuf, passwd, len(passwd), opslimit, memlimit, alg
562 )
563
564 ensure(
565 ret == 0,
566 "Unexpected failure in key derivation",
567 raising=exc.RuntimeError,
568 )
569
570 return ffi.string(outbuf)
571
572
573def crypto_pwhash_str_verify(passwd_hash: bytes, passwd: bytes) -> bool:
574 """
575 Verifies the ``passwd`` against a given password hash.
576
577 Returns True on success, raises InvalidkeyError on failure
578 :param passwd_hash: saved password hash
579 :type passwd_hash: bytes
580 :param passwd: password to be checked
581 :type passwd: bytes
582 :return: success
583 :rtype: boolean
584 """
585 ensure(isinstance(passwd_hash, bytes), raising=TypeError)
586 ensure(isinstance(passwd, bytes), raising=TypeError)
587 ensure(
588 len(passwd_hash) <= 127,
589 "Hash must be at most 127 bytes long",
590 raising=exc.ValueError,
591 )
592
593 ret = lib.crypto_pwhash_str_verify(passwd_hash, passwd, len(passwd))
594
595 ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
596 # all went well, therefore:
597 return True
598
599
600crypto_pwhash_argon2i_str_verify = crypto_pwhash_str_verify