• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Пишем свою программу для шифрования файлов

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Сегодня мы с тобой разработаем собственную программу для шифрования файлов. Писать будем на C++ и используем криптоалгоритмы из библиотеки Для просмотра ссылки Войди или Зарегистрируйся. Давай посмотрим, какие подводные камни есть при разработке подобных программ и как ненароком не помочь атакующему.

Что помогает атакующему взломать шифрование​

Значительно облегчить задачу атакующему, который пытается взломать зашифрованный файл, может ряд факторов:

  • Длина и сложность пароля. Например, перебор 4-символьного пароля займет гораздо меньше времени, чем 12-символьного, а если расширить алфавит, например использовав буквы верхнего и нижнего регистра, цифры и спецсимволы, то это значительно увеличивает пространство вариантов для перебора, что сильно замедлит взлом. Разумеется, пароли не должны быть любым словарным словом или производным от него, иначе такой пароль будет взломан атакой по словарю. Также существуют так называемые rainbow tables («радужные таблицы») — предвычисленные таблицы хешей паролей. Они позволяют находить исходный пароль по его хешу. Если для шифрования или хеширования пароля не использовались соль или другие защитные меры, такие атаки могут быть достаточно эффективными.
  • Ошибки в реализации криптографии. Как уже много раз говорилось, не стоит изобретать свои алгоритмы шифрования и лучше придерживаться готовых и популярных библиотек с открытым исходным кодом. Выбирай проверенные временем и криптоаналитиками алгоритмы шифрования, хеширования и KDF.
  • Метаданные. Программы шифрования файлов могут оставлять кучу метаданных: например, создавать в зашифрованном файле заголовок, в котором указываются алгоритмы шифрования и хеширования, версия программы и еще куча данных. Практически все эти данные могут помочь атакующему во взломе. Если уже заранее известны все используемые алгоритмы, то нужно просто таргетированно атаковать их. Даже простая смена расширения файла на уникальное для твоей программы иногда может помочь.
  • Известные паттерны открытого текста. Если атакующий знает часть исходного текста, он может применить Для просмотра ссылки Войди или Зарегистрируйся. К примеру, знание, что файл содержит определенную стандартную информацию, может помочь в атаке (например, когда есть стандартный заголовок файла).
  • Настройки по умолчанию. Если в программе есть какие‑то значения по умолчанию, то в большинстве случаев именно они и будут использоваться. Если это программа для шифрования, то пользователи будут выбирать алгоритмы и их параметры, заданные изначально.
Как можно всему этому противостоять? Давай посмотрим на вопрос глазами разработчика шифрующей программы. Вот что его продукт должен делать:


  • из пользовательского пароля выработать ключ шифрования;
  • зашифровать или расшифровать данные;
  • учесть при этом перечисленные векторы атаки.
Звучит просто, но есть некоторые тонкости, которые мы обсудим далее.

Случайные числа и важность PRNG​

Перед началом работы с шифрованием необходимо познакомиться с базовыми понятиями криптографии, с которыми придется столкнуться и которые нужно будет понимать: соль (salt) и вектор инициализации (initialization vector, IV). Это два компонента, которые используются в криптографических процессах для повышения безопасности. Хотя они служат схожим целям (предотвращение предсказуемости), их роли и способы использования различаются.

Соль применяется в процессе хеширования для создания уникальных хешей (в том числе KDF), даже если пароль или другие исходные данные для хеширования одинаковы. Это усложняет попытки атак методом подбора и rainbow-атак, поскольку добавляет случайность к хешу. Какую длину соли выбрать? Если опустить некоторые математические расчеты (которые все равно никто не читает), то можно сказать, что длина соли в 256 бит и более считается сегодня безопасной.

Вектор инициализации применяется в симметричном шифровании (например, в режиме CBC, CFB или GCM) и нужен для того, чтобы одинаковые сообщения, зашифрованные одинаковым ключом, давали разные результаты. Это предотвращает предсказуемость зашифрованных данных, улучшая их безопасность.

