You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
12 KiB
354 lines
12 KiB
6 years ago
|
|
||
|
# HG changeset patch
|
||
|
# User Benjamin Peterson <benjamin@python.org>
|
||
|
# Date 1399849904 25200
|
||
|
# Node ID b40f1a00b13460cc089450028280c4e52dd24a64
|
||
|
# Parent 951775c68b1b7782750c213b0fce1f61d46b2f51
|
||
|
backport hmac.compare_digest to partially implement PEP 466 (closes #21306)
|
||
|
|
||
|
Backport from Alex Gaynor.
|
||
|
|
||
|
diff --git a/Doc/library/hmac.rst b/Doc/library/hmac.rst
|
||
|
--- a/Doc/library/hmac.rst
|
||
|
+++ b/Doc/library/hmac.rst
|
||
|
@@ -38,6 +38,13 @@ An HMAC object has the following methods
|
||
|
This string will be the same length as the *digest_size* of the digest given to
|
||
|
the constructor. It may contain non-ASCII characters, 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:`compare_digest` function instead of the ``==`` operator
|
||
|
+ to reduce the vulnerability to timing attacks.
|
||
|
+
|
||
|
|
||
|
.. method:: HMAC.hexdigest()
|
||
|
|
||
|
@@ -45,6 +52,13 @@ An HMAC object has the following methods
|
||
|
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:`compare_digest` function instead of the ``==`` operator
|
||
|
+ to reduce the vulnerability to timing attacks.
|
||
|
+
|
||
|
|
||
|
.. method:: HMAC.copy()
|
||
|
|
||
|
@@ -52,6 +66,25 @@ 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:: compare_digest(a, b)
|
||
|
+
|
||
|
+ Return ``a == b``. This function uses an approach designed to prevent
|
||
|
+ timing analysis by avoiding content-based short circuiting behaviour,
|
||
|
+ making it appropriate for cryptography. *a* and *b* must both be of the
|
||
|
+ same type: either :class:`unicode` or a :term:`bytes-like object`.
|
||
|
+
|
||
|
+ .. note::
|
||
|
+
|
||
|
+ If *a* and *b* are of different lengths, or if an error occurs,
|
||
|
+ a timing attack could theoretically reveal information about the
|
||
|
+ types and lengths of *a* and *b*--but not their values.
|
||
|
+
|
||
|
+
|
||
|
+ .. versionadded:: 2.7.7
|
||
|
+
|
||
|
+
|
||
|
.. seealso::
|
||
|
|
||
|
Module :mod:`hashlib`
|
||
|
diff --git a/Lib/hmac.py b/Lib/hmac.py
|
||
|
--- a/Lib/hmac.py
|
||
|
+++ b/Lib/hmac.py
|
||
|
@@ -5,6 +5,9 @@ Implements the HMAC algorithm as describ
|
||
|
|
||
|
import warnings as _warnings
|
||
|
|
||
|
+from operator import _compare_digest as compare_digest
|
||
|
+
|
||
|
+
|
||
|
trans_5C = "".join ([chr (x ^ 0x5C) for x in xrange(256)])
|
||
|
trans_36 = "".join ([chr (x ^ 0x36) for x in xrange(256)])
|
||
|
|
||
|
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
|
||
|
--- a/Lib/test/test_hmac.py
|
||
|
+++ b/Lib/test/test_hmac.py
|
||
|
@@ -302,12 +302,122 @@ class CopyTestCase(unittest.TestCase):
|
||
|
self.assertTrue(h1.hexdigest() == h2.hexdigest(),
|
||
|
"Hexdigest of copy doesn't match original hexdigest.")
|
||
|
|
||
|
+
|
||
|
+class CompareDigestTestCase(unittest.TestCase):
|
||
|
+
|
||
|
+ def test_compare_digest(self):
|
||
|
+ # Testing input type exception handling
|
||
|
+ a, b = 100, 200
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = 100, b"foobar"
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = b"foobar", 200
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = u"foobar", b"foobar"
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = b"foobar", u"foobar"
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+
|
||
|
+ # Testing bytes of different lengths
|
||
|
+ a, b = b"foobar", b"foo"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+ a, b = b"\xde\xad\xbe\xef", b"\xde\xad"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing bytes of same lengths, different values
|
||
|
+ a, b = b"foobar", b"foobaz"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+ a, b = b"\xde\xad\xbe\xef", b"\xab\xad\x1d\xea"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing bytes of same lengths, same values
|
||
|
+ a, b = b"foobar", b"foobar"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+ a, b = b"\xde\xad\xbe\xef", b"\xde\xad\xbe\xef"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing bytearrays of same lengths, same values
|
||
|
+ a, b = bytearray(b"foobar"), bytearray(b"foobar")
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing bytearrays of diffeent lengths
|
||
|
+ a, b = bytearray(b"foobar"), bytearray(b"foo")
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing bytearrays of same lengths, different values
|
||
|
+ a, b = bytearray(b"foobar"), bytearray(b"foobaz")
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing byte and bytearray of same lengths, same values
|
||
|
+ a, b = bytearray(b"foobar"), b"foobar"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+ self.assertTrue(hmac.compare_digest(b, a))
|
||
|
+
|
||
|
+ # Testing byte bytearray of diffeent lengths
|
||
|
+ a, b = bytearray(b"foobar"), b"foo"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+ self.assertFalse(hmac.compare_digest(b, a))
|
||
|
+
|
||
|
+ # Testing byte and bytearray of same lengths, different values
|
||
|
+ a, b = bytearray(b"foobar"), b"foobaz"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+ self.assertFalse(hmac.compare_digest(b, a))
|
||
|
+
|
||
|
+ # Testing str of same lengths
|
||
|
+ a, b = "foobar", "foobar"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing str of diffeent lengths
|
||
|
+ a, b = "foo", "foobar"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing bytes of same lengths, different values
|
||
|
+ a, b = "foobar", "foobaz"
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # Testing error cases
|
||
|
+ a, b = u"foobar", b"foobar"
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = b"foobar", u"foobar"
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = b"foobar", 1
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = 100, 200
|
||
|
+ self.assertRaises(TypeError, hmac.compare_digest, a, b)
|
||
|
+ a, b = "fooä", "fooä"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ # subclasses are supported by ignore __eq__
|
||
|
+ class mystr(str):
|
||
|
+ def __eq__(self, other):
|
||
|
+ return False
|
||
|
+
|
||
|
+ a, b = mystr("foobar"), mystr("foobar")
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+ a, b = mystr("foobar"), "foobar"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+ a, b = mystr("foobar"), mystr("foobaz")
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+ class mybytes(bytes):
|
||
|
+ def __eq__(self, other):
|
||
|
+ return False
|
||
|
+
|
||
|
+ a, b = mybytes(b"foobar"), mybytes(b"foobar")
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+ a, b = mybytes(b"foobar"), b"foobar"
|
||
|
+ self.assertTrue(hmac.compare_digest(a, b))
|
||
|
+ a, b = mybytes(b"foobar"), mybytes(b"foobaz")
|
||
|
+ self.assertFalse(hmac.compare_digest(a, b))
|
||
|
+
|
||
|
+
|
||
|
def test_main():
|
||
|
test_support.run_unittest(
|
||
|
TestVectorsTestCase,
|
||
|
ConstructorTestCase,
|
||
|
SanityTestCase,
|
||
|
- CopyTestCase
|
||
|
+ CopyTestCase,
|
||
|
+ CompareDigestTestCase,
|
||
|
)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
diff --git a/Modules/operator.c b/Modules/operator.c
|
||
|
--- a/Modules/operator.c
|
||
|
+++ b/Modules/operator.c
|
||
|
@@ -235,6 +235,132 @@ op_delslice(PyObject *s, PyObject *a)
|
||
|
#define spam2o(OP,ALTOP,DOC) {#OP, op_##OP, METH_O, PyDoc_STR(DOC)}, \
|
||
|
{#ALTOP, op_##OP, METH_O, PyDoc_STR(DOC)},
|
||
|
|
||
|
+
|
||
|
+
|
||
|
+/* compare_digest **********************************************************/
|
||
|
+
|
||
|
+/*
|
||
|
+ * timing safe compare
|
||
|
+ *
|
||
|
+ * Returns 1 of the strings are equal.
|
||
|
+ * In case of len(a) != len(b) the function tries to keep the timing
|
||
|
+ * dependent on the length of b. CPU cache locally may still alter timing
|
||
|
+ * a bit.
|
||
|
+ */
|
||
|
+static int
|
||
|
+_tscmp(const unsigned char *a, const unsigned char *b,
|
||
|
+ Py_ssize_t len_a, Py_ssize_t len_b)
|
||
|
+{
|
||
|
+ /* The volatile type declarations make sure that the compiler has no
|
||
|
+ * chance to optimize and fold the code in any way that may change
|
||
|
+ * the timing.
|
||
|
+ */
|
||
|
+ volatile Py_ssize_t length;
|
||
|
+ volatile const unsigned char *left;
|
||
|
+ volatile const unsigned char *right;
|
||
|
+ Py_ssize_t i;
|
||
|
+ unsigned char result;
|
||
|
+
|
||
|
+ /* loop count depends on length of b */
|
||
|
+ length = len_b;
|
||
|
+ left = NULL;
|
||
|
+ right = b;
|
||
|
+
|
||
|
+ /* don't use else here to keep the amount of CPU instructions constant,
|
||
|
+ * volatile forces re-evaluation
|
||
|
+ * */
|
||
|
+ if (len_a == length) {
|
||
|
+ left = *((volatile const unsigned char**)&a);
|
||
|
+ result = 0;
|
||
|
+ }
|
||
|
+ if (len_a != length) {
|
||
|
+ left = b;
|
||
|
+ result = 1;
|
||
|
+ }
|
||
|
+
|
||
|
+ for (i=0; i < length; i++) {
|
||
|
+ result |= *left++ ^ *right++;
|
||
|
+ }
|
||
|
+
|
||
|
+ return (result == 0);
|
||
|
+}
|
||
|
+
|
||
|
+PyDoc_STRVAR(compare_digest__doc__,
|
||
|
+"compare_digest(a, b) -> bool\n"
|
||
|
+"\n"
|
||
|
+"Return 'a == b'. This function uses an approach designed to prevent\n"
|
||
|
+"timing analysis, making it appropriate for cryptography.\n"
|
||
|
+"a and b must both be of the same type: either str (ASCII only),\n"
|
||
|
+"or any type that supports the buffer protocol (e.g. bytes).\n"
|
||
|
+"\n"
|
||
|
+"Note: If a and b are of different lengths, or if an error occurs,\n"
|
||
|
+"a timing attack could theoretically reveal information about the\n"
|
||
|
+"types and lengths of a and b--but not their values.\n");
|
||
|
+
|
||
|
+static PyObject*
|
||
|
+compare_digest(PyObject *self, PyObject *args)
|
||
|
+{
|
||
|
+ PyObject *a, *b;
|
||
|
+ int rc;
|
||
|
+
|
||
|
+ if (!PyArg_ParseTuple(args, "OO:compare_digest", &a, &b)) {
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Unicode string */
|
||
|
+ if (PyUnicode_Check(a) && PyUnicode_Check(b)) {
|
||
|
+ rc = _tscmp(PyUnicode_AS_DATA(a),
|
||
|
+ PyUnicode_AS_DATA(b),
|
||
|
+ PyUnicode_GET_DATA_SIZE(a),
|
||
|
+ PyUnicode_GET_DATA_SIZE(b));
|
||
|
+ }
|
||
|
+ /* fallback to buffer interface for bytes, bytesarray and other */
|
||
|
+ else {
|
||
|
+ Py_buffer view_a;
|
||
|
+ Py_buffer view_b;
|
||
|
+
|
||
|
+ if ((PyObject_CheckBuffer(a) == 0) & (PyObject_CheckBuffer(b) == 0)) {
|
||
|
+ PyErr_Format(PyExc_TypeError,
|
||
|
+ "unsupported operand types(s) or combination of types: "
|
||
|
+ "'%.100s' and '%.100s'",
|
||
|
+ Py_TYPE(a)->tp_name, Py_TYPE(b)->tp_name);
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (PyObject_GetBuffer(a, &view_a, PyBUF_SIMPLE) == -1) {
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+ if (view_a.ndim > 1) {
|
||
|
+ PyErr_SetString(PyExc_BufferError,
|
||
|
+ "Buffer must be single dimension");
|
||
|
+ PyBuffer_Release(&view_a);
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (PyObject_GetBuffer(b, &view_b, PyBUF_SIMPLE) == -1) {
|
||
|
+ PyBuffer_Release(&view_a);
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+ if (view_b.ndim > 1) {
|
||
|
+ PyErr_SetString(PyExc_BufferError,
|
||
|
+ "Buffer must be single dimension");
|
||
|
+ PyBuffer_Release(&view_a);
|
||
|
+ PyBuffer_Release(&view_b);
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ rc = _tscmp((const unsigned char*)view_a.buf,
|
||
|
+ (const unsigned char*)view_b.buf,
|
||
|
+ view_a.len,
|
||
|
+ view_b.len);
|
||
|
+
|
||
|
+ PyBuffer_Release(&view_a);
|
||
|
+ PyBuffer_Release(&view_b);
|
||
|
+ }
|
||
|
+
|
||
|
+ return PyBool_FromLong(rc);
|
||
|
+}
|
||
|
+
|
||
|
static struct PyMethodDef operator_methods[] = {
|
||
|
|
||
|
spam1o(isCallable,
|
||
|
@@ -318,6 +444,8 @@ spam2(ne,__ne__, "ne(a, b) -- Same as a!
|
||
|
spam2(gt,__gt__, "gt(a, b) -- Same as a>b.")
|
||
|
spam2(ge,__ge__, "ge(a, b) -- Same as a>=b.")
|
||
|
|
||
|
+ {"_compare_digest", (PyCFunction)compare_digest, METH_VARARGS,
|
||
|
+ compare_digest__doc__},
|
||
|
{NULL, NULL} /* sentinel */
|
||
|
|
||
|
};
|
||
|
|