Файлы Java .class
довольно легко декомпилировать. Как я могу защитить свою базу данных, если мне нужно использовать данные для входа в код?
Как я могу защитить имя пользователя и пароль MySQL от декомпиляции?
Ответы (6)
Никогда не вставляйте пароли в свой код жестко. Это недавно упоминалось в 25 самых опасных ошибках программирования:
Жесткое кодирование секретной учетной записи и пароля в вашем программном обеспечении чрезвычайно удобно - для опытных инженеров-реверс-инженеров. Если пароль один и тот же для всего вашего программного обеспечения, тогда каждый клиент становится уязвимым, когда этот пароль неизбежно становится известен. И поскольку это жестко запрограммировано, это очень сложно исправить.
Вы должны хранить информацию о конфигурации, включая пароли, в отдельном файле, который приложение читает при запуске. Это единственный реальный способ предотвратить утечку пароля в результате декомпиляции (никогда не компилируйте его в двоичный файл для начала).
Дополнительную информацию об этой распространенной ошибке можно найти в статье CWE-259. В статье содержится более подробное определение, примеры и много другой информации о проблеме.
В Java один из самых простых способов сделать это - использовать класс Preferences. Он предназначен для хранения всевозможных настроек программы, некоторые из которых могут включать имя пользователя и пароль.
import java.util.prefs.Preferences;
public class DemoApplication {
Preferences preferences =
Preferences.userNodeForPackage(DemoApplication.class);
public void setCredentials(String username, String password) {
preferences.put("db_username", username);
preferences.put("db_password", password);
}
public String getUsername() {
return preferences.get("db_username", null);
}
public String getPassword() {
return preferences.get("db_password", null);
}
// your code here
}
В приведенном выше коде вы можете вызвать метод setCredentials
после отображения диалогового окна с запросом имени пользователя и пароля. Когда вам нужно подключиться к базе данных, вы можете просто использовать методы getUsername
и getPassword
для получения сохраненных значений. Учетные данные для входа не будут жестко закодированы в ваших двоичных файлах, поэтому декомпиляция не представляет угрозы безопасности.
Важное примечание: файлы настроек представляют собой просто текстовые XML-файлы. Убедитесь, что вы предприняли соответствующие шаги для предотвращения просмотра сырых файлов неавторизованными пользователями (разрешения UNIX, разрешения Windows и т. Д.). В Linux, по крайней мере, это не проблема, потому что вызов Preferences.userNodeForPackage
создаст XML-файл в домашнем каталоге текущего пользователя, который в любом случае не читается другими пользователями. В Windows ситуация может быть иной.
Дополнительные важные примечания. В комментариях к этому и другим ответам было много дискуссий о том, какая архитектура является правильной для этой ситуации. В исходном вопросе на самом деле не упоминается контекст, в котором используется приложение, поэтому я расскажу о двух ситуациях, которые могу придумать. Первый - это случай, когда человек, использующий программу, уже знает (и имеет право знать) учетные данные базы данных. Во втором случае вы, разработчик, пытаетесь сохранить в секрете учетные данные базы данных от человека, использующего программу.
Первый случай: пользователю разрешено знать учетные данные для входа в базу данных
В этом случае подойдет решение, о котором я говорил выше. Класс Java Preference
будет хранить имя пользователя и пароль в виде обычного текста, но файл настроек будет доступен для чтения только авторизованному пользователю. Пользователь может просто открыть XML-файл настроек и прочитать учетные данные для входа в систему, но это не представляет угрозы для безопасности, поскольку пользователь изначально знал учетные данные.
Второй случай: попытка скрыть учетные данные для входа от пользователя
Это более сложный случай: пользователь не должен знать учетные данные для входа, но ему по-прежнему нужен доступ к базе данных. В этом случае пользователь, запускающий приложение, имеет прямой доступ к базе данных, что означает, что программе необходимо заранее знать учетные данные для входа. Упомянутое выше решение не подходит для этого случая. Вы можете сохранить учетные данные для входа в базу данных в файле настроек, но пользователь сможет читать этот файл, поскольку он будет владельцем. На самом деле, на самом деле нет хорошего способа безопасного использования этого кейса.
Правильный случай: использование многоуровневой архитектуры
Правильный способ сделать это - создать промежуточный уровень между сервером базы данных и клиентским приложением, который аутентифицирует отдельных пользователей и позволяет выполнять ограниченный набор операций. У каждого пользователя будут свои собственные учетные данные для входа, но не для сервера базы данных. Учетные данные позволят получить доступ к среднему уровню (уровню бизнес-логики) и будут разными для каждого пользователя.
У каждого пользователя будет свое собственное имя пользователя и пароль, которые можно будет хранить локально в файле настроек без какого-либо риска для безопасности. Это называется трехуровневой архитектурой (уровни - это ваш сервер базы данных, сервер бизнес-логики и клиентское приложение). Это более сложный, но на самом деле самый безопасный способ делать подобные вещи.
Основной порядок действий:
- Клиент аутентифицируется на уровне бизнес-логики, используя личное имя пользователя и пароль. Имя пользователя и пароль известны пользователю и никак не связаны с учетными данными для входа в базу данных.
- Если аутентификация прошла успешно, клиент делает запрос к уровню бизнес-логики, запрашивая некоторую информацию из базы данных. Например, инвентарь продуктов. Обратите внимание, что запрос клиента не является запросом SQL; это удаленный вызов процедуры, такой как
getInventoryList
. - Уровень бизнес-логики подключается к базе данных и получает запрошенную информацию. Уровень бизнес-логики отвечает за формирование безопасного SQL-запроса на основе запроса пользователя. Все параметры SQL-запроса должны быть очищены, чтобы предотвратить атаки с использованием SQL-инъекций.
- Уровень бизнес-логики отправляет список инвентаризации обратно в клиентское приложение.
- Клиент отображает список инвентаря пользователю.
Обратите внимание, что в течение всего процесса клиентское приложение никогда не подключается напрямую к базе данных. Уровень бизнес-логики получает запрос от аутентифицированного пользователя, обрабатывает запрос клиента на список инвентаризации и только после этого выполняет SQL-запрос.
Поместите пароль в файл, который будет читать приложение. НИКОГДА не вставляйте пароли в исходный файл. Период.
В Ruby есть малоизвестный модуль под названием DBI :: DBRC для такого использования. Не сомневаюсь, что у Java есть аналог. Во всяком случае, написать его несложно.
Вы пишете веб-приложение? Если это так, используйте JNDI для внешней настройки приложения. Обзор доступен здесь:
JNDI предоставляет приложению единый способ поиска и доступа к удаленным сервисам в сети. Удаленная служба может быть любой корпоративной службой, включая службу обмена сообщениями или службу для конкретного приложения, но, конечно, приложение JDBC в основном заинтересовано в службе базы данных. После создания и регистрации объекта DataSource в службе имен JNDI приложение может использовать JNDI API для доступа к этому объекту DataSource, который затем можно использовать для подключения к источнику данных, который он представляет.
Независимо от того, что вы делаете, конфиденциальная информация будет где-то храниться в каком-то файле. Ваша цель - сделать это как можно сложнее. Насколько это удастся, зависит от вашего проекта, потребностей и толщины кошелька вашей компании.
Лучший способ - нигде не хранить пароли. Это достигается за счет использования хеш-функций для генерации и хранения хэшей паролей:
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
Алгоритмы хеширования - это односторонние функции. Они превращают любой объем данных в «отпечаток пальца» фиксированной длины, который нельзя отменить. У них также есть свойство, что если ввод изменится даже на крошечный бит, результирующий хэш будет совершенно другим (см. Пример выше). Это отлично подходит для защиты паролей, потому что мы хотим хранить пароли в форме, которая защищает их, даже если сам файл паролей скомпрометирован, но в то же время нам нужно иметь возможность проверить правильность пароля пользователя.
Несвязанное примечание: в старые времена Интернета, когда вы нажимали ссылку «забыл мой пароль», веб-сайты отправляли вам ваш пароль в виде обычного текста по электронной почте. Вероятно, они хранили их где-то в базе данных. Когда хакеры получали доступ к своей базе данных, они получали доступ ко всем паролям. Поскольку многие пользователи использовали один и тот же пароль на нескольких веб-сайтах, это было огромной проблемой для безопасности. К счастью, в настоящее время это не обычная практика.
Теперь возникает вопрос: как лучше всего хранить пароли? Я бы счел это решение (stormpath службы аутентификации и управления пользователями) чертовски чертовски идеальный:
- Ваш пользователь вводит учетные данные, и это проверяется хешем пароля.
- Генерируются и сохраняются хэши паролей, а не пароли
- Хеширование выполняется несколько раз
- Хэши генерируются с использованием случайно сгенерированной соли
- Хэши зашифрованы закрытым ключом
- Закрытый ключ хранится в другом месте, чем хеши
- Закрытые ключи обновляются по времени.
- Зашифрованные хэши делятся на блоки.
- Эти фрагменты хранятся в физически разных местах.
Очевидно, что вы не гугл или банк, так что это излишнее решение для вас. Но тогда возникает вопрос: какой уровень безопасности требует ваш проект, сколько времени и денег у вас есть?
Для многих приложений, хотя это и не рекомендуется, хранение жестко закодированного пароля в коде может быть достаточно хорошим решением. Однако, легко добавив пару дополнительных шагов безопасности из приведенного выше списка, вы можете сделать свое приложение намного более безопасным.
Например, предположим, что шаг 1 не является приемлемым решением для вашего проекта. Вы не хотите, чтобы пользователи вводили пароль каждый раз, или вы даже не хотите / не хотите, чтобы пользователи знали пароль. Тем не менее, у вас где-то есть конфиденциальная информация, и вы хотите ее защитить. У вас простое приложение, нет сервера для хранения ваших файлов или это слишком хлопотно для вашего проекта. Ваше приложение работает в средах, где невозможно безопасно хранить файлы. Это один из наихудших случаев, но все же с некоторыми дополнительными мерами безопасности вы можете получить гораздо более безопасное решение. Например, вы можете сохранить конфиденциальную информацию в файле и зашифровать его. Вы можете жестко запрограммировать закрытый ключ шифрования в коде. Вы можете запутать код, чтобы кому-то было труднее его взломать. Для этой цели существует множество библиотек, см. эту ссылку. (Я хочу еще раз предупредить вас, что это не на 100% безопасно. Умный хакер с нужными знаниями и инструментами может это взломать. Но, исходя из ваших требований и потребностей, это может быть достаточно хорошим решением для вас).
Этот вопрос показывает, как хранить пароли и другие данные в зашифрованном файле: Java 256- битовое шифрование на основе пароля AES
MD5 - это алгоритм хеширования, а не алгоритм шифрования, короче говоря, вы не можете вернуться к хешированию, вы можете только сравнивать. В идеале его следует использовать при хранении информации аутентификации пользователя, а не имени пользователя и пароля db. Имя пользователя db и pwd должны быть зашифрованы и сохранены в файле конфигурации, по крайней мере.