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 "The salt must be exactly %s, not %s bytes long"
125 % (SALTBYTES, len(salt)),
126 raising=exc.ValueError,
127 )
128
129 n_log2, r, p = nacl.bindings.nacl_bindings_pick_scrypt_params(
130 opslimit, memlimit
131 )
132 maxmem = memlimit + (2**16)
133
134 return encoder.encode(
135 nacl.bindings.crypto_pwhash_scryptsalsa208sha256_ll(
136 password,
137 salt,
138 # Cast safety: n_log2 is a positive integer, and so 2 ** n_log2 is also
139 # a positive integer. Mypy+typeshed can't deduce this, because there's no
140 # way to for them to know that n_log2: int is positive.
141 cast(int, 2**n_log2),
142 r,
143 p,
144 maxmem=maxmem,
145 dklen=size,
146 )
147 )
148
149
150def str(
151 password: bytes,
152 opslimit: int = OPSLIMIT_INTERACTIVE,
153 memlimit: int = MEMLIMIT_INTERACTIVE,
154) -> bytes:
155 """
156 Hashes a password with a random salt, using the memory-hard
157 scryptsalsa208sha256 construct and returning an ascii string
158 that has all the needed info to check against a future password
159
160 The default settings for opslimit and memlimit are those deemed
161 correct for the interactive user login case.
162
163 :param bytes password:
164 :param int opslimit:
165 :param int memlimit:
166 :rtype: bytes
167 :raises nacl.exceptions.UnavailableError: If called when using a
168 minimal build of libsodium.
169
170 .. versionadded:: 1.2
171 """
172 ensure(
173 AVAILABLE,
174 "Not available in minimal build",
175 raising=exc.UnavailableError,
176 )
177
178 return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str(
179 password, opslimit, memlimit
180 )
181
182
183def verify(password_hash: bytes, password: bytes) -> bool:
184 """
185 Takes the output of scryptsalsa208sha256 and compares it against
186 a user provided password to see if they are the same
187
188 :param password_hash: bytes
189 :param password: bytes
190 :rtype: boolean
191 :raises nacl.exceptions.UnavailableError: If called when using a
192 minimal build of libsodium.
193
194 .. versionadded:: 1.2
195 """
196 ensure(
197 AVAILABLE,
198 "Not available in minimal build",
199 raising=exc.UnavailableError,
200 )
201
202 ensure(
203 len(password_hash) == PWHASH_SIZE,
204 "The password hash must be exactly %s bytes long"
205 % nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
206 raising=exc.ValueError,
207 )
208
209 return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str_verify(
210 password_hash, password
211 )