Соль и IV должны быть случайными, но случайность соли более критична, чем случайность IV. Строго говоря, IV должен быть просто уникальным для зашифрованных данных при использовании режима GCM (для режима AEAD более верно его назвать nonce). Библиотека Botan, которая используется в статье, позволяет генерировать случайные числа при помощи функций randomize_with_ts_input и randomize.

Шифруем файлы​


Как создается ключ шифрования​

После того как пользователь введет пароль для шифрования данных, его необходимо превратить в ключ шифрования. Для этого придуманы специальные математические функции выработки ключа из пароля — Key Derivation Function (KDF). Еще их называют «медленные хеши», потому что ключ из пароля создается медленно: эта процедура требует значительных ресурсов процессора и оперативной памяти. Так сделано намеренно, чтобы противостоять атакам сплошного перебора (или по большому словарю): атакующему нужно перебрать сотни миллионов комбинаций, чтобы получить правильный пароль, а когда используются сильные функции KDF, скорость подбора будет критически мала и на взлом пароля уйдет слишком много времени.

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

В статье в качестве функции KDF я буду использовать Argon2id. Это финалист конкурса Для просмотра ссылки Войди или Зарегистрируйся. У этой функции есть несколько настраиваемых параметров: количество используемой памяти для выработки ключа, количество итераций и число потоков выполнения. Строго говоря, выбор параметров для разных ситуаций регламентируется Для просмотра ссылки Войди или Зарегистрируйся, но для статьи я использую такие параметры: размер памяти — 2 Гбайт, одна итерация, четыре потока. Разумеется, ты можешь их изменить как угодно, но лучше увеличивать, а не уменьшать.

Итак, код создания ключа из пароля будет таким.

Код:
// Имеется такая структура определения параметров ключа
    struct KeyParam {
        Botan::secure_vector<uint8_t> key;
        Botan::secure_vector<uint8_t> salt;
        Botan::secure_vector<uint8_t> iv;
    }

...

size_t derive_key_from_pass(
  // Передаем пароль пользователя
    const std::string& password,
    // Структура параметров ключа
    KeyParam& keydata
)
{
    try {

        // Используем PRNG, встроенный в Botan
        Botan::AutoSeeded_RNG rng;
        // Создали случайную соль длиной 256 бит
        rng.randomize_with_ts_input(&keydata.salt[0], 32);

    // Используем алгоритм Argon2id для KDF
        Botan:asswordHashFamily::create_or_throw("Argon2id")->
          // Установили параметры — размер памяти в килобайтах, число итераций и потоков
            from_params(2097120, 1, 4)->
            // Передали соль, пароль и получили ключ в keydata.key
            hash(keydata.key, password, keydata.salt);

        return ERROR_OK;
    }
    catch (const Botan::Exception& e) {
        std::cerr << "Botan exception: " << e.what() << std::endl;
        return ERROR_DERIVE_KEY;
    }
    catch (const std::runtime_error& e) {
        std::cerr << "Runtime error: " << e.what() << std::endl;
        return ERROR_DERIVE_KEY;
    }
    catch (...) {
        std::cerr << "Unknown error occurred during key derivation." << std::endl;
        return ERROR_DERIVE_KEY;
    }
}

Я снабдил код комментариями, чтобы была понятна каждая строка.

В библиотеке Botan есть функция derive_key, которая позволяет сразу выработать ключ, но в таком случае мы лишимся тонкой настройки и усиления KDF. Обрати внимание: это создание ключа для шифрования, а не для расшифрования. Все дело в том, что соль создается на лету в начале работы функции, а при операции расшифрования соль нужно будет передать в функцию derive_key_from_pass, для этого немного изменив ее.

Итак, ключ выработали, дальше по плану шифрование.

info​

Вероятно, ты мог подумать, что число потоков (параметр parallelism при вызове from_params) влияет только на производительность, но не на результат. Но это не так: в алгоритме Argon2id этот параметр влияет на структуру вычислений. Процесс хеширования разделяется на несколько потоков, которые выполняются параллельно. Из‑за этого порядок обработки блоков изменяется в зависимости от количества потоков, и разные значения parallelism приведут к разным промежуточным состояниям при хешировании и в итоге к другому хешу.

Шифруем файл​

Прежде чем начать шифровать, разберемся с таким понятием, как режим шифрования. Режим работы шифра — это способ применения алгоритма шифрования для обработки данных, с целью обеспечения конфиденциальности, целостности и аутентичности. Блочные шифры шифруют данные блоками определенного размера (например, 128 бит для AES). Если применять шифрование на уровне этих блоков без режима, то одинаковые блоки открытого текста всегда будут преобразовываться в одинаковые блоки шифротекста. Это делает шифротекст предсказуемым и уязвимым для атак. Современные режимы шифрования помогают еще и контролировать целостность зашифрованных данных (Authenticated Encryption with Associated Data, AEAD). Разумеется, мы будем использовать один из таких режимов, под названием GCM (Galois/Counter Mode).

info​

У режима GCM есть слабое место — слишком короткий nonce. Это ограничивает размер шифруемых данных до 64 Гбайт на одном nonce. В данный момент ожидается исправленная версия — режим Для просмотра ссылки Войди или Зарегистрируйся.
Для начала создадим и инициализируем шифратор. Для примера используем алгоритм AES в режиме GCM, но, разумеется, можно добавить несколько алгоритмов шифрования на выбор.


Код:
std::unique_ptr<Botan::AEAD_Mode> create_cipher(
    const Botan::SymmetricKey& key,
    const Botan::InitializationVector& iv,
    // Сделаем универсальный шифратор для шифрования и расшифрования
    const std::string& mode
)
{

    try {

    // Шифруем или расшифровываем?
    auto operation = (mode == "Encrypt") ?
    Botan::Cipher_Dir::Encryption :
        (mode == "Decrypt") ? Botan::Cipher_Dir:ecryption :
        throw std::invalid_argument("Invalid mode: " + mode);

    std::unique_ptr<Botan::AEAD_Mode> cipher_mode;

    Botan::SymmetricKey key(keyparams.key.data(), keyparams.key.size());

  // Задали операцию, шифр и режим
    cipher_mode = Botan::AEAD_Mode::create_or_throw("AES-GCM", operation);

  // Передали ключ и IV
    cipher_mode->set_key(key);
    cipher_mode->start(iv);

    }
    catch (...) {

        throw std::runtime_error("Failed to create cipher mode");
    }

    return cipher_mode;
}

Шифратор создан. Теперь все готово для процесса шифрования.

Код:
size_t encrypt_file(
    const std::string& inputFile,
    const std::string& outputFile,
    const KeyParam& keyparams
)
{

    try {

        // in — файл, который шифруется (прочитан в output_data)
        // out — выходной зашифрованный файл

        std::unique_ptr<Botan::AEAD_Mode> cryptor;
        Botan::AutoSeeded_RNG rng;

    // Для режима GCM IV должен быть 96 бит
        Botan::InitializationVector iv(rng, 12);

        // В выходной файл записываются IV и соль, требуемые для расшифровки
        out.write(reinterpret_cast<const char*>(iv.data()), iv.size());
        out.write(reinterpret_cast<const char*>(keyparams.salt.data()), keyparams.salt.size());

        cryptor = create_cipher(key, iv, "Encrypt");

    // Шифруем разом весь буфер output_data, в который прочитан шифруемый файл
        cryptor->finish(output_data, 0);

        out.write(reinterpret_cast<const char*>(output_data.data()), output_data.size());

        return ERROR_OK;

    catch (...)
    {
        in.close();
        out.close();

        return ERROR_ENCRYPT;
    }
}

После того как функция отработает, на выходе мы получим зашифрованный файл, имеющий в качестве заголовка IV (для расшифровки) и соль (для KDF), то есть случайные данные, которые нельзя отличить от самого зашифрованного файла. По сути, у нашего зашифрованного файла ноль метаданных, связанных с шифрованием.

info​

Помни, что все метаданные могут быть использованы против тебя атакующим — он заранее будет знать, с какими параметрами взламывать твой зашифрованный файл. Что касается соли и IV, в соответствии с Для просмотра ссылки Войди или Зарегистрируйся в секрете нужно держать только ключ.

Как проверить правильность расшифровки и целостность данных​

У нас есть зашифрованный нами файл, теперь нам нужно его как‑то расшифровать. Программа для расшифровки должна, кроме прочего, понять, что файл расшифрован правильно, — это реализуется за счет использования AEAD-режима шифрования.

Мы сможем переиспользовать основную часть уже написанного кода, внеся небольшие изменения.

В первую очередь подправим функцию derive_key_from_pass — для расшифрования нам не надо генерировать соль, вместо этого ее нужно будет передать в эту функцию в качестве параметра. Разумеется, как соль, так и IV нужно будет извлечь из зашифрованного файла, просто прочитав их в самом начале.

После этого приступаем к непосредственной расшифровке:

Код:
size_t decrypt_file(
    const std::string& inputFile,
    const std::string& outputFile,
    const Botan::InitializationVector& iv
)
{

    try {
        // in — файл, который расшифровывается
        // out — выходной расшифрованный файл
        // IV мы просто прочитали в начале файла
        // output_data — буфер, содержащий зашифрованный файл

        std::unique_ptr<Botan::AEAD_Mode> cryptor;

        cryptor = create_cipher(key, iv, "Decrypt");

        // Расшифровываем весь буфер output_data, в который прочитан зашифрованный файл
        cryptor->finish(output_data, 0);

        // Записываем на диск расшифрованный файл
        out.write(reinterpret_cast<const char*>(output_data.data()), output_data.size());

        return ERROR_OK;

  // Проверка на целостность расшифрованных данных
    catch (const Botan::Invalid_Authentication_Tag&)
    {
        in.close();
        out.close();

        return ERROR_DECRYPT;
    }
}
Обрати внимание на исключение Botan::Invalid_Authentication_Tag — оно сигнализирует, что твои данные были расшифрованы неправильно (по любой причине, будь то неправильный пароль или поврежденный файл). Если ты захочешь встроить в свое приложение возможность шифрования несколькими алгоритмами на выбор, то при помощи этого исключения легко организовать перебор возможных вариантов расшифрования на разных алгоритмах: если все алгоритмы перепробованы, но исключение все‑таки возникает, значит, пароль неверный.

info​

В статье я ничего не говорил о безопасном удалении ключей из памяти и прочих подобных вещах. В ситуации, когда злоумышленник способен получить доступ к твоему компьютеру на уровне, достаточном для снятия дампов ОЗУ, любое шифрование, скорее всего, теряет смысл.

Что с квантовыми вычислениями?​

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

Асимметричное шифрование: квантовые компьютеры практически полностью уничтожат безопасность RSA, эллиптических криптографических систем и других схем, основанных на дискретных логарифмах и факторизации, если будут реализованы мощные квантовые компьютеры. Но в той же библиотеке Botan уже реализована постквантовая асимметрика.

С симметричным шифрованием ситуация лучше, но квантовые компьютеры все же способны ослабить безопасность симметричных алгоритмов. Основной виновник — Для просмотра ссылки Войди или Зарегистрируйся, который позволяет квантовому компьютеру ускорить атаку брутфорсом.
Алгоритм Гровера может искать в неупорядоченных данных, таких как пространство паролей или ключей, с квадратичным ускорением. Это значит, что если на классическом компьютере требуется 2^n операций для перебора всех возможных ключей, то на квантовом компьютере с алгоритмом Гровера это число сократится до 2^n/2.

В таком случае симметричные алгоритмы будут ослаблены квантовыми компьютерами в два раза, но это не приведет к полной компрометации. Для защиты от квантовых атак можно просто удвоить длину ключа, что значительно повысит безопасность. Можно, например, использовать AES-256 вместо AES-128 или взять Threefish-512 для обеспечения стойкости 256 бит.

Выводы​

Сегодня ты узнал больше о защите данных с помощью симметричного шифрования и теперь способен написать ПО, шифрование которого очень непросто взломать, даже если у атакующего будут полные исходные тексты твоей программы. Надеюсь, тебя увлечет тема криптографии, потому что в современном мире только шифрование способно надежно защитить твои данные от киберпреступников.
 
Activity
So far there's no one here
Сверху Снизу