Мой защищённый контейнер

    При разработке проекта на C++ возникла необходимость создать защищённый контейнер. Вернее, кросс-платформенные на уровне исходного кода классы, поддерживающие защищённое хранение информации от нескольких байт до гигабайтов, что делает необходимым поддержку потокового шифрования/дешифрования.
    Не имея альтернативы нанять на данном этапе профессионального криптографа, приступил к созданию велосипеда.
    Данная статья написана для открытого обсуждения предлагаемых решений людьми, близкими к криптографии, а также для тех, кто будет проходить этот путь с нуля, как экономия их времени и сил.

    В ходе изучения и анализа информации были выбраны OpenSSL (отличная скорость работы судя по сравнению с другими библиотеками, не испытал трудностей при включении в проект, кроссплатформенность), а также алгоритм шифрования AES (Rijndael) с ключом 128, 192, 256 на выбор, как признанный (соответствующим комитетом сами знаете кого) современный стандарт шифрования.

    Самый первый вопрос — криптографически стойкий генератор (псевдо)случайных чисел и его установка (сидирование). Только он годен для генерации, например, криптостойкого ключа. Алгоритм генератора, вполне логично, взят из OpenSSL, а вот с сидированием вопрос сложнее. POSIX системы, у которых есть /dev/*random, снимают с меня ответственность за случайный seed. Что касается Windows, то в OpenSSL можно взять набор сообщений к окну (лучше) и содержимое экрана (хуже). Несмотря на это, решение с сообщениями в окно мне не очень понравилось, так как это лишние проблемы пользователю. Я остановился на копии экрана плюс свои пять копеек. Они заключались в том, что я использую генерацию самим Windows уникального GUID, так что в seed перед копией экрана я добавляю некий крокодил из GetTickCount() XOR Системное_время XOR GUID c небольшими сдвигами и преобразованиями.
    Кстати говоря, в реальном тесте на Windows XP SP2 x64 генератор OpenSSL заявил что уже достаточно сидирован по умолчанию. Почему — я не знаю, но в качестве потенциальной альтернативы решение предложено (и активируется на системах, где потребуется дополнительное сидирование).

    Идём дальше. Предполагая, что сам ключ хранится отдельно, таким образом, контейнер должен содержать всю необходимую информацию для расшифровки. В случае AES в потоковом (cbc) режиме, необходимо хранить начальный вектор инициализации (iv0), а также точную длину данных, так как алгоритм работает с блоками по 16 байт (128 бит).

    Что касается хранения начального вектора, то здесь существует стандарт RFC 3394, однако, при внимательном изучении, оказалось, что в нём используется постоянное значение начального вектора инициализации. Вместе с этим, в книге «An introduction to cryptography» чётко написано, что начальный вектор ни в коем случае не должен быть постоянным (здесь). Поэтому я отказался от этого алгоритма в пользу создания вектора инициализации криптографически стойким генератором случайных чисел, что конечно хуже чем инициализация дополнительным случайным сидом, но гораздо лучше чем постоянный вектор.

    Длина начального вектора выбрана 16 байт, равной длине блока данных.

    Перейдём к заголовку. Чтобы сделать длину заголовка кратной 16 байтам (размер блок AES), вспоминаем длину вектора 16 байт, тогда все дополнительные данные лучше уместить в другие 16 байт. 8 байт занял размер (ага, большие данные). Оставшиеся 8 байт я отдал под магическое число, с некоторой вероятностью гарантирующее, что ключ подобран правильно. И здесь я тоже попытался уйти от заранее заготовленного магического числа (M). Моя идея состоит в следующем: я беру заранее заготовленное 4-х байтное число-константу. Генерирую два стойких случайных 4-х байтных A1 и A2. При этом первое число я оставляю как есть, а второе изменяю с тем условием, что
    A1 + A2' = M

    Тогда
    A2' = A2 + D

    и
    D = M — (A1 + A2)


    При этом, для определения правильности пароля, я расшифровываю первый 16-ти байтный блок заголовка, беру два первых dword-а, суммирую и смотрю что получилось. Вероятность совпадения значений невелика, но конечна. В моём случае это вполне приемлимо (если неверен ключ, то это уже проблемы злоумышленника что он получил ложно-расшифрованные данные).

    Выравнивание размера данных по 16-байтам я делаю наиболее трививально, путём дописывания в конец нужного числа случайных байтов, которые также создаю криптографически стойким генератором OpenSSL.

    Таким образом, я избавляюсь от постоянных чисел в исходных данных и (как было показано выше) постоянного вектора инициализации.
    Дальше дело техники: 48-байтный заголовок я кодирую AES в блочном режиме (ecb), потом идут блоки данных, шифрованные в потоковом режиме (cbc).

    image

    P.S. Что можно бы было улучшить:
    + сделать более случайным начальный сид в Windows (и POSIX?)
    + создавать начальный вектор инициализации при помощи сида, а не генератором
    + возможно, лучше бы было шифровать заголовок в том же потоковом режиме как часть данных
    + сделать более надёжной проверку правильности ключа
    +…?
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 8

      0
      Не очень понял, на счет генерации случайного числа в Win. Почему нельзя использовать стандартное CAPI? Из-за закрытости кода?
      0
      По поводу рандома. Если Вы ставили TrueCrypt, Вы наверное видели, как система генерирует рандомный ключ. Там и движения мышей по окну, и ежесекундное обновление ключа, если такого движения не происходит. Может быть, подсмотреть исходный код TrueCrypt и отсыпать оттуда идей?
        0
        Я немного писал про это в тексте. Решение с взятием сообщений от окна (движение мышью, клавиатура, системные сообщения) мне не очень нравится в виду неудобств для пользователя. Меня лично необходимость елозить мышкой (если это не игра Hammerfight :) напрягает. К тому же, я мог бы потеоретизировать на тему «90% пользователей генерируют в целом схожие пассы мышкой, что вряд ли лучше хорошего PRNG по ДРУГОМУ алгоритму, поксоренному на всякие процессорные такты и прочее», если бы был профессионалом в криптографии.
          +1
          Я видел Вашу ремарку об этом. Я не знаю откуда, но трукрипт берет рандомные данные НЕ ТОЛЬКО с мыши/клавиатуры. Может быть, эта та самая хрень которая нужна и Вам?
            0
            Спасибо за наводку. Будет время, посмотрю.
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Насколько я понимаю, формирование вектора по номеру посылки заранее не слишком сложной функцией, вряд ли лучше чем вектор, собранный из двух РАЗНЫХ криптостойких PRNG, поксоренный на число тактов процессора и внутренние такты ОС со смещением. Разве нет?

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое