Как и где хранить секретные данные в проекте мобильного приложения?

Я пишу приложение на 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 шт):

Автор решения: Uranus

Хранить секретные данные на стороне клиента имеет смысл только в тех случаях, когда сам пользователь должен их вводить согласно предназначению приложения. Например, если пользователь вводит пароль, токен или 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, но я не готов его рекомендовать, так как у меня нет опыта с ним.

→ Ссылка