Является ли SecureString практичным в приложении C #?

224

Не стесняйтесь поправлять меня, если мои предположения здесь неверны, но позвольте мне объяснить, почему я спрашиваю.

Взято из MSDN, а SecureString:

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

Я понимаю, что имеет смысл хранить пароль или другую личную информацию в более SecureStringчем System.String, потому что вы можете контролировать, как и когда она фактически хранится в памяти, потому что System.String:

является одновременно неизменным и, если больше не нужен, не может быть программно запланирован для сбора мусора; то есть экземпляр только для чтения после его создания, и невозможно предсказать, когда экземпляр будет удален из памяти компьютера. Следовательно, если объект String содержит конфиденциальную информацию, такую ​​как пароль, номер кредитной карты или личные данные, существует риск, что эта информация может быть раскрыта после ее использования, поскольку ваше приложение не может удалить данные из памяти компьютера.

Однако, в случае приложения с графическим интерфейсом (например, ssh-клиента), SecureString он должен быть построен из System.String . Все текстовые элементы управления используют строку в качестве основного типа данных .

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

Теперь пришло время войти на сервер. Угадай, что? Вам нужно передать строку через соединение для аутентификации . Итак, давайте преобразуем наше SecureStringв System.String.... и теперь у нас есть строка в куче, и мы не можем заставить ее пройти сборку мусора (или записать 0 в ее буфер).

Моя точка : независимо от того , что вы делаете, где - то вдоль линии, что SecureStringэто собирается быть преобразованы в System.String, то есть это будет по крайней мере , существует на куче в какой - то момент (без каких - либо гарантий сбора мусора).

Моя точка зрения не в том, существуют ли способы обойти отправку строки в ssh-соединение или обойти, если элемент управления хранит строку (создайте пользовательский элемент управления). Для этого вопроса вы можете заменить «ssh connection» на «форму входа в систему», «форму регистрации», «форму оплаты», «форму« еда, которую вы будете кормить щенком, но не ваши дети »», и т.п.

  • Итак, в какой момент использование SecureStringфактически становится практичным?
  • Стоит ли когда-нибудь дополнительное время разработки, чтобы полностью искоренить использование System.Stringобъекта?
  • Неужели весь смысл в том, SecureStringчтобы просто сократить время нахождения System.Stringв куче (уменьшая риск перехода к физическому файлу подкачки)?
  • Если у злоумышленника уже есть средства для проверки кучи, то, скорее всего, у него (A) уже есть средства для чтения нажатий клавиш, или (B) уже физически есть машина ... Так что, если бы он воспользовался, SecureStringчтобы он не смог добраться до данные в любом случае?
  • Это просто «безопасность через неизвестность»?

Извините, если я задаю слишком толстые вопросы, любопытство только что одолело меня. Не стесняйтесь отвечать на любые или все мои вопросы (или скажите, что мои предположения совершенно неверны). :)

Стивен Джеффрис
источник
25
Имейте в виду, что SecureStringэто не совсем безопасная строка. Это просто способ уменьшить временное окно, в котором кто-то может проверить вашу память и успешно получить конфиденциальные данные. Это не пуленепробиваемое и не должно было быть. Но пункты, которые вы поднимаете, очень актуальны. Связанный: stackoverflow.com/questions/14449579/…
Теодорос Chatzigiannakis
1
@TheodorosChatzigiannakis, да, это почти то, что я понял. Я сегодня потратил весь день, ломая голову над попыткой найти безопасный способ хранения пароля для срока службы приложения, и это только заставило меня задуматься, стоит ли это того?
Стивен Джеффрис
1
Наверное, не стоит. Но опять же, вы можете защищаться от экстремального сценария. Крайне не в том смысле, что кто-то вряд ли получит такой доступ к вашему компьютеру, но в том смысле, что если кто-то получит такой доступ, компьютер считается (для всех намерений и целей) скомпрометированным, и я не Я не думаю, что есть какой-либо язык или любая техника, которую вы можете использовать, чтобы полностью защититься от этого.
Теодорос Чатзигианнакис
8
Он защищает от того, чтобы пароль был виден годами , даже после того, как все перестали думать, что это может быть проблемой. На удаленном жестком диске, хранящемся в файле подкачки. Очистка памяти так, чтобы шансы для этого были низкими, важна, этого нельзя сделать с System.String, поскольку она неизменна. Это требует инфраструктуры, к которой в наши дни имеют доступ лишь немногие программы, взаимодействующие с собственным кодом, поэтому методы Marshal.SecureStringXxx () становятся все более редкими. Или достойный способ подсказать пользователю об этом :)
Ганс Пассант
1
SecureString - это глубокая техника безопасности. В большинстве ситуаций компромисс между затратами на разработку и усилением безопасности не является благоприятным. Есть очень мало хороших вариантов использования для этого.
USR

