Rfc2898DeriveBytes + PBKDF2 + SecureString можно ли использовать безопасную строку вместо строки?

У меня есть функция 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);

person NoobTom    schedule 16.03.2012    source источник
comment
... ах, да, я не знаю, почему такая соль: D   -  person NoobTom    schedule 16.03.2012
comment
то, что я делаю прямо сейчас, это то, что я использую securePwd.ToString, проблема в том, что он делает весь процесс использования secureString бесполезным: D   -  person NoobTom    schedule 16.03.2012
comment
Это то, что началось с //read the password from terminal   -  person Hans Passant    schedule 16.03.2012
comment
ToString() возвращает содержимое SecureString? Вы уверены? Это не то, о чем говорится в документации.   -  person erickson    schedule 17.03.2012


Ответы (3)


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

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

person Maarten Bodewes    schedule 17.03.2012

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

WPF позволяет обрабатывать пароли как SecureString объекты с _ 4_ контроль. Казалось, что дополнительная безопасность, которую предлагает этот элемент управления, была потеряна из-за того, что мы не могли передать 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 в оператор using, он обеспечивает его детерминированное удаление. Это важно, поскольку у него есть внутренний HMACSHA1 объект, который является KeyedHashAlgorithm, и для его обнуления при вызове Dispose требуется копия ключа (пароля), которым он обладает. Для получения полной информации см. Справочный источник.
  • Как только мы закончим с BSTR, мы обнуляем его и освобождаем через ZeroFreeBSTR.
  • Наконец, мы обнуляем (очищаем) нашу копию пароля.
  • Обновление: добавлено закрепление byte[]. Как обсуждалось в комментариях к этому ответу, если byte[] не закреплен, то сборщик мусора может переместить объект во время сбора и у нас не останется возможности обнулить исходную копию.

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

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

person Derek W    schedule 08.05.2017
comment
Это именно то, что я надеялся найти, и я считаю, что это настолько безопасно, насколько это возможно. Я считаю, что в моем небольшом исследовании вы рассмотрели все вопросы. Поскольку реализация Rfc2898DeriveBytes не закрепляет byte[], память может быть доступна в течение более длительного времени, если сборщик мусора перемещает ее, обнажая вектор атаки. Хотя мы знаем, что если у злоумышленника есть доступ к оперативной памяти, это меньшая из наших проблем. Ваше решение ограничивает появление незакрепленных конфиденциальных данных поведением API. - person Nicholas Miller; 28.08.2018

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

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

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

person erickson    schedule 17.03.2012
comment
спасибо, но мне нужно зашифровать файл на машине с паролем и расшифровать его на другой машине с тем же паролем. Поэтому я думаю, что соль должна быть такой же, иначе вторая машина не сможет ее расшифровать. Я прав? - person NoobTom; 19.03.2012
comment
Я выбрал другой ответ, потому что он более формальный, но я очень ценю ваше хитрое предложение - person NoobTom; 19.03.2012
comment
@NoobTom Да, конечно, соль должна быть одинаковой на обеих машинах. При использовании хеширования паролей для целей аутентификации хэш, соль и количество итераций должны быть сохранены, чтобы вы могли повторно вычислить хеш при представлении пароля, чтобы увидеть, совпадает ли новый хеш с сохраненным хешем. Никогда не следует использовать жестко запрограммированный хеш для аутентификации по паролю. - person erickson; 19.03.2012
comment
Примечание: API также должен ответственно управлять своей потенциальной копией ключа и обнулять ее. При просмотре справочного источника: до байт пароля массив составляет 64 байта или меньше, тогда объект HMACSHA1, внутренний по отношению к объекту Rfc2898DeriveBytes, имеет собственную клонированную копию массива байтов пароля. К счастью, HMACSHA1 - это KeyedHashAlgorithm, который обеспечивает обнуление своей копии ключа при вызове Dispose. - person Derek W; 08.05.2017
comment
Я бы также добавил, что также важно закрепить byte[]. Если byte[] не закреплен, тогда сборщик мусора может переместить его во время сбора и не оставит нам способа обнулить исходный массив. - person Derek W; 09.05.2017