Как безопасно хранить пароль без использования сторонних сервисов для хранения секретов?
Что проверяют
Различаете ли вы две задачи: хранить пароли пользователей и хранить секрет самого приложения, например пароль к базе. Сильный кандидат сначала уточняет, о чём речь. Следом обычно спрашивают, чем плох SHA-256 с солью.
Как отвечать
Пароли пользователей не шифруют, хранят только результат медленной функции хеширования с солью:
- алгоритм: Argon2id, bcrypt или PBKDF2. В .NET без сторонних пакетов доступен PBKDF2:
Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, 32). OWASP для PBKDF2-HMAC-SHA256 рекомендует от 600 000 итераций, для Argon2id и bcrypt нужны пакеты NuGet; - соль: уникальная для каждого пароля, от 16 байт, из
RandomNumberGenerator.GetBytes(16), лежит рядом с хешем; - формат: вместе с хешем храните алгоритм и число итераций, чтобы позже поднять их и перехешировать пароль при следующем входе;
- сравнение:
CryptographicOperations.FixedTimeEquals, а неSequenceEqual, чтобы не было утечки по времени.
В ASP.NET Core всё это уже есть в PasswordHasher<TUser> из Identity, включая PasswordVerificationResult.SuccessRehashNeeded. Своя реализация нужна редко.
Если речь о секрете приложения, хеш не подходит: пароль к базе нужен в открытом виде. Без Vault и Key Vault остаются переменные окружения или файл с правами только для учётной записи сервиса, шифрование через DPAPI (ProtectedData.Protect) на Windows или через ASP.NET Core Data Protection (IDataProtector). Ещё лучше обойтись без пароля, например через встроенную аутентификацию Windows к SQL Server.
Где спотыкаются
Шифруют пароли пользователей через AES или хешируют быстрым SHA-256. Шифр обратим, а быстрый хеш видеокарта перебирает миллиардами вариантов в секунду, соль от этого не спасает.
Собирают свою схему: один проход HMAC со случайным ключом вместо соли, соль из System.Random, сравнение через ==. По отдельности детали выглядят безобидно, вместе получается хеш, который подбирается со скоростью обычного SHA-256.
Кладут секрет приложения в appsettings.json в репозитории или считают dotnet user-secrets защищённым хранилищем. Это обычный JSON в профиле пользователя, инструмент только для разработки.