Ответы:

237

Есть на самом деле очень практическое использование SecureString.

Вы знаете, сколько раз я видел такие сценарии? (ответ: много!)

  • Пароль появляется в файле журнала случайно.
  • Где-то отображается пароль - когда-то в графическом интерфейсе отображалась командная строка приложения, которое запускалось, а командная строка состояла из пароля. К сожалению .
  • Использование профилировщика памяти для профилирования программного обеспечения с вашим коллегой. Коллега видит ваш пароль в памяти. Звучит нереально? Не за что.
  • Я когда-то использовал RedGateпрограммное обеспечение, которое может захватывать «значение» локальных переменных в случае исключений, удивительно полезно. Хотя я могу себе представить, что он будет регистрировать «строковые пароли» случайно.
  • Аварийный дамп, содержащий строковый пароль.

Вы знаете, как избежать всех этих проблем? SecureString, Как правило, это гарантирует, что вы не делаете глупых ошибок как таковых. Как это избежать? Убедившись в том, что пароль зашифрован в неуправляемой памяти, а реальное значение доступно только тогда, когда вы на 90% уверены в том, что делаете.

В смысле SecureStringработает довольно легко:

1) Все зашифровано

2) Пользовательские звонки AppendChar

3) Расшифруйте все в НЕМЕДЛЕННОЙ ПАМЯТИ и добавьте персонажа

4) Зашифруйте все снова в НЕЗАМЕНИМОЙ ПАМЯТИ.

Что если у пользователя есть доступ к вашему компьютеру? Сможет ли вирус получить доступ ко всем SecureStrings? Да. Все, что вам нужно сделать, это подключиться, RtlEncryptMemoryкогда расшифровывается память, вы получите местоположение адреса незашифрованной памяти и прочитаете его. Вуаля! Фактически, вы можете создать вирус, который будет постоянно проверять использование SecureStringи регистрировать все действия с ним. Я не говорю, что это будет легкая задача, но это можно сделать. Как видите, «мощь» SecureStringполностью исчезает, когда в вашей системе есть пользователь / вирус.

У вас есть несколько пунктов в вашем посте. Конечно, если вы используете некоторые элементы управления пользовательского интерфейса, которые содержат «строковый пароль» внутри, использование фактического SecureStringне очень полезно. Хотя, тем не менее, он может защитить от некоторой глупости, которую я перечислил выше.

Также, как уже отмечали другие, WPF поддерживает PasswordBox, который используется SecureStringвнутри через свойство SecurePassword .

Суть в том, если у вас есть конфиденциальные данные (пароли, кредитные карты, ...), используйте SecureString. Это то, что C # Framework придерживается. Например, NetworkCredentialкласс хранит пароль как SecureString. Если вы посмотрите на это , вы увидите более 80 различных вариантов использования в .NET Framework SecureString.

Есть много случаев, когда вам нужно преобразовать SecureStringв строку, потому что некоторые API ожидают этого.

Обычная проблема:

  1. API является ОБЩИМ. Он не знает, что есть конфиденциальные данные.
  2. API знает, что имеет дело с конфиденциальными данными и использует «строку» - это просто плохой дизайн.

Вы подняли хороший вопрос: что происходит, когда SecureStringпреобразуется в string? Это может произойти только из-за первого пункта. Например, API не знает, что это конфиденциальные данные. Я лично не видел, чтобы это произошло. Получить строку из SecureString не так просто.

Это не просто по простой причине ; оно никогда не предназначалось для того, чтобы позволить пользователю преобразовать SecureString в строку, как вы заявили: GC сработает. Если вы видите, что делаете это, вам нужно отойти назад и спросить себя: зачем я это делаю, или мне действительно нужно это зачем?

Есть один интересный случай, который я видел. А именно, функция WinApi LogonUser принимает LPTSTR в качестве пароля, что означает необходимость вызова SecureStringToGlobalAllocUnicode. Это в основном дает вам незашифрованный пароль, который находится в неуправляемой памяти. Вам нужно избавиться от этого, как только вы закончите:

// Marshal the SecureString to unmanaged memory.
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
   //...snip...
}
finally 
{
   // Zero-out and free the unmanaged string reference.
   Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}

