Rfc2898DeriveBytes + PBKDF2 + SecureString возможно ли использовать безопасную строку вместо строки? - PullRequest
5 голосов
/ 16 марта 2012

У меня есть функция GetPassword, которая возвращает тип SecureString.

Когда я передаю эту безопасную строку в Rfc2898DeriveBytes для генерации ключа, Visual Studio показывает ошибку. Мои ограниченные знания говорят мне, что это потому, что Rfc2898DeriveBytes принимает только строку, а не безопасную строку. Есть ли обходной путь к этому?

//read the password from terminal
Console.Write("Insert password");
securePwd = myCryptography.GetPassword();

//dont know why the salt is initialized like this
byte[] salt = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
 try
 {   //PBKDF2 standard 
     Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(securePwd, salt, iterationsPwd);

Ответы [ 3 ]

6 голосов
/ 17 марта 2012

Очевидно, что вы можете нарушить защиту, предоставляемую SecureString, а выставить свое внутреннее состояние с помощью функции Marshal.SecureStringToBSTR().

Вместо того, чтобы создавать String из результата, скопируйте содержимое в Byte[] для передачи в Rfc2898DeriveBytes. Создание String не позволит вам уничтожить информацию о пароле, позволив ему бесконечно зависать в куче или попадать на диск, что, в свою очередь, увеличивает вероятность того, что злоумышленник сможет его найти. Вместо этого вы должны уничтожить пароль, как только вы закончили его использовать, заполнив массив нулями. По той же причине вам также следует присвоить ноль каждому элементу BSTR, поскольку вы копируете его в Byte[].

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

4 голосов
/ 09 мая 2017

Мне показалось интересным, что класс Rfc2898DeriveBytes не поддерживает перегрузку SecureString для передачи пароля, используемого при получении ключа.

WPF позволяет обрабатывать пароли как SecureString объекты с элементом управления PasswordBox. Это казалось такой пустой тратой, что дополнительная безопасность, которую предлагает этот элемент управления, была потеряна из-за того, что мы не могли передать конструктору SecureString. Тем не менее, erickson продемонстрировал превосходную возможность использования byte[] вместо перегрузки string, поскольку относительно легче правильно управлять содержимым byte[] в памяти, чем string.

Используя предложение Эриксона в качестве вдохновения, я придумал следующую обертку, которая должна позволять использовать значение пароля, защищенного SecureString, с минимальным раскрытием значения открытого текста в памяти.

private byte[] DeriveKey(SecureString password, byte[] salt, int iterations, int keyByteLength)
{
    IntPtr ptr = Marshal.SecureStringToBSTR(password);
    byte[] passwordByteArray = null;
    try
    {
        int length = Marshal.ReadInt32(ptr, -4);
        passwordByteArray = new byte[length];
        GCHandle handle = GCHandle.Alloc(passwordByteArray, GCHandleType.Pinned);
        try
        {
            for (int i = 0; i < length; i++)
            {
                passwordByteArray[i] = Marshal.ReadByte(ptr, i);
            }

            using (var rfc2898 = new Rfc2898DeriveBytes(passwordByteArray, salt, iterations))
            {
                return rfc2898.GetBytes(keyByteLength);
            }
        }
        finally
        {
            Array.Clear(passwordByteArray, 0, passwordByteArray.Length);  
            handle.Free();
        }
    }
    finally
    {
        Marshal.ZeroFreeBSTR(ptr);
    }
}

Этот подход использует тот факт, что BSTR является указателем, указывающим на первый символ строки данных с префиксом длины в четыре байта.

Важные моменты:

  • Обернув Rfc2898DeriveBytes в оператор использования, он гарантирует, что он будет расположен детерминистическим образом. Это важно, поскольку он имеет внутренний HMACSHA1 объект, который является KeyedHashAlgorithm и должен иметь копию ключа (пароля), который он должен обнулять при вызове Dispose . См. Справочный источник для получения полной информации.
  • Как только мы закончим с BSTR, мы обнуляем его и освобождаем через ZeroFreeBSTR .
  • Наконец, мы обнуляем (удаляем) нашу копию пароля.
  • Обновление: Добавлено закрепление byte[]. Как обсуждалось в комментариях к этому ответу , если byte[] не закреплен, то сборщик мусора может переместить объект во время сбора, и у нас не будет возможности обнулить оригинальную копию.

Это должно хранить открытый текст в памяти в течение кратчайшего периода времени и не ослаблять преимущества использования SecureString слишком много. Хотя, если у злоумышленника есть доступ к оперативной памяти, у вас, вероятно, большие проблемы. Другое дело, что мы можем управлять только своими собственными копиями пароля, а API, который мы используем, может очень плохо управлять (не обнулять / очищать) их копиями. Насколько мне известно, это не относится к Rfc2898DeriveBytes, хотя их копия ключа byte[] (пароль) не закреплена, и поэтому следы массива могут зависать, если он был перемещен в кучу до обнуления из. Сообщение здесь в том, что код может выглядеть безопасным, но проблемы могут лежать под ним.

Если кто-нибудь обнаружит какие-либо серьезные дыры в этой реализации, пожалуйста, дайте мне знать.

4 голосов
/ 17 марта 2012

После некоторого исследования и просмотра предыдущих ответов о стекопереработке с упоминанием SecureString этот ответ почти наверняка будет «Нет». Только создатели API могут принимать SecureString и обрабатывать его внутренне правильно. И они могут сделать это только с помощью платформы.

Если бы вы, как пользователь, могли получить простой текст String, вы бы свели на нет большинство преимуществ использования SecureString. Это даже было бы немного опасно, так как вы создали бы защищенный код, который вообще не был бы безопасным.

...