1#
2# HMAC.py - Implements the HMAC algorithm as described by RFC 2104.
3#
4# ===================================================================
5#
6# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12#
13# 1. Redistributions of source code must retain the above copyright
14#    notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16#    notice, this list of conditions and the following disclaimer in
17#    the documentation and/or other materials provided with the
18#    distribution.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31# POSSIBILITY OF SUCH DAMAGE.
32# ===================================================================
33
34from Crypto.Util.py3compat import bord, tobytes
35
36from binascii import unhexlify
37
38from Crypto.Hash import MD5
39from Crypto.Hash import BLAKE2s
40from Crypto.Util.strxor import strxor
41from Crypto.Random import get_random_bytes
42
43__all__ = ['new', 'HMAC']
44
45
46class HMAC(object):
47    """An HMAC hash object.
48    Do not instantiate directly. Use the :func:`new` function.
49
50    :ivar digest_size: the size in bytes of the resulting MAC tag
51    :vartype digest_size: integer
52    """
53
54    def __init__(self, key, msg=b"", digestmod=None):
55
56        if digestmod is None:
57            digestmod = MD5
58
59        if msg is None:
60            msg = b""
61
62        # Size of the MAC tag
63        self.digest_size = digestmod.digest_size
64
65        self._digestmod = digestmod
66
67        if isinstance(key, memoryview):
68            key = key.tobytes()
69
70        try:
71            if len(key) <= digestmod.block_size:
72                # Step 1 or 2
73                key_0 = key + b"\x00" * (digestmod.block_size - len(key))
74            else:
75                # Step 3
76                hash_k = digestmod.new(key).digest()
77                key_0 = hash_k + b"\x00" * (digestmod.block_size - len(hash_k))
78        except AttributeError:
79            # Not all hash types have "block_size"
80            raise ValueError("Hash type incompatible to HMAC")
81
82        # Step 4
83        key_0_ipad = strxor(key_0, b"\x36" * len(key_0))
84
85        # Start step 5 and 6
86        self._inner = digestmod.new(key_0_ipad)
87        self._inner.update(msg)
88
89        # Step 7
90        key_0_opad = strxor(key_0, b"\x5c" * len(key_0))
91
92        # Start step 8 and 9
93        self._outer = digestmod.new(key_0_opad)
94
95    def update(self, msg):
96        """Authenticate the next chunk of message.
97
98        Args:
99            data (byte string/byte array/memoryview): The next chunk of data
100        """
101
102        self._inner.update(msg)
103        return self
104
105    def _pbkdf2_hmac_assist(self, first_digest, iterations):
106        """Carry out the expensive inner loop for PBKDF2-HMAC"""
107
108        result = self._digestmod._pbkdf2_hmac_assist(
109                                    self._inner,
110                                    self._outer,
111                                    first_digest,
112                                    iterations)
113        return result
114
115    def copy(self):
116        """Return a copy ("clone") of the HMAC object.
117
118        The copy will have the same internal state as the original HMAC
119        object.
120        This can be used to efficiently compute the MAC tag of byte
121        strings that share a common initial substring.
122
123        :return: An :class:`HMAC`
124        """
125
126        new_hmac = HMAC(b"fake key", digestmod=self._digestmod)
127
128        # Syncronize the state
129        new_hmac._inner = self._inner.copy()
130        new_hmac._outer = self._outer.copy()
131
132        return new_hmac
133
134    def digest(self):
135        """Return the **binary** (non-printable) MAC tag of the message
136        authenticated so far.
137
138        :return: The MAC tag digest, computed over the data processed so far.
139                 Binary form.
140        :rtype: byte string
141        """
142
143        frozen_outer_hash = self._outer.copy()
144        frozen_outer_hash.update(self._inner.digest())
145        return frozen_outer_hash.digest()
146
147    def verify(self, mac_tag):
148        """Verify that a given **binary** MAC (computed by another party)
149        is valid.
150
151        Args:
152          mac_tag (byte string/byte string/memoryview): the expected MAC of the message.
153
154        Raises:
155            ValueError: if the MAC does not match. It means that the message
156                has been tampered with or that the MAC key is incorrect.
157        """
158
159        secret = get_random_bytes(16)
160
161        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=mac_tag)
162        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=self.digest())
163
164        if mac1.digest() != mac2.digest():
165            raise ValueError("MAC check failed")
166
167    def hexdigest(self):
168        """Return the **printable** MAC tag of the message authenticated so far.
169
170        :return: The MAC tag, computed over the data processed so far.
171                 Hexadecimal encoded.
172        :rtype: string
173        """
174
175        return "".join(["%02x" % bord(x)
176                        for x in tuple(self.digest())])
177
178    def hexverify(self, hex_mac_tag):
179        """Verify that a given **printable** MAC (computed by another party)
180        is valid.
181
182        Args:
183            hex_mac_tag (string): the expected MAC of the message,
184                as a hexadecimal string.
185
186        Raises:
187            ValueError: if the MAC does not match. It means that the message
188                has been tampered with or that the MAC key is incorrect.
189        """
190
191        self.verify(unhexlify(tobytes(hex_mac_tag)))
192
193
194def new(key, msg=b"", digestmod=None):
195    """Create a new MAC object.
196
197    Args:
198        key (bytes/bytearray/memoryview):
199            key for the MAC object.
200            It must be long enough to match the expected security level of the
201            MAC.
202        msg (bytes/bytearray/memoryview):
203            Optional. The very first chunk of the message to authenticate.
204            It is equivalent to an early call to :meth:`HMAC.update`.
205        digestmod (module):
206            The hash to use to implement the HMAC.
207            Default is :mod:`Crypto.Hash.MD5`.
208
209    Returns:
210        An :class:`HMAC` object
211    """
212
213    return HMAC(key, msg, digestmod)
214