Вы всегда можете расширить SecureStringкласс с помощью метода расширения, например ToEncryptedString(__SERVER__PUBLIC_KEY), который дает вам stringэкземпляр, SecureStringкоторый зашифрован с использованием открытого ключа сервера. Только сервер может затем расшифровать его. Проблема решена: сборщик мусора никогда не увидит «оригинальную» строку, так как вы никогда не выставите ее в управляемой памяти. Это именно то, что делается в PSRemotingCryptoHelper( EncryptSecureStringCore(SecureString secureString)).

И как что-то очень похожее : Mono SecureString вообще не шифрует . Реализация была закомментирована, потому что .. жду этого .. "Это как-то вызывает разрыв теста nunit" , что подводит к моему последнему замечанию:

SecureStringне поддерживается везде Если платформа / архитектура не поддерживает SecureString, вы получите исключение. Там есть список платформ, которые поддерживаются в документации.

Эрти-Крис Элмаа
источник
Я думаю, SecureStringчто было бы намного более практичным, если бы существовал механизм доступа к отдельным персонажам. Эффективность такого механизма может быть повышена за счет имеющего тип структуры SecureStringTempBufferбез каких - либо общественных членов, которые могут быть переданы с помощью refк SecureString«чтения один символ» метод [если шифрование / дешифрование обрабатывается в 8-символьных кусков, такая структура может содержать данные для 8 символов и расположение первого из этих символов в пределах SecureString].
суперкат
2
Я отмечаю это в каждой проверке исходного кода, которую я выполняю. Также следует повторить, что если вы используете SecureStringего, его необходимо использовать на протяжении всего стека.
Кейси
1
Я реализовал расширение .ToEncryptedString (), но с использованием сертификата. Не могли бы вы взглянуть и дать мне знать, если я все делаю неправильно? I'm надеясь его безопасное engouhg gist.github.com/sturlath/99cfbfff26e0ebd12ea73316d0843330
Sturla
@Sturla - Что EngineSettingsв вашем методе расширения?
InteXX
15

В ваших предположениях мало вопросов.

Прежде всего, класс SecureString не имеет конструктора String. Для его создания вы выделяете объект, а затем добавляете символы.

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

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

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

В .NET Framework у вас есть несколько классов, которые могут использовать SecureString

  • Элемент управления PasswordBox в WPF хранит пароль внутри SecureString.
  • Свойство Password для System.Diagnostics.ProcessInfo является SecureString.
  • Конструктор для X509Certificate2 принимает SecureString для пароля.

(Больше)

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

Все это, с примерами, хорошо описано в документации MSStnt SecureString.

Дамиан Лещинский - Ваш
источник
10
Я не совсем понимаю от третьего до последнего параграфа вашего ответа. Как бы вы передали SecureStringпо сети без сериализации в string? Я думаю, что точка зрения ОП заключается в том, что наступает момент, когда вы действительно хотите использовать значение, хранящееся в безопасном месте, а SecureStringэто означает, что вы должны вывести его оттуда, и это больше не безопасно. Во всей библиотеке классов Framework практически нет методов, которые принимают SecureStringвходные данные напрямую (насколько я вижу), так какой смысл держать значение внутри a SecureStringв первую очередь?
stakx - больше не участвует
@ DamianLeszczyński-Vash, я знаю, что SecureString не имеет строкового конструктора, но я хочу сказать, что вы либо (A) создаете SecureString и добавляете каждый символ из строки к нему, либо (B) в какой-то момент необходимо использовать значение хранится в SecureString в виде строки, чтобы передать его некоторому API. Тем не менее, вы поднимаете некоторые хорошие моменты, и я вижу, как в некоторых очень конкретных случаях это может быть полезно.
Стивен Джеффрис
1
@stakx Вы бы отправили его по сети, получив char[]и отправив каждый charиз них через сокет, не создавая в процессе никаких объектов для сбора мусора.
user253751
Char [] является коллекционным и изменчивым, поэтому его можно перезаписать.
Питер Ричи
Конечно, также легко забыть перезаписать этот массив. В любом случае любой API, которому вы передаете символы, также может сделать копию.
cHao
10

SecureString полезна, если:

  • Вы строите его символ за символом (например, из ввода с консоли) или получаете его из неуправляемого API

  • Вы используете его, передавая его неуправляемому API (SecureStringToBSTR).

Если вы когда-нибудь преобразуете его в управляемую строку, вы победили ее назначение.

ОБНОВЛЕНИЕ в ответ на комментарий

... или BSTR, как вы упоминаете, который не кажется более безопасным

После преобразования в BSTR неуправляемый компонент, который использует BSTR, может обнулить память. Неуправляемая память более безопасна в том смысле, что ее можно сбросить таким образом.

