Защитить PII в базе данных веб-приложения путем шифрования открытым ключом в сочетании с закрытым ключом, защищенным паролями пользователя?
Цель:
Я хотел бы позволить пользователям создавать вопросы и собирать информацию от других пользователей в пользовательском веб-приложении (PHP/MySQL в среде хостинга) и защищать собранные данные.
Фон:
Заданные по умолчанию вопросы, на которые отвечают все пользователи, являются достаточно общими, поэтому их нельзя истолковывать как информацию, позволяющую установить личность (PII), что ограничивает мою ответственность за ее защиту, но пользователи, которые создают свои собственные вопросы, скорее всего, будут запрашивать PII, которая затем становится обязательством.
То, что я хотел бы сделать, это защитить эту информацию таким образом, чтобы в случае взлома учетной записи хостинга или базы данных (или обоих!), PII не был бы восстановлен без значительного объема работы, и даже тогда только небольшая часть будет теоретически восстановима.
Предложенное решение:
Предполагая встроенный в MySQL AES_ENCRYPT()/AES_DECRYPT()
Функции используются для шифрования таблицы PII, парольную фразу необходимо будет хранить в учетной записи хостинга, поэтому, если учетная запись хостинга будет скомпрометирована, данные могут быть легко прочитаны.
Поскольку пароли пользователей хорошо защищены (хешируются солью), я думаю о том, чтобы захватить их незашифрованный пароль во время аутентификации, зашифровать его и сохранить в сеансе PHP до тех пор, пока пользователь не выйдет из системы.
Для каждого пользователя будет создана комбинация открытого / закрытого ключа, причем закрытый ключ защищен паролем с помощью пароля пользователя + соли.
Затем, когда данные PII, основанные на пользовательских вопросах этого пользователя, добавляются в БД, открытый ключ пользователя будет использоваться для шифрования PII, который он собирал через приложение. Когда данные читаются (только когда пользователь вошел в систему), данные будут незашифрованы с помощью закрытого ключа пользователя (который разблокируется их паролем + солью).
Я вижу следующие преимущества:
- в худшем случае, когда серверы полностью скомпрометированы, код приложения считывается для поиска ключей шифрования, файлы сеанса PHP дешифруются для поиска паролей пользователя, затем дешифруются записи в таблице PII, связанной с этим пользователем, а затем только PII, собранный из вопросов. из зарегистрированных в настоящее время пользователей могут быть восстановлены. Любые пользователи, не вошедшие в систему, будут в безопасности.
- даже администратор базы данных или подобное не сможет прочитать PII.
Недостатки, которые я вижу:
- пользовательские пароли хранятся в восстанавливаемой форме, когда они вошли в систему.
- пользователи, которые забыли свои пароли, потеряли бы доступ к своим данным.
- каждый относительно небольшой бит данных занимает гораздо больше места в БД из-за шифрования.
Мой вопрос: есть ли лучший способ сделать это?
2 ответа
Я вижу ряд проблем с этим дизайном с точки зрения безопасности. Прежде всего, пароли никогда не должны быть зашифрованы, это уязвимость, выявленная CWE-257.
Дальше больше MySQL AES_ENCRYPT()
полная фигня по нескольким причинам. Он использует режим EBC, и вот хороший пример того, почему это дерьмо:
Исходное изображение:
EBC Mode (что такое MySQL AES_ENCRYPT()
использует):
Но если база данных скомпрометирована, злоумышленник собирается победить AES_ENCRYPT()
включив журнал запросов.
Следует избегать использования пароля пользователя для шифрования, вы должны использовать одноразовый шифратор. Если вы используете пароль, убедитесь, что вы используете функцию String2Key. Вы также должны использовать режим CBC или CMAC со случайным iv. Я действительно не понимаю, как асимметричная криптография может помочь. Асимметричная криптография очень медленная, интенсивная память. Эти данные, которые он защищает, становятся менее безопасными, когда злоумышленник контролирует сообщение, потому что вы можете сравнить зашифрованные текстовые сообщения. Вот почему случайный IV важен, а в асимметричном мире у вас нет такого уровня защиты.
Генерация ключей должна выглядеть примерно так:$key=string2key($base_nonce.$salt.$user_password)
Убедитесь, что выходные данные вашей функции string2key имеют тот же размер, что и ваше пространство клавиш. Таким образом, aes 128 нуждается в 128-битном ключе. Каждый пароль должен иметь свой собственный $salt
и $base
является криптографическим одноразовым номером, хранящимся в текстовом файле. (Злоумышленнику придется прочитать этот файл, прежде чем он сможет взломать ключ, если это значение велико, как 128 бит, то это спорный вопрос.) Каждое сообщение нуждается в своем собственном $iv
и это значение также должно быть криптографическим одноразовым номером (аналог солью). Я бы сгенерировал $salt
,$iv
а также $base_nonce
от /dev/urandom
, IV может храниться в виде простого текста в столбце в вашей базе данных вместе с зашифрованным текстом.
С юридической точки зрения, даже если вы создаете защищенную криптографическую систему, у вас все еще есть проблемы с внутренними угрозами, и если сервер полностью скомпрометирован, все данные все равно будут скомпрометированы. Это действительно не инженерная проблема.
Лучшая защита от юридической угрозы - это строгие условия, написанные опытным юристом.
Одна из проблем, с которой я столкнулся, заключается в следующем. Часть "все пользователи, не вошедшие в систему, будут в безопасности", слишком оптимистична. Защищая закрытый ключ паролем пользователя, вы открываете себя к различным атакам с использованием перебора паролей. Не только для текущих сессий, но и для всех. Один из эффективных способов - просто перечислить, скажем, 100 самых распространенных паролей, и просто опробовать их против всех пользователей. Атакующий обязан раскрыть некоторые ключи. (Я предполагаю, что вы либо сохраняете случайную соль для каждого пользователя вместе с записью пользователя, которую может видеть злоумышленник, либо у вас есть секретная соль, которую злоумышленник смог получить с помощью компрометации.)