]> granicus.if.org Git - python/commitdiff
Issue #14532: Add a secure_compare() helper to the hmac module, to mitigate
authorCharles-François Natali <neologix@free.fr>
Sun, 13 May 2012 17:53:07 +0000 (19:53 +0200)
committerCharles-François Natali <neologix@free.fr>
Sun, 13 May 2012 17:53:07 +0000 (19:53 +0200)
timing attacks. Patch by Jon Oberheide.

Doc/library/hmac.rst
Lib/hmac.py
Lib/test/test_hmac.py
Misc/ACKS
Misc/NEWS

index eff27241bc874f800d8da6b0b5188104206e0a99..e8f6488b557ff703629829e402430ce83205e7cd 100644 (file)
@@ -38,6 +38,13 @@ An HMAC object has the following methods:
    given to the constructor.  It may contain non-ASCII bytes, including NUL
    bytes.
 
+   .. warning::
+
+      When comparing the output of :meth:`digest` to an externally-supplied
+      digest during a verification routine, it is recommended to use the
+      :func:`hmac.secure_compare` function instead of the ``==`` operator
+      to avoid potential timing attacks.
+
 
 .. method:: HMAC.hexdigest()
 
@@ -45,6 +52,13 @@ An HMAC object has the following methods:
    length containing only hexadecimal digits.  This may be used to exchange the
    value safely in email or other non-binary environments.
 
+   .. warning::
+
+      When comparing the output of :meth:`hexdigest` to an externally-supplied
+      digest during a verification routine, it is recommended to use the
+      :func:`hmac.secure_compare` function instead of the ``==`` operator
+      to avoid potential timing attacks.
+
 
 .. method:: HMAC.copy()
 
@@ -52,6 +66,24 @@ An HMAC object has the following methods:
    compute the digests of strings that share a common initial substring.
 
 
+This module also provides the following helper function:
+
+.. function:: secure_compare(a, b)
+
+   Returns the equivalent of ``a == b``, but using a time-independent
+   comparison method. Comparing the full lengths of the inputs *a* and *b*,
+   instead of short-circuiting the comparison upon the first unequal byte,
+   prevents leaking information about the inputs being compared and mitigates
+   potential timing attacks. The inputs must be either :class:`str` or
+   :class:`bytes` instances.
+
+   .. note::
+
+      While the :func:`hmac.secure_compare` function prevents leaking the
+      contents of the inputs via a timing attack, it does leak the length
+      of the inputs. However, this generally is not a security risk.
+
+
 .. seealso::
 
    Module :mod:`hashlib`
index 956fc65d2c06173e9b76b7525df17656ba5db61d..13ffdbe14b231b05c83805fc083507c8f9a95da1 100644 (file)
@@ -13,6 +13,27 @@ trans_36 = bytes((x ^ 0x36) for x in range(256))
 digest_size = None
 
 
+def secure_compare(a, b):
+    """Returns the equivalent of 'a == b', but using a time-independent
+    comparison method to prevent timing attacks."""
+    if not ((isinstance(a, str) and isinstance(b, str)) or
+            (isinstance(a, bytes) and isinstance(b, bytes))):
+        raise TypeError("inputs must be strings or bytes")
+
+    if len(a) != len(b):
+        return False
+
+    result = 0
+    if isinstance(a, bytes):
+        for x, y in zip(a, b):
+            result |= x ^ y
+    else:
+        for x, y in zip(a, b):
+            result |= ord(x) ^ ord(y)
+
+    return result == 0
+
+
 class HMAC:
     """RFC 2104 HMAC class.  Also complies with RFC 4231.
 
index 4de0620e1a9986df474d318570bd846385d7acd2..042bc5d8f7a7103986d002b8f75a99820b52f9d1 100644 (file)
@@ -302,12 +302,48 @@ class CopyTestCase(unittest.TestCase):
         self.assertEqual(h1.hexdigest(), h2.hexdigest(),
             "Hexdigest of copy doesn't match original hexdigest.")
 
+class SecureCompareTestCase(unittest.TestCase):
+
+    def test_compare(self):
+        # Testing input type exception handling
+        a, b = 100, 200
+        self.assertRaises(TypeError, hmac.secure_compare, a, b)
+        a, b = 100, "foobar"
+        self.assertRaises(TypeError, hmac.secure_compare, a, b)
+        a, b = "foobar", b"foobar"
+        self.assertRaises(TypeError, hmac.secure_compare, a, b)
+
+        # Testing str/bytes of different lengths
+        a, b = "foobar", "foo"
+        self.assertFalse(hmac.secure_compare(a, b))
+        a, b = b"foobar", b"foo"
+        self.assertFalse(hmac.secure_compare(a, b))
+        a, b = b"\xde\xad\xbe\xef", b"\xde\xad"
+        self.assertFalse(hmac.secure_compare(a, b))
+
+        # Testing str/bytes of same lengths, different values
+        a, b = "foobar", "foobaz"
+        self.assertFalse(hmac.secure_compare(a, b))
+        a, b = b"foobar", b"foobaz"
+        self.assertFalse(hmac.secure_compare(a, b))
+        a, b = b"\xde\xad\xbe\xef", b"\xab\xad\x1d\xea"
+        self.assertFalse(hmac.secure_compare(a, b))
+
+        # Testing str/bytes of same lengths, same values
+        a, b = "foobar", "foobar"
+        self.assertTrue(hmac.secure_compare(a, b))
+        a, b = b"foobar", b"foobar"
+        self.assertTrue(hmac.secure_compare(a, b))
+        a, b = b"\xde\xad\xbe\xef", b"\xde\xad\xbe\xef"
+        self.assertTrue(hmac.secure_compare(a, b))
+
 def test_main():
     support.run_unittest(
         TestVectorsTestCase,
         ConstructorTestCase,
         SanityTestCase,
-        CopyTestCase
+        CopyTestCase,
+        SecureCompareTestCase
     )
 
 if __name__ == "__main__":
index c7dc2f4098ca56117dc5bc399395b19aada17ffa..2be4bf7f919a1d375d3f02214918450d2d80e606 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -746,6 +746,7 @@ Nigel O'Brian
 John O'Connor
 Kevin O'Connor
 Tim O'Malley
+Jon Oberheide
 Pascal Oberndoerfer
 Jeffrey Ollie
 Adam Olsen
index 9adce9f6307000b0898f67220297180c4668c8e0..031738d95aa103d77652d44efa7be6b2bbeab9aa 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -23,6 +23,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #14532: Add a secure_compare() helper to the hmac module, to mitigate
+  timing attacks. Patch by Jon Oberheide. 
+
 - Add importlib.util.resolve_name().
 
 - Issue #14366: Support lzma compression in zip files.