Однако в .NET Framework очень мало API-интерфейсов, поддерживающих SecureString, поэтому вы правы, говоря, что сегодня это очень ограниченная ценность.

Основной вариант использования, который я вижу, - это клиентское приложение, которое требует от пользователя ввода высокочувствительного кода или пароля. Пользовательский ввод может использоваться символ за символом для создания SecureString, затем он может быть передан в неуправляемый API, который обнуляет BSTR, который он получает после его использования. Любой последующий дамп памяти не будет содержать чувствительную строку.

В серверном приложении трудно понять, где это будет полезно.

ОБНОВЛЕНИЕ 2

Одним из примеров .NET API, который принимает SecureString, является этот конструктор для класса X509Certificate . Если вы говорите с ILSpy или аналогичным, вы увидите, что SecureString внутренне преобразуется в неуправляемый буфер ( Marshal.SecureStringToGlobalAllocUnicode), который затем обнуляется, когда завершается с ( Marshal.ZeroFreeGlobalAllocUnicode).

Джо
источник
1
+1, но разве ты не всегда будешь "побеждать свою цель"? Учитывая, что почти ни один метод в библиотеке классов Framework не принимает SecureStringвходные данные, что бы их использовать? Вы всегда должны будете вернуться обратно stringрано или поздно (или, BSTRкак вы упоминаете, это не кажется более безопасным). Так зачем SecureStringвообще?
stakx - больше не участвует
5

Microsoft не рекомендует использовать SecureStringдля более новых кодов.

Из документации класса SecureString :

Важный

Мы не рекомендуем использовать этот SecureStringкласс для новых разработок. Для получения дополнительной информации см. SecureStringНе должны использоваться

Который рекомендует:

Не используйте SecureStringдля нового кода. При переносе кода на .NET Core учтите, что содержимое массива не зашифровано в памяти.

Общий подход к работе с учетными данными - избегать их и вместо этого полагаться на другие средства аутентификации, такие как сертификаты или проверка подлинности Windows. на GitHub.

SᴇM
источник
4

Как вы правильно определили, SecureStringпредлагает одно конкретное преимущество перед string: детерминированное стирание. Есть две проблемы с этим фактом:

  1. Как уже упоминали другие, и как вы сами заметили, этого недостаточно. Вы должны убедиться, что каждый шаг процесса (включая извлечение ввода, построение строки, использование, удаление, транспортировку и т. Д.) Происходит без ущерба для цели использования SecureString. Это означает, что вы должны быть осторожны, чтобы никогда не создавать управляемый GC неизменяемый stringили какой-либо другой буфер, в котором будет храниться конфиденциальная информация (или вам также придется отслеживать это ). На практике это не всегда легко достичь, потому что многие API предлагают только способ работы string, а не SecureString. И даже если вам удастся все правильно ...
  2. SecureStringзащищает от очень специфических видов атак (и для некоторых из них это даже не так надежно). Например, SecureStringпозволяет ли вам сократить временное окно, в котором злоумышленник может сбросить память вашего процесса и успешно извлечь конфиденциальную информацию (опять же, как вы правильно указали), но надеясь, что окно слишком мало для злоумышленника сделать снимок вашей памяти вообще не считается безопасностью.

Итак, когда вы должны его использовать? Только когда вы работаете с чем-то, что может позволить вам работать SecureStringдля всех ваших потребностей, и даже тогда вы должны помнить, что это безопасно только в определенных обстоятельствах.

Теодорос Чатзигианнакис
источник
2
Вы предполагаете, что злоумышленник может сделать снимок памяти процесса в любой момент времени. Это не обязательно так. Рассмотрим аварийную свалку. Если сбой произошел после того, как приложение было выполнено с конфиденциальными данными, аварийный дамп не должен содержать конфиденциальные данные.
4

Ниже текст скопирован из статического анализатора кода HP Fortify

Аннотация: Метод PassString () в PassGenerator.cs хранит конфиденциальные данные небезопасным способом (т. Е. В виде строки), что позволяет извлекать данные путем проверки кучи.

Объяснение: Конфиденциальные данные (такие как пароли, номера социального страхования, номера кредитных карт и т. Д.), Хранящиеся в памяти, могут быть утечки, если они хранятся в управляемом объекте String. Строковые объекты не закреплены, поэтому сборщик мусора может переместить эти объекты по своему желанию и оставить несколько копий в памяти. Эти объекты по умолчанию не зашифрованы, поэтому любой, кто сможет прочитать память процесса, сможет увидеть его содержимое. Кроме того, если память процесса будет выгружена на диск, незашифрованное содержимое строки будет записано в файл подкачки. Наконец, поскольку объекты String являются неизменяемыми, удаление значения String из памяти может быть выполнено только сборщиком мусора CLR. Сборщик мусора не требуется для запуска, если CLR не хватает памяти, поэтому нет никакой гарантии, когда будет происходить сборка мусора.

