Issue #15061: Re-implemented hmac.compare_digest() in C

This commit is contained in:
Christian Heimes 2012-06-24 13:48:32 +02:00
parent 605a62ddb1
commit 6cea65555c
5 changed files with 234 additions and 30 deletions

View file

@ -159,6 +159,146 @@ is_not(PyObject *s, PyObject *a)
#undef spam2
#undef spam1o
#undef spam1o
/* 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 the equivalent of 'a == b', but avoid any short circuiting to\n"
"counterfeit timing analysis of input data. The function should be used to\n"
"compare cryptographic secrets. a and b must both either support the buffer\n"
"protocol (e.g. bytes) or be ASCII only str instances at the same time.\n"
"\n"
"Note: In case of an error or different lengths the function may disclose\n"
"some timing information about the types and lengths of a and b.\n");
static PyObject*
compare_digest(PyObject *self, PyObject *args)
{
PyObject *a, *b;
int rc;
PyObject *result;
if (!PyArg_ParseTuple(args, "OO:compare_digest", &a, &b)) {
return NULL;
}
/* ASCII unicode string */
if(PyUnicode_Check(a) && PyUnicode_Check(b)) {
if (PyUnicode_READY(a) == -1 || PyUnicode_READY(b) == -1) {
return NULL;
}
if (!PyUnicode_IS_ASCII(a) || !PyUnicode_IS_ASCII(b)) {
PyErr_SetString(PyExc_TypeError,
"comparing strings with non-ASCII characters is "
"not supported");
return NULL;
}
rc = _tscmp(PyUnicode_DATA(a),
PyUnicode_DATA(b),
PyUnicode_GET_LENGTH(a),
PyUnicode_GET_LENGTH(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);
}
result = PyBool_FromLong(rc);
Py_INCREF(result);
return result;
}
/* operator methods **********************************************************/
#define spam1(OP,DOC) {#OP, OP, METH_VARARGS, PyDoc_STR(DOC)},
#define spam2(OP,ALTOP,DOC) {#OP, op_##OP, METH_VARARGS, PyDoc_STR(DOC)}, \
{#ALTOP, op_##OP, METH_VARARGS, PyDoc_STR(DOC)},
@ -227,6 +367,8 @@ spam2(ne,__ne__, "ne(a, b) -- Same as a!=b.")
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 */
};