summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLegrandin <helderijs@gmail.com>2013-05-14 19:00:43 +0200
committerDwayne Litzenberger <dlitz@dlitz.net>2013-10-20 13:30:21 -0700
commit77b0b9123c32b181f7f7a0072b2baa6312620f66 (patch)
treeb808b1809304a161ec73586736146c8dec9027c8
parent661f2a1f6ed02b5b2f21e340845361e70610ff3f (diff)
downloadpycrypto-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.py55
-rw-r--r--lib/Crypto/SelfTest/Hash/common.py13
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()