Рекомендации: вместо хранения конфиденциальных данных в таких объектах, как Strings, храните их в объекте SecureString. Каждый объект всегда хранит свое содержимое в зашифрованном формате в памяти.

vikrantx
источник
3

Я хотел бы обратиться к этому вопросу:

Если у злоумышленника уже есть средства для проверки кучи, то, скорее всего, у него (A) уже есть средства для считывания нажатий клавиш, или (B) уже физически есть машина ... Так что, если бы они воспользовались, SecureStringчтобы они не могли добраться до данные в любом случае?

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

HeartBleed утечка памяти

Возьмите Heartbleed, например. Специально сконструированные запросы могут привести к тому, что код предоставит злоумышленнику случайные части памяти процесса. Злоумышленник может извлечь SSL-сертификаты из памяти, но единственное, что ему нужно, это просто использовать некорректный запрос.

В мире управляемого кода переполнения буфера становятся проблемой гораздо реже. А в случае WinForms данные уже хранятся небезопасным образом, и вы ничего не можете с этим поделать. Это делает защиту SecureStringпрактически бесполезной.

Тем не менее, GUI может быть запрограммирован для использования SecureString, и в этом случае уменьшение окна доступности пароля в памяти может стоить того. Например, PasswordBox.SecurePassword из WPF имеет тип SecureString.

Асария
источник
Хм, определенно интересно узнать. Честно говоря, я не особо беспокоюсь о типах атак, необходимых для того, чтобы даже вывести значение System.String из моей памяти, я просто спрашиваю из чистого любопытства. Тем не менее, спасибо за информацию! :)
Стивен Джеффрис
2

Некоторое время назад мне пришлось создать интерфейс ac # против шлюза оплаты через кредитную карту java, и один из них нуждался в совместимом шифровании ключей защищенной связи. Поскольку реализация Java была довольно специфичной, и мне пришлось работать с защищенными данными определенным образом.

Я обнаружил, что этот дизайн довольно прост в использовании и проще, чем работа с SecureString ... для тех, кто любит использовать ... не стесняйтесь, никаких юридических ограничений :-). Обратите внимание, что эти классы являются внутренними, вам может потребоваться сделать их общедоступными.

namespace Cardinity.Infrastructure
{
    using System.Security.Cryptography;
    using System;
    enum EncryptionMethods
    {
        None=0,
        HMACSHA1,
        HMACSHA256,
        HMACSHA384,
        HMACSHA512,
        HMACMD5
    }


internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }
}


    internal class SecretKeySpec:Protected,IDisposable
    {
        readonly EncryptionMethods _method;

        private byte[] _secretKey;
        public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
        {
            _secretKey = Protect(secretKey);
            _method = encryptionMethod;
        }

        public EncryptionMethods Method => _method;
        public byte[] SecretKey => Unprotect( _secretKey);

        public void Dispose()
        {
            if (_secretKey == null)
                return;
            //overwrite array memory
            for (int i = 0; i < _secretKey.Length; i++)
            {
                _secretKey[i] = 0;
            }

            //set-null
            _secretKey = null;
        }
        ~SecretKeySpec()
        {
            Dispose();
        }
    }

    internal class Mac : Protected,IDisposable
    {
        byte[] rawHmac;
        HMAC mac;
        public Mac(SecretKeySpec key, string data)
        {

            switch (key.Method)
            {
                case EncryptionMethods.HMACMD5:
                    mac = new HMACMD5(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA512:
                    mac = new HMACSHA512(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA384:
                    mac = new HMACSHA384(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA256:
                    mac = new HMACSHA256(key.SecretKey);

                break;
                case EncryptionMethods.HMACSHA1:
                    mac = new HMACSHA1(key.SecretKey);
                    break;

                default:                    
                    throw new NotSupportedException("not supported HMAC");
            }
            rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

        }

        public string AsBase64()
        {
            return System.Convert.ToBase64String(Unprotect(rawHmac));
        }

        public void Dispose()
        {
            if (rawHmac != null)
            {
                //overwrite memory address
                for (int i = 0; i < rawHmac.Length; i++)
                {
                    rawHmac[i] = 0;
                }

                //release memory now
                rawHmac = null;

            }
            mac?.Dispose();
            mac = null;

        }
        ~Mac()
        {
            Dispose();
        }
    }
}
Уолтер Веховен
источник