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