Мне показалось интересным, что 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
//read the password from terminal
- person Hans Passant   schedule 16.03.2012ToString()
возвращает содержимоеSecureString
? Вы уверены? Это не то, о чем говорится в документации. - person erickson   schedule 17.03.2012