Как и где хранить секретные данные в проекте мобильного приложения?
Я пишу приложение на maui, в котором я работаю с базой данной. Для связи с БД мне нужна stringConnection, в которой будет секретная информация(пароль, логин, порт и т.д). И такую секретную информацию, как я понял, нельзя просто так хранить в кодовом файле.
Например, у меня сейчас вот так.
public static class MySqlDatabaseManager
{
private static string _server = "someServer.com";
private static string _port = "1234";
private static string _user = "user1";
private static string _password = "password123";
private static string _database = "database1";
private static string _stringConnection = $"server={_server};port={_port};user={_user};password={_password};database={_database}";
public static User Authorization(string login, string password)
{
...
}
public static bool TestConnection()
{
...
}
}
Я храню всю информацию открыто в самом классе.
Вопрос - как безопасно хранить такие данные ?
Я пытался пользоваться secrets.json. Создал класс для получения секретов.
public class UserSecretsManager
{
private static IConfigurationRoot _configuration = new ConfigurationBuilder()
.AddUserSecrets<UserSecretsManager>()
.Build();
public static string StringConnection => _configuration["Database:ConnectionString"];
}
Но это все работает только на моей устройстве, где я пишу код. Если же запустить приложение, например, на эмуляторе android, все перестает работать, и StringConnection возвращает пустую строку.
Как я узнал после - UserSecrets предназначены для хранения локальных секретов разработчика во время разработки, и они не переносятся вместе с приложением на другие устройства.
Подскажите пожалуйста, как и где мне безопасно хранить такие данные ?
Ответы (1 шт):
Хранить секретные данные на стороне клиента имеет смысл только в тех случаях, когда сам пользователь должен их вводить согласно предназначению приложения. Например, если пользователь вводит пароль, токен или API-ключ, который требуется сохранить для дальнейшего использования, то для мобильных устройств рекомендуется использовать Secure Storage. Он использует Keychain (iOS/macOS) и Keystore (Android), обеспечивая надежную защиту данных.
Пример добавления секрета в безопасное хранилище:
await SecureStorage.SetAsync("password", password);
Пример получения секрета:
string password = await SecureStorage.GetAsync("password");
Однако, если строка подключения к базе данных должна быть одинаковой для всех пользователей приложения, хранить ее на клиенте категорически нельзя. Даже если ее зашифровать, пользователь так или иначе сможет получить к ней доступ.
Дело в том, что приложение должно где-то хранить ключ, чтобы расшифровать строку. Если он хранится в коде, то его можно извлечь с помощью реверс-инжиниринга.
Даже если строка подключения зашифрована в коде, она будет расшифрована в оперативной памяти во время работы. Существует множество инструментов (например Frida, Xposed), позволяющих читать память приложения в реальном времени.
Если приложение обращается к базе данных напрямую, атакующий может перехватить запросы и извлечь логин/пароль из пакетов. Даже при использовании TLS можно выполнить атаку типа MITM (Man-in-the-Middle) с подменным сертификатом.
Безопасный подход
Вместо передачи клиенту строки подключения, рекомендуется использовать промежуточный сервер (API-шлюз), который:
- поддерживает авторизацию по стандартному протоколу (например, OAuth 2.0, OpenID Connect, JWT).
- принимает запросы от клиента и делегирует их в базу данных.
- ограничивает доступ к БД только для авторизованных клиентов.
Если вы хотите реализовать API-шлюз на .NET, я бы порекомендовал ASP.NET Core Web API как наиболее гибкий вариант. Но нужно будет реализовать всю логику работы с базой данных самостоятельно.
Пример кода:
[Authorize] // Требуем авторизацию
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly DatabaseService _dbService;
public DataController(DatabaseService dbService)
{
_dbService = dbService;
}
[HttpGet("users")]
public async Task<IActionResult> GetUsers()
{
var users = await _dbService.GetUsersAsync();
return Ok(users);
}
}
Для авторизации могу порекомендовать IdentityServer4. Правда он устарел в том плане, что с версии 5.0 он стал платным и больше не разрабатывается сообществом. Но при этом он остается мощным решением для OAuth 2.0 и OpenID Connect. Я считаю что для пет проекта или стартапа он все еще годен, и если что, мигрировать на Duende IdentityServer (IdentityServer5) относительно просто. Это фактически IdentityServer4 (разработчики те же), но с поддержкой и обновлениями.
Есть еще бесплатная альтернатива IdentityServer - OpenIddict, но я не готов его рекомендовать, так как у меня нет опыта с ним.