Issue #14532: Add a secure_compare() helper to the hmac module, to mitigate
timing attacks. Patch by Jon Oberheide.
diff --git a/Lib/hmac.py b/Lib/hmac.py
index 956fc65..13ffdbe 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -13,6 +13,27 @@
 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.
 
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 4de0620..042bc5d 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -302,12 +302,48 @@
         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__":