воскресенье, 13 апреля 2008 г.

Хранение паролей в .config-файлах

Проблема, о которой я слышу слишком часто - защищенное хранение паролей в .config-файлах.

Стандартное решение для неё - System.Configuration.SectionInformation.ProtectSection() шифрует всю указанную Config-секцию, что не всегда удобно. Да и писать свои кастомные секции хочется не всегда.

John Galloway написал отличную статью о хранении паролей и прочей закрытой информции в .config-файлах приложения. В ней предлагается напрямую использовать DPAPI - технологию, скрытую за вызовами ProtectSection (если используется DpapiProtectedConfigurationProvider).

Код, который получается в результате, мне кажется более чистым и понятным, чем при других подходах.

DPAPI - Data Protection API, встроенный в Windows набор вызовов, позволяющий шифровать хранимую информацию 320-битным ключом, уникальным для пользователя. В .net framework эти вызовы выставлены в классе System.Security.Cryptography.ProtectedData, который мы и будем использовать.

Класс, ответственный за шифрование конфигурационных строк:

/// <summary>
    /// Служит для защиты пользовательских секретов, хранимых в .config-файлах 
    /// и других уязвимых источниках 
    /// Для компиляции понадобится подкючить System.Security
    /// </summary>
    class SecureConfigStringProvider
    {
 
        static byte[] entropy = System.Text.Encoding.Unicode.GetBytes("Salt Is Not A Password");
 
        /// <summary>
        /// Шифрует строку уникальным для пользователя ключом        
        /// </summary>        
        /// <returns>Зашифрованная строка в base64</returns>
        public static string EncryptString(string input)
        {
            byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
                System.Text.Encoding.Unicode.GetBytes(input),
                entropy,
                System.Security.Cryptography.DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedData);
        }
 
        /// <summary>
        /// Расшифрует строку, зашифрованную EncryptString
        /// </summary>
        /// <param name="encryptedData">Зашифрованная строка в base64</param>
        /// <returns>Расшифровання строка</returns>
        public static string DecryptString(string encryptedData)
        {
            try
            {
                byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
                    Convert.FromBase64String(encryptedData),
                    entropy,
                    System.Security.Cryptography.DataProtectionScope.CurrentUser);
                return System.Text.Encoding.Unicode.GetString(decryptedData);
            }
            catch
            {
                return "";
            }                       
        }
    }


Его использование для хранения пароля в .config-файле:

Сохраняем пароль:
AppSettings.Password = EncryptString(PasswordTextBox.Password);


После чего он в файле выглядит примерно вот так:

<setting name="Password" serializeAs="String">

<value>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAV......</value>

</setting>



Читаем пароль из файла:
string password = DecryptString(AppSettings.Password);


Этот подход хорошо работает как для windows-приложений, так и для asp.net предложений. Одно неудобство, привязка к одному и тому же пользователю, вероятно помешает его использовать в asp.net-приложении на ферме серверов.

P.S. в оригинальной статье используется SecureString для "безопасной" работы с паролями, но безопасности такое использование не добавляет, а только раздувает код. Поэтому я привел свой вариант вызвовов EncryptString/DecryptString.

Комментариев нет: