Шпаргалка по криптографическому хранению данных

Введение

В этой статье приведена простая модель, которой следует руководствоваться при реализации решений для защиты данных в состоянии хранения.

Пароли не следует хранить с использованием обратимого шифрования — вместо этого следует использовать безопасные алгоритмы хеширования паролей. В Password Storage Cheat Sheet приведены дополнительные рекомендации по хранению паролей.

Архитектурное проектирование

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

Этот процесс следует начать с рассмотрения модели угроз приложения (то есть того, от кого вы пытаетесь защитить эти данные).

Использование специализированных систем управления секретами или ключами может обеспечить дополнительный уровень защиты безопасности, а также значительно упростить управление секретами, однако ценой дополнительной сложности и административных накладных расходов; поэтому применение этого решения может оказаться неосуществимым для некоторых приложений. Обратите внимание, что многие облачные среды предоставляют такие службы, поэтому ими следует пользоваться там, где это возможно. В Secrets Management Cheat Sheet приведены дополнительные рекомендации по этой теме.

Где выполнять шифрование

Шифрование можно выполнять на ряде уровней стека приложения, например:

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

Минимизируйте хранение конфиденциальной информации

Лучший способ защитить конфиденциальную информацию — вообще не хранить её. Хотя это относится ко всем видам информации, чаще всего это применимо к данным кредитных карт, поскольку они весьма желательны для злоумышленников, а PCI DSS устанавливает столь строгие требования к способам их хранения. Где это возможно, хранения конфиденциальной информации следует избегать.

Алгоритмы

Для симметричного шифрования в качестве предпочтительного алгоритма следует использовать AES с ключом длиной не менее 128 бит (в идеале — 256 бит) и безопасным режимом.

Для шифрования с открытым ключом используйте поддерживаемую библиотеку, реализующую установленную гибридную схему шифрования, например Hybrid Public Key Encryption (HPKE), которая объединяет установление ключа, выработку ключа и аутентифицированное симметричное шифрование. X25519 на Curve25519 — примитив согласования ключа; сам по себе он не шифрует хранимые данные. Выберите подходящий режим аутентификации HPKE, если требуется аутентификация отправителя; базовый режим не аутентифицирует отправителя.

Если необходимо использовать RSA, убедитесь, что длина ключа составляет не менее 2048 бит.

Учёт постквантовой безопасности

Криптография с открытым ключом на основе RSA и ECC (включая Curve25519) не является постквантово стойкой. Будущий квантовый компьютер, криптографически значимый по своим возможностям, мог бы взломать их, что важно для данных, которые должны оставаться конфиденциальными в течение длительных периодов.

Если требуется постквантовое установление ключа, используйте поддерживаемый протокол или интеграцию библиотеки стандартизованного механизма, такого как ML-KEM (NIST FIPS 203), обычно в гибридной конструкции наряду с классическим алгоритмом в период миграции. ML-KEM устанавливает общий секрет для симметричного шифрования; он не заменяет AES для шифрования хранимых данных. Включите в план миграции защиту хранимых ключей шифрования с использованием криптографии с открытым ключом.

Рекомендации по миграции см. в Post-Quantum Cryptography Cheat Sheet.

При выборе криптографических алгоритмов для конкретного случая использования учитывайте:

В некоторых случаях могут существовать регуляторные требования, ограничивающие алгоритмы, которые можно использовать, например FIPS 140-3 или PCI DSS.

Пользовательские алгоритмы

Не делайте этого.

Режимы шифрования

Существуют различные режимы, которые можно использовать, чтобы позволить блочным шифрам (таким как AES) шифровать произвольные объёмы данных так же, как это сделал бы потоковый шифр. Эти режимы имеют различные характеристики безопасности и производительности, и их полное обсуждение выходит за рамки этой шпаргалки. Некоторые режимы предъявляют требования к генерации безопасных векторов инициализации (IV) и другим атрибутам, но библиотеке следует обрабатывать это автоматически.

Там, где они доступны, всегда следует использовать аутентифицированные режимы. Они обеспечивают гарантии целостности и подлинности данных, а также конфиденциальности. Наиболее часто используемые аутентифицированные режимы — GCM и CCM, которые следует использовать в первую очередь.

Если GCM или CCM недоступны, следует использовать режим CTR или режим CBC. Поскольку они не дают никаких гарантий подлинности данных, следует реализовать отдельную аутентификацию, например с использованием метода Encrypt-then-MAC. При использовании этого метода с сообщениями переменной длины необходимо соблюдать осторожность.

ECB не следует использовать вне очень специфических обстоятельств.

Случайное дополнение

Для шифрования RSA используйте реализацию RSAES-OAEP (Optimal Asymmetric Encryption Padding) из поддерживаемой библиотеки, как указано в RFC 8017, разделе 7.1. OAEP использует рандомизированное кодирование и функции генерации маски; простое добавление случайных байтов перед сообщением не является заменой. Не используйте RSA без схемы дополнения для шифрования.

Безопасная генерация случайных чисел

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

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

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

