1# Copyright 2013-2019 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 NoReturn, TypeVar
15
16from nacl import exceptions as exc
17from nacl._sodium import ffi, lib
18from nacl.exceptions import ensure
19
20
21crypto_generichash_BYTES: int = lib.crypto_generichash_blake2b_bytes()
22crypto_generichash_BYTES_MIN: int = lib.crypto_generichash_blake2b_bytes_min()
23crypto_generichash_BYTES_MAX: int = lib.crypto_generichash_blake2b_bytes_max()
24crypto_generichash_KEYBYTES: int = lib.crypto_generichash_blake2b_keybytes()
25crypto_generichash_KEYBYTES_MIN: int = (
26 lib.crypto_generichash_blake2b_keybytes_min()
27)
28crypto_generichash_KEYBYTES_MAX: int = (
29 lib.crypto_generichash_blake2b_keybytes_max()
30)
31crypto_generichash_SALTBYTES: int = lib.crypto_generichash_blake2b_saltbytes()
32crypto_generichash_PERSONALBYTES: int = (
33 lib.crypto_generichash_blake2b_personalbytes()
34)
35crypto_generichash_STATEBYTES: int = lib.crypto_generichash_statebytes()
36
37_OVERLONG = "{0} length greater than {1} bytes"
38_TOOBIG = "{0} greater than {1}"
39
40
41def _checkparams(
42 digest_size: int, key: bytes, salt: bytes, person: bytes
43) -> None:
44 """Check hash parameters"""
45 ensure(
46 isinstance(key, bytes),
47 "Key must be a bytes sequence",
48 raising=exc.TypeError,
49 )
50
51 ensure(
52 isinstance(salt, bytes),
53 "Salt must be a bytes sequence",
54 raising=exc.TypeError,
55 )
56
57 ensure(
58 isinstance(person, bytes),
59 "Person must be a bytes sequence",
60 raising=exc.TypeError,
61 )
62
63 ensure(
64 isinstance(digest_size, int),
65 "Digest size must be an integer number",
66 raising=exc.TypeError,
67 )
68
69 ensure(
70 digest_size <= crypto_generichash_BYTES_MAX,
71 _TOOBIG.format("Digest_size", crypto_generichash_BYTES_MAX),
72 raising=exc.ValueError,
73 )
74
75 ensure(
76 len(key) <= crypto_generichash_KEYBYTES_MAX,
77 _OVERLONG.format("Key", crypto_generichash_KEYBYTES_MAX),
78 raising=exc.ValueError,
79 )
80
81 ensure(
82 len(salt) <= crypto_generichash_SALTBYTES,
83 _OVERLONG.format("Salt", crypto_generichash_SALTBYTES),
84 raising=exc.ValueError,
85 )
86
87 ensure(
88 len(person) <= crypto_generichash_PERSONALBYTES,
89 _OVERLONG.format("Person", crypto_generichash_PERSONALBYTES),
90 raising=exc.ValueError,
91 )
92
93
94def generichash_blake2b_salt_personal(
95 data: bytes,
96 digest_size: int = crypto_generichash_BYTES,
97 key: bytes = b"",
98 salt: bytes = b"",
99 person: bytes = b"",
100) -> bytes:
101 """One shot hash interface
102
103 :param data: the input data to the hash function
104 :type data: bytes
105 :param digest_size: must be at most
106 :py:data:`.crypto_generichash_BYTES_MAX`;
107 the default digest size is
108 :py:data:`.crypto_generichash_BYTES`
109 :type digest_size: int
110 :param key: must be at most
111 :py:data:`.crypto_generichash_KEYBYTES_MAX` long
112 :type key: bytes
113 :param salt: must be at most
114 :py:data:`.crypto_generichash_SALTBYTES` long;
115 will be zero-padded if needed
116 :type salt: bytes
117 :param person: must be at most
118 :py:data:`.crypto_generichash_PERSONALBYTES` long:
119 will be zero-padded if needed
120 :type person: bytes
121 :return: digest_size long digest
122 :rtype: bytes
123 """
124
125 _checkparams(digest_size, key, salt, person)
126
127 ensure(
128 isinstance(data, bytes),
129 "Input data must be a bytes sequence",
130 raising=exc.TypeError,
131 )
132
133 digest = ffi.new("unsigned char[]", digest_size)
134
135 # both _salt and _personal must be zero-padded to the correct length
136 _salt = ffi.new("unsigned char []", crypto_generichash_SALTBYTES)
137 _person = ffi.new("unsigned char []", crypto_generichash_PERSONALBYTES)
138
139 ffi.memmove(_salt, salt, len(salt))
140 ffi.memmove(_person, person, len(person))
141
142 rc = lib.crypto_generichash_blake2b_salt_personal(
143 digest, digest_size, data, len(data), key, len(key), _salt, _person
144 )
145 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
146
147 return ffi.buffer(digest, digest_size)[:]
148
149
150_Blake2State = TypeVar("_Blake2State", bound="Blake2State")
151
152
153class Blake2State:
154 """
155 Python-level wrapper for the crypto_generichash_blake2b state buffer
156 """
157
158 __slots__ = ["_statebuf", "digest_size"]
159
160 def __init__(self, digest_size: int):
161 self._statebuf = ffi.new(
162 "unsigned char[]", crypto_generichash_STATEBYTES
163 )
164 self.digest_size = digest_size
165
166 def __reduce__(self) -> NoReturn:
167 """
168 Raise the same exception as hashlib's blake implementation
169 on copy.copy()
170 """
171 raise TypeError(
172 "can't pickle {} objects".format(self.__class__.__name__)
173 )
174
175 def copy(self: _Blake2State) -> _Blake2State:
176 _st = self.__class__(self.digest_size)
177 ffi.memmove(
178 _st._statebuf, self._statebuf, crypto_generichash_STATEBYTES
179 )
180 return _st
181
182
183def generichash_blake2b_init(
184 key: bytes = b"",
185 salt: bytes = b"",
186 person: bytes = b"",
187 digest_size: int = crypto_generichash_BYTES,
188) -> Blake2State:
189 """
190 Create a new initialized blake2b hash state
191
192 :param key: must be at most
193 :py:data:`.crypto_generichash_KEYBYTES_MAX` long
194 :type key: bytes
195 :param salt: must be at most
196 :py:data:`.crypto_generichash_SALTBYTES` long;
197 will be zero-padded if needed
198 :type salt: bytes
199 :param person: must be at most
200 :py:data:`.crypto_generichash_PERSONALBYTES` long:
201 will be zero-padded if needed
202 :type person: bytes
203 :param digest_size: must be at most
204 :py:data:`.crypto_generichash_BYTES_MAX`;
205 the default digest size is
206 :py:data:`.crypto_generichash_BYTES`
207 :type digest_size: int
208 :return: a initialized :py:class:`.Blake2State`
209 :rtype: object
210 """
211
212 _checkparams(digest_size, key, salt, person)
213
214 state = Blake2State(digest_size)
215
216 # both _salt and _personal must be zero-padded to the correct length
217 _salt = ffi.new("unsigned char []", crypto_generichash_SALTBYTES)
218 _person = ffi.new("unsigned char []", crypto_generichash_PERSONALBYTES)
219
220 ffi.memmove(_salt, salt, len(salt))
221 ffi.memmove(_person, person, len(person))
222
223 rc = lib.crypto_generichash_blake2b_init_salt_personal(
224 state._statebuf, key, len(key), digest_size, _salt, _person
225 )
226 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
227
228 return state
229
230
231def generichash_blake2b_update(state: Blake2State, data: bytes) -> None:
232 """Update the blake2b hash state
233
234 :param state: a initialized Blake2bState object as returned from
235 :py:func:`.crypto_generichash_blake2b_init`
236 :type state: :py:class:`.Blake2State`
237 :param data:
238 :type data: bytes
239 """
240
241 ensure(
242 isinstance(state, Blake2State),
243 "State must be a Blake2State object",
244 raising=exc.TypeError,
245 )
246
247 ensure(
248 isinstance(data, bytes),
249 "Input data must be a bytes sequence",
250 raising=exc.TypeError,
251 )
252
253 rc = lib.crypto_generichash_blake2b_update(
254 state._statebuf, data, len(data)
255 )
256 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
257
258
259def generichash_blake2b_final(state: Blake2State) -> bytes:
260 """Finalize the blake2b hash state and return the digest.
261
262 :param state: a initialized Blake2bState object as returned from
263 :py:func:`.crypto_generichash_blake2b_init`
264 :type state: :py:class:`.Blake2State`
265 :return: the blake2 digest of the passed-in data stream
266 :rtype: bytes
267 """
268
269 ensure(
270 isinstance(state, Blake2State),
271 "State must be a Blake2State object",
272 raising=exc.TypeError,
273 )
274
275 _digest = ffi.new("unsigned char[]", crypto_generichash_BYTES_MAX)
276 rc = lib.crypto_generichash_blake2b_final(
277 state._statebuf, _digest, state.digest_size
278 )
279
280 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
281 return ffi.buffer(_digest, state.digest_size)[:]