diff options
author | Legrandin <helderijs@gmail.com> | 2013-05-14 19:00:43 +0200 |
---|---|---|
committer | Dwayne Litzenberger <dlitz@dlitz.net> | 2013-10-20 13:30:21 -0700 |
commit | 77b0b9123c32b181f7f7a0072b2baa6312620f66 (patch) | |
tree | b808b1809304a161ec73586736146c8dec9027c8 | |
parent | 661f2a1f6ed02b5b2f21e340845361e70610ff3f (diff) | |
download | pycrypto-77b0b9123c32b181f7f7a0072b2baa6312620f66.tar.gz |
Add HMAC.verify() and HMAC.hexverify() with constant-time comparison
In the current implementation, it is left up to the caller
to assess if the locally computed MAC matches the MAC associated
to the received message.
However, the most natural way to do that (use == operator)
is also deepy unsecure, see here:
http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf
With this patch, the new HMAC.verify() method accepts
the given MAC and perform the check on behalf of the caller.
The method will use constant-time code (still dependent on the length
of the MAC, but not on the actual content).
[dlitz@dlitz.net: Modified commit message subject line.]
[dlitz@dlitz.net: Whitespace fixed with "git rebase --whitespace=fix"]
-rw-r--r-- | lib/Crypto/Hash/HMAC.py | 55 | ||||
-rw-r--r-- | lib/Crypto/SelfTest/Hash/common.py | 13 |
2 files changed, 65 insertions, 3 deletions
diff --git a/lib/Crypto/Hash/HMAC.py b/lib/Crypto/Hash/HMAC.py index 6244db4..b44a082 100644 --- a/lib/Crypto/Hash/HMAC.py +++ b/lib/Crypto/Hash/HMAC.py @@ -43,7 +43,7 @@ The strength of an HMAC depends on: - the strength of the hash algorithm - the length and entropy of the secret key -An example of possible usage is the following: +This is an example showing how to *create* a MAC: >>> from Crypto.Hash import HMAC >>> @@ -52,6 +52,22 @@ An example of possible usage is the following: >>> h.update(b'Hello') >>> print h.hexdigest() +This is an example showing how to *check* a MAC: + + >>> from Crypto.Hash import HMAC + >>> + >>> # We have received a message 'msg' together + >>> # with its MAC 'mac' + >>> + >>> secret = b'Swordfish' + >>> h = HMAC.new(secret) + >>> h.update(msg) + >>> try: + >>> h.verify(mac) + >>> print "The message '%s' is authentic" % msg + >>> except ValueError: + >>> print "The message or the key is wrong" + .. _RFC2104: http://www.ietf.org/rfc/rfc2104.txt .. _FIPS-198: http://csrc.nist.gov/publications/fips/fips198/fips-198a.pdf """ @@ -63,6 +79,8 @@ __revision__ = "$Id$" __all__ = ['new', 'digest_size', 'HMAC' ] +from binascii import unhexlify + from Crypto.Util.strxor import strxor_c from Crypto.Util.py3compat import * @@ -171,12 +189,32 @@ class HMAC: You can continue updating the object after calling this function. :Return: A byte string of `digest_size` bytes. It may contain non-ASCII - characters, including null bytes. + characters, including null bytes. """ + h = self.outer.copy() h.update(self.inner.digest()) return h.digest() + def verify(self, mac_tag): + """Verify that a given **binary** MAC (computed by another party) is valid. + + :Parameters: + mac_tag : byte string + The expected MAC of the message. + :Raises ValueError: + if the MAC does not match. It means that the message + has been tampered with or that the MAC key is incorrect. + """ + + mac = self.digest() + res = 0 + # Constant-time comparison + for x,y in zip(mac, mac_tag): + res |= bord(x) ^ bord(y) + if res or len(mac_tag)!=self.digest_size: + raise ValueError("MAC check failed") + def hexdigest(self): """Return the **printable** MAC of the message that has been authenticated so far. @@ -189,6 +227,19 @@ class HMAC: return "".join(["%02x" % bord(x) for x in tuple(self.digest())]) + def hexverify(self, hex_mac_tag): + """Verify that a given **printable** MAC (computed by another party) is valid. + + :Parameters: + hex_mac_tag : string + The expected MAC of the message, as a hexadecimal string. + :Raises ValueError: + if the MAC does not match. It means that the message + has been tampered with or that the MAC key is incorrect. + """ + + self.verify(unhexlify(hex_mac_tag)) + def new(key, msg = None, digestmod = None): """Create a new HMAC object. diff --git a/lib/Crypto/SelfTest/Hash/common.py b/lib/Crypto/SelfTest/Hash/common.py index 48cebe7..4976690 100644 --- a/lib/Crypto/SelfTest/Hash/common.py +++ b/lib/Crypto/SelfTest/Hash/common.py @@ -43,6 +43,7 @@ if sys.hexversion < 0x02030000: else: dict = dict +from Crypto.Util.strxor import strxor_c class HashDigestSizeSelfTest(unittest.TestCase): @@ -184,9 +185,19 @@ class MACSelfTest(unittest.TestCase): h = self.hashmod.new(key, digestmod=hashmod) h.update(data) - out1 = binascii.b2a_hex(h.digest()) + out1_bin = h.digest() + out1 = binascii.b2a_hex(out1_bin) out2 = h.hexdigest() + # Verify that correct MAC does not raise any exception + h.hexverify(out1) + h.verify(out1_bin) + + # Verify that incorrect MAC does raise ValueError exception + wrong_mac = strxor_c(out1_bin, 255) + self.assertRaises(ValueError, h.verify, wrong_mac) + self.assertRaises(ValueError, h.hexverify, "4556") + h = self.hashmod.new(key, data, hashmod) out3 = h.hexdigest() |