Криптографически стойкие генераторы псевдослучайных чисел (CSPRNG) предназначены для создания случайности значительно более высокого качества (точнее, с большим количеством энтропии), что делает их безопасными для использования в функциональности, чувствительной к безопасности. Однако они медленнее и сильнее нагружают CPU, а в некоторых обстоятельствах могут блокироваться, когда запрашиваются большие объёмы случайных данных. Поэтому, если нужны большие объёмы случайных данных, не связанных с безопасностью, они могут не подходить.

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

ЯзыкНебезопасные функцииКриптографически стойкие функции
Crandom(), rand()getrandom(2)
JavaMath.random(), StrictMath.random(), java.util.Random, java.util.SplittableRandom, java.util.concurrent.ThreadLocalRandomjava.security.SecureRandom, java.util.UUID.randomUUID()
PHParray_rand(), lcg_value(), mt_rand(), rand(), uniqid()Random\Engine\Secure в PHP 8.2, random_bytes(), random_int() в PHP 7, openssl_random_pseudo_bytes() в PHP 5
.NET/C#Random()RandomNumberGenerator
Objective-CПодклассы GKRandomSource, rand(), random()SecRandomCopyBytes, семейство arc4random на современных платформах Apple
Pythonrandom()secrets()
Rubyrand(), RandomSecureRandom
Gorand с использованием пакета math/randпакет crypto.rand
Rustrand::prng::XorShiftRngrand::prng::chacha::ChaChaRng и остальные CSPRNG библиотеки Rust.
Node.jsMath.random()crypto.randomBytes(), crypto.randomInt(), crypto.randomUUID()

UUID и GUID

Универсально уникальные идентификаторы (UUID или GUID) иногда используются как быстрый способ генерировать случайные строки. Хотя они могут предоставлять приемлемый источник случайности, это зависит от типа или версии создаваемого UUID.

В частности, UUID версии 1 состоят из высокоточной временной метки и MAC-адреса системы, которая их создала, поэтому они не являются случайными (хотя их может быть трудно угадать, учитывая, что временная метка имеет точность до ближайших 100 нс). UUID типа 4 генерируются случайным образом, хотя то, выполняется ли это с использованием CSPRNG, будет зависеть от реализации. Если не известно, что это безопасно в конкретном языке или фреймворке, полагаться на случайность UUID не следует.

Многоуровневая защита

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

Управление ключами

Процессы

Следует внедрить (и протестировать) формальные процессы, охватывающие все аспекты управления ключами, включая:

Генерация ключей

Ключи следует генерировать случайным образом с использованием криптографически безопасной функции, например одной из функций, рассмотренных в разделе «Безопасная генерация случайных чисел». Ключи не следует основывать на распространённых словах или фразах либо на «случайных» символах, полученных беспорядочным нажатием клавиш на клавиатуре.

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

1 Примечание переводчика: в оригинале ошибочно: «such as data separate data-encrypting and key-encrypting keys». Первое data перед separate прочитано как лишнее; перевод передаёт смысл «separate data-encrypting and key-encrypting keys».

Сроки жизни ключей и ротация

Ключи шифрования следует менять (или ротировать) на основании ряда различных критериев:

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

  1. Расшифровать их и повторно зашифровать новым ключом.
  2. Пометить каждый элемент идентификатором ключа, использованного для его шифрования, и хранить несколько ключей, позволяющих расшифровать старые данные.

Первый вариант в общем случае следует предпочесть, поскольку он существенно упрощает как код приложения, так и процессы управления ключами; однако он не всегда может быть осуществим. Следует учитывать, что старые ключи в общем случае следует хранить в течение определённого периода после их вывода из эксплуатации — на случай, если потребуется расшифровать старые резервные копии копий данных.

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

Хранение ключей

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

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

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

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

Secrets Management Cheat Sheet содержит более подробную информацию о безопасном хранении секретов.

Разделение ключей и данных

Если возможно, ключи шифрования следует хранить отдельно от зашифрованных данных. Например, если данные хранятся в базе данных, ключи следует хранить в файловой системе. Это означает, что если атакующий имеет доступ только к одному из них (например, посредством обхода каталогов или SQL-инъекции), он не сможет получить доступ одновременно и к ключам, и к данным.

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

Шифрование хранимых ключей

Если возможно, сами ключи шифрования следует хранить в зашифрованной форме. Для этого требуются как минимум два отдельных ключа:

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

KEK также следует иметь не меньшую стойкость, чем DEK. Рекомендации Google по конвертному шифрованию содержат дополнительные сведения об управлении DEK и KEK.

В более простых архитектурах приложений (таких как среды виртуального хостинга), где KEK и DEK невозможно хранить отдельно, ценность этого подхода ограничена, поскольку атакующий, вероятно, сможет получить оба ключа одновременно. Однако он может создать дополнительное препятствие для неквалифицированных атакующих.

Для генерации KEK из предоставленных пользователем входных данных (таких как парольная фраза) можно использовать функцию выработки ключа (KDF); полученный KEK затем будет использоваться для шифрования случайно сгенерированного DEK. Это позволяет легко менять KEK (когда пользователь меняет свою парольную фразу) без необходимости повторно шифровать данные (поскольку DEK остаётся тем же).

Ссылки