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 cast
15
16import nacl.bindings
17import nacl.encoding
18from nacl import exceptions as exc
19from nacl.exceptions import ensure
20
21_strbytes_plus_one = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES
22
23AVAILABLE = nacl.bindings.has_crypto_pwhash_scryptsalsa208sha256
24
25STRPREFIX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRPREFIX
26
27SALTBYTES = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_SALTBYTES
28
29PASSWD_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN
30PASSWD_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX
31
32PWHASH_SIZE = _strbytes_plus_one - 1
33
34BYTES_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN
35BYTES_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX
36
37MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN
38MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX
39OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN
40OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX
41
42OPSLIMIT_INTERACTIVE = (
43 nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
44)
45MEMLIMIT_INTERACTIVE = (
46 nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
47)
48OPSLIMIT_SENSITIVE = (
49 nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
50)
51MEMLIMIT_SENSITIVE = (
52 nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
53)
54
55OPSLIMIT_MODERATE = 8 * OPSLIMIT_INTERACTIVE
56MEMLIMIT_MODERATE = 8 * MEMLIMIT_INTERACTIVE
57
58
59def kdf(
60 size: int,
61 password: bytes,
62 salt: bytes,
63 opslimit: int = OPSLIMIT_SENSITIVE,
64 memlimit: int = MEMLIMIT_SENSITIVE,
65 encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
66) -> bytes:
67 """
68 Derive a ``size`` bytes long key from a caller-supplied
69 ``password`` and ``salt`` pair using the scryptsalsa208sha256
70 memory-hard construct.
71
72
73 the enclosing module provides the constants
74
75 - :py:const:`.OPSLIMIT_INTERACTIVE`
76 - :py:const:`.MEMLIMIT_INTERACTIVE`
77 - :py:const:`.OPSLIMIT_SENSITIVE`
78 - :py:const:`.MEMLIMIT_SENSITIVE`
79 - :py:const:`.OPSLIMIT_MODERATE`
80 - :py:const:`.MEMLIMIT_MODERATE`
81
82 as a guidance for correct settings respectively for the
83 interactive login and the long term key protecting sensitive data
84 use cases.
85
86 :param size: derived key size, must be between
87 :py:const:`.BYTES_MIN` and
88 :py:const:`.BYTES_MAX`
89 :type size: int
90 :param password: password used to seed the key derivation procedure;
91 it length must be between
92 :py:const:`.PASSWD_MIN` and
93 :py:const:`.PASSWD_MAX`
94 :type password: bytes
95 :param salt: **RANDOM** salt used in the key derivation procedure;
96 its length must be exactly :py:const:`.SALTBYTES`
97 :type salt: bytes
98 :param opslimit: the time component (operation count)
99 of the key derivation procedure's computational cost;
100 it must be between
101 :py:const:`.OPSLIMIT_MIN` and
102 :py:const:`.OPSLIMIT_MAX`
103 :type opslimit: int
104 :param memlimit: the memory occupation component
105 of the key derivation procedure's computational cost;
106 it must be between
107 :py:const:`.MEMLIMIT_MIN` and
108 :py:const:`.MEMLIMIT_MAX`
109 :type memlimit: int
110 :rtype: bytes
111 :raises nacl.exceptions.UnavailableError: If called when using a
112 minimal build of libsodium.
113
114 .. versionadded:: 1.2
115 """
116 ensure(
117 AVAILABLE,
118 "Not available in minimal build",
119 raising=exc.UnavailableError,
120 )
121
122 ensure(
123 len(salt) == SALTBYTES,
124 f"The salt must be exactly {SALTBYTES}, not {len(salt)} bytes long",
125 raising=exc.ValueError,
126 )
127
128 n_log2, r, p = nacl.bindings.nacl_bindings_pick_scrypt_params(
129 opslimit, memlimit
130 )
131 maxmem = memlimit + (2**16)
132
133 return encoder.encode(
134 nacl.bindings.crypto_pwhash_scryptsalsa208sha256_ll(
135 password,
136 salt,
137 # Cast safety: n_log2 is a positive integer, and so 2 ** n_log2 is also
138 # a positive integer. Mypy+typeshed can't deduce this, because there's no
139 # way to for them to know that n_log2: int is positive.
140 cast(int, 2**n_log2),
141 r,
142 p,
143 maxmem=maxmem,
144 dklen=size,
145 )
146 )
147
148
149def str(
150 password: bytes,
151 opslimit: int = OPSLIMIT_INTERACTIVE,
152 memlimit: int = MEMLIMIT_INTERACTIVE,
153) -> bytes:
154 """
155 Hashes a password with a random salt, using the memory-hard
156 scryptsalsa208sha256 construct and returning an ascii string
157 that has all the needed info to check against a future password
158
159 The default settings for opslimit and memlimit are those deemed
160 correct for the interactive user login case.
161
162 :param bytes password:
163 :param int opslimit:
164 :param int memlimit:
165 :rtype: bytes
166 :raises nacl.exceptions.UnavailableError: If called when using a
167 minimal build of libsodium.
168
169 .. versionadded:: 1.2
170 """
171 ensure(
172 AVAILABLE,
173 "Not available in minimal build",
174 raising=exc.UnavailableError,
175 )
176
177 return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str(
178 password, opslimit, memlimit
179 )
180
181
182def verify(password_hash: bytes, password: bytes) -> bool:
183 """
184 Takes the output of scryptsalsa208sha256 and compares it against
185 a user provided password to see if they are the same
186
187 :param password_hash: bytes
188 :param password: bytes
189 :rtype: boolean
190 :raises nacl.exceptions.UnavailableError: If called when using a
191 minimal build of libsodium.
192
193 .. versionadded:: 1.2
194 """
195 ensure(
196 AVAILABLE,
197 "Not available in minimal build",
198 raising=exc.UnavailableError,
199 )
200
201 ensure(
202 len(password_hash) == PWHASH_SIZE,
203 f"The password hash must be exactly {nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES} bytes long",
204 raising=exc.ValueError,
205 )
206
207 return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str_verify(
208 password_hash, password
209 )