Мне показалось интересным, что класс 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[]
(пароль) не закреплена, и поэтому следы массива могут зависать, если он был перемещен в кучу до обнуления из. Сообщение здесь в том, что код может выглядеть безопасным, но проблемы могут лежать под ним.
Если кто-нибудь обнаружит какие-либо серьезные дыры в этой реализации, пожалуйста, дайте мне знать.