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