Hash Digest / Сравнение массивов в C # - PullRequest
1 голос
/ 31 декабря 2010

Я пишу приложение, которое должно проверять контрольные суммы HMAC-SHA256. Код, который у меня сейчас есть, выглядит примерно так:

    static bool VerifyIntegrity(string secret, string checksum, string data)
    {
        // Verify HMAC-SHA256 Checksum
        byte[] key = System.Text.Encoding.UTF8.GetBytes(secret);
        byte[] value = System.Text.Encoding.UTF8.GetBytes(data);
        byte[] checksum_bytes = System.Text.Encoding.UTF8.GetBytes(checksum);
        using (var hmac = new HMACSHA256(key))
        {
            byte[] expected_bytes = hmac.ComputeHash(value);
            return checksum_bytes.SequenceEqual(expected_bytes);
        }
    }

Я знаю, что это восприимчиво к времени атак .

Есть ли функция сравнения дайджестов сообщений в стандартной библиотеке? Я понимаю, что мог бы написать свой собственный метод сравнения, усиленный временем, но я должен верить, что он уже реализован в другом месте.

Ответы [ 3 ]

2 голосов
/ 31 декабря 2010

РЕДАКТИРОВАТЬ: Оригинальный ответ ниже - все еще стоит прочитать IMO, но о времени атаки ...

Страница, на которую вы ссылаетесь, дает некоторые интересные моменты об оптимизации компилятора.Учитывая, что вы знаете, что два байтовых массива будут одинаковой длины (при условии, что размер контрольной суммы не является особо секретным, вы можете сразу же вернуться, если длины разные), вы можете попробовать что-то вроде этого:

public static bool CompareArraysExhaustively(byte[] first, byte[] second)
{
    if (first.Length != second.Length)
    {
        return false;
    }
    bool ret = true;
    for (int i = 0; i < first.Length; i++)
    {
        ret = ret & (first[i] == second[i]);
    }
    return ret;
}

Теперь это все равно не займет одинаковое количество времени для всех входных данных - например, если два массива находятся в кеше L1, скорее всего, это будет быстрее, чем если быдолжен быть извлечен из основной памяти.Однако я подозреваю, что это вряд ли вызовет серьезную проблему с точки зрения безопасности.

Это нормально?Кто знает.Разные процессоры и разные версии CLR могут занимать разное количество времени для операции & в зависимости от двух операндов.По сути, это то же самое, что и вывод страницы, на которую вы ссылались, - что она, вероятно, так же хороша, как мы получим переносимым способом, но для этого потребуется проверка на каждой платформе, на которой вы пытаетесь работать.

По крайней мере, приведенный выше код использует только относительно простые операции.Я бы лично не использовал здесь операции LINQ, поскольку в некоторых случаях может происходить хитрая оптимизация.Я не думаю, что будет в этом случае - или их будет легко победить - но вам, по крайней мере, придется думать о них.С помощью приведенного выше кода, по крайней мере, существует достаточно тесная связь между исходным кодом и IL - оставляя «только» JIT-компилятор и оптимизацию процессора, чтобы беспокоиться о:)


Оригинальный ответ

Существует одна существенная проблема: для предоставления контрольной суммы необходимо иметь строку, форма которой в кодировке UTF-8 совпадает с контрольной суммой.Существует множество байтовых последовательностей, которые просто не представляют кодированный в UTF-8 текст.По сути, попытка кодировать произвольные двоичные данные в виде текста с использованием UTF-8 - плохая идея.

Base64, с другой стороны, в основном предназначен для этого:

static bool VerifyIntegrity(string secret, string checksum, string data)
{
    // Verify HMAC-SHA256 Checksum
    byte[] key = Encoding.UTF8.GetBytes(secret);
    byte[] value = Encoding.UTF8.GetBytes(data);
    byte[] checksumBytes = Convert.FromBase64String(checksum);
    using (var hmac = new HMACSHA256(key))
    {
        byte[] expectedBytes = hmac.ComputeHash(value);
        return checksumBytes.SequenceEqual(expectedBytes);
    }
}

С другой стороны, вместо использования SequenceEqual в байтовом массиве, вы можете Base64 кодировать фактический хеш и посмотреть, соответствует ли это:

static bool VerifyIntegrity(string secret, string checksum, string data)
{
    // Verify HMAC-SHA256 Checksum
    byte[] key = Encoding.UTF8.GetBytes(secret);
    byte[] value = Encoding.UTF8.GetBytes(data);
    using (var hmac = new HMACSHA256(key))
    {
        return checksum == Convert.ToBase64String(hmac.ComputeHash(value));
    }
}

Я незнать что-нибудь лучше в рамках.Было бы не сложно написать специализированный оператор SequenceEqual для массивов (или общих реализаций ICollection<T>), который сначала проверял бы одинаковую длину ... но, учитывая, что хэши короткие, я бы об этом не беспокоился.

2 голосов
/ 31 декабря 2010

Если вы беспокоитесь о сроках SequenceEqual, вы всегда можете заменить его на что-то вроде этого:

checksum_bytes.Zip( expected_bytes, (a,b) => a == b ).Aggregate( true, (a,r) => a && r );

Возвращает тот же результат, что и SequenceEquals, но всегда проверяет каждый элемент перед тем, как дать ответ, что дает меньше шансов обнаружить что-либо с помощью временной атаки.

0 голосов
/ 31 декабря 2010

Насколько он подвержен временным атакам?Ваш код работает одинаковое количество времени в случае действительного или недействительного дайджеста.И вычислить дайджест / проверить дайджест выглядит как самый простой способ проверить это.

...