Коли мені знадобиться SecureString у .NET?


179

Я намагаюся чітко визначити мету SecureString .NET. Від MSDN:

Екземпляр класу System.String є одночасно незмінним і, коли це більше не потрібно, не може бути запрограмований програмно для збирання сміття; тобто екземпляр читається лише після його створення, і неможливо передбачити, коли екземпляр буде видалений з пам'яті комп'ютера. Отже, якщо об’єкт String містить конфіденційну інформацію, таку як пароль, номер кредитної картки або особисті дані, існує ризик, що інформація може бути розкрита після її використання, оскільки ваша програма не може видалити дані з пам'яті комп'ютера.

Об'єкт SecureString схожий на об'єкт String тим, що має текстове значення. Однак значення об’єкта SecureString автоматично шифрується, може змінюватися, поки ваша програма не позначає це лише для читання, і може бути видалена з пам'яті комп'ютера або вашим додатком, або збирачем сміття .NET Framework.

Значення екземпляра SecureString автоматично шифрується при ініціалізації екземпляра або при зміні значення. Ваша програма може зробити примірник незмінним і запобігти подальшій модифікації, посилаючись на метод MakeReadOnly.

Чи є автоматичне шифрування великою окупністю?

І чому я не можу просто сказати:

SecureString password = new SecureString("password");

замість

SecureString pass = new SecureString();
foreach (char c in "password".ToCharArray())
    pass.AppendChar(c);

Який аспект SecureString мені не вистачає?


3
Через 11 років і MS більше не рекомендує SecureStringнових розробок: github.com/dotnet/platform-compat/blob/master/docs/DE0001.md
Метт Томас

Відповіді:


4

Я б перестав використовувати SecureString. Схоже, хлопці PG втрачають підтримку. Можливо, навіть потягніть її в майбутньому - https://github.com/dotnet/apireviews/tree/master/2015-07-14-securestring .

Ми повинні видалити шифрування із SecureString на всіх платформах .NET Core - Ми повинні застаріти SecureString - Ми, мабуть, не повинні виставляти SecureString у .NET Core


1
Посилання мертва , але це , здається, продовжувати: docs.microsoft.com/en-us/dotnet/api / ... .. з деяким дуже слабким керівництвом по шляху вперед execpt «Використовувати облікові дані Dont» - github.com/dotnet/platform- compat / blob / master / docs / DE0001.md .. чи не смієте ви використовувати пароль для захисту приватного ключа своїх сертифікатів!
felickz

1
Через 11 років ця відповідь зараз виглядає "новою" правильною. Посилання здаються несвіжими, але вказівки від MS: SecureString не слід використовувати
Richard Morgan

109

Деякі частини основи, які зараз використовують SecureString:

Основна мета - зменшити атакуючу поверхню, а не усунути її. SecureStrings"закріплені" в оперативній пам'яті, щоб збирач сміття не переміщав його і не робив його копії. Він також гарантує, що звичайний текст не буде записаний у файл Swap або в основні демпфи. Шифрування більше схоже на обфускування і не зупинить визначеного хакера, який зможе знайти симетричний ключ, який використовується для його шифрування та розшифрування.

Як вже говорили інші, причина, що вам доведеться створити SecureStringперсонаж за персонажем, полягає в тому, що перший очевидний недолік вчинити інакше: ви, мабуть, вже маєте секретне значення як звичайний рядок, тож який сенс?

SecureStrings - це перший крок у вирішенні проблеми з куркою і яйцем, тому, хоча більшість сучасних сценаріїв потребують перетворення їх у звичайні рядки, щоб взагалі їх використовувати, їхнє існування в рамках тепер означає кращу підтримку для них у майбутнє - принаймні до моменту, коли ваша програма не повинна бути слабкою ланкою.


2
Я наткнувся на нього, переглядаючи властивість пароля ProcessStartInfo; навіть не звертаючи уваги на тип, я просто встановив його на звичайний рядок, поки компілятор не гавкав на мене.
Річард Морган

Неможливо легко знайти симетричний ключ шифрування, оскільки SecureString заснований на DPAPI, який точно не зберігає ключ у
простому

1
Крім того, це не стільки проблема курячих яєць, оскільки це не заміна шифрування в сховищі, а її вирішення для незмінних, керованих рядків .NET.
AviD

2
"Ви, мабуть, вже маєте секретне значення як звичайний рядок, тож який сенс?" Чи є відповідь на це питання? Здається, що в кращому випадку це рішення «краще, ніж нічого», якщо ви збираєтесь тривалий час зберігати в пам'яті пароль.
xr280xr

2
А як бути з кількома простими прикладами використання коду? Я вважаю, що краще зрозумію, як і коли ним користуватися.
codea

37

Редагувати : не використовувати SecureString

Поточні вказівки тепер кажуть, що клас не слід використовувати. Деталі можна знайти за цим посиланням: https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md

Зі статті:

DE0001: SecureString не слід використовувати

Мотивація

  • Мета SecureString- уникнути збереження секретів, збережених у пам'яті процесу, як звичайний текст.
  • Однак навіть у Windows SecureStringне існує як концепція ОС.
    • Це просто робить вікно, де простий текст стає коротшим; це не повністю перешкоджає цьому, оскільки .NET все ще повинен перетворити рядок у звичайне подання тексту.
    • Перевага полягає в тому, що звичайне подання тексту не зависає як екземпляр System.String- термін служби нативного буфера коротший.
  • Вміст масиву незашифровано, за винятком .NET Framework.
    • У .NET Framework вміст внутрішнього масиву char зашифрований. .NET не підтримує шифрування у будь-яких середовищах, через відсутніх API чи ключових проблем управління.

Рекомендація

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

Загальний підхід до роботи з обліковими записами - це уникати їх, а замість цього покладатися на інші способи аутентифікації, наприклад сертифікати або автентифікацію Windows.

Кінець редагування: Оригінальне підсумок нижче

Багато чудових відповідей; ось короткий конспект того, про що йшлося.

Microsoft впровадила клас SecureString, намагаючись забезпечити кращу безпеку з конфіденційною інформацією (наприклад, кредитними картками, паролями тощо). Він автоматично забезпечує:

  • шифрування (у випадку скидання пам'яті або кешування сторінок)
  • закріплення в пам’яті
  • можливість маркування лише для читання (для запобігання будь-яких подальших змін)
  • безпечна конструкція, НЕ дозволяючи передавати постійну струну

Наразі SecureString обмежений у використанні, але очікуйте кращого прийняття в майбутньому.

Виходячи з цієї інформації, конструктор SecureString не повинен просто брати рядок і розрізати його до масиву char, оскільки написана рядок перемагає мету SecureString.

Додаткова інформація:

  • Повідомлення з .NET Security в блозі говорити багато про що так само , як описано тут.
  • І ще один його перегляд та згадування інструменту, який МОЖЕ скинути вміст SecureString.

Редагувати: мені було важко вибрати найкращу відповідь, оскільки в багатьох є хороша інформація; занадто погано, що немає допоміжних варіантів відповідей.


19

Коротка відповідь

чому я не можу просто сказати:

SecureString password = new SecureString("password");

Тому що тепер у вас є passwordпам’ять; без способу її витерти - саме в цьому полягає SecureString .

Довга відповідь

Причина SecureString існує в тому, що ви не можете використовувати ZeroMemory для видалення конфіденційних даних, коли ви закінчите з цим. Він існує, щоб вирішити питання, яке існує через CLR.

У звичайній рідній програмі ви б зателефонували SecureZeroMemory:

Заповнює блок пам'яті нулями.

Примітка : SecureZeroMemory ідентичний ZeroMemory, за винятком того, що компілятор не оптимізує його.

Проблема полягає в тому, що ви не можете телефонувати ZeroMemoryабо SecureZeroMemoryвсередині .NET. І в рядках .NET незмінні; ви навіть не можете перезаписати вміст рядка, як це можна зробити іншими мовами:

//Wipe out the password
for (int i=0; i<password.Length; i++)
   password[i] = \0;

Отже, що ти можеш зробити? Як ми надаємо можливість .NET стерти пароль або номер кредитної картки з пам'яті, коли ми закінчимо це?

Єдиний спосіб це зробити - це помістити рядок у якийсь рідний блок пам'яті, куди потім можна дзвонити ZeroMemory. Нативний об'єкт пам'яті, такий як:

  • a BSTR
  • HGLOBAL
  • CoTaskMem некерована пам'ять

SecureString повертає втрачену здатність

У .NET, рядки не можна стерти, коли ви закінчите з ними:

  • вони незмінні; ви не можете перезаписати їх вміст
  • ви не можете Disposeз них
  • їх прибирання - на розсуд сміттєзбірника

SecureString існує як спосіб забезпечити безпеку рядків і бути в змозі гарантувати їх очищення, коли потрібно.

Ви задали питання:

чому я не можу просто сказати:

SecureString password = new SecureString("password");

Тому що тепер у вас є passwordпам’ять; без способу його витерти. Він застряг там, поки CLR не вирішить повторно використовувати цю пам'ять. Ви поставили нас прямо там, де ми почали; запущена програма з паролем, якої ми не можемо позбутися, і де дамп пам'яті (або Монітор процесів) може бачити пароль.

SecureString використовує API захисту даних для зберігання зашифрованого рядка в пам'яті; таким чином, рядок не буде існувати в swapfiles, краш-дампах або навіть у вікні локальних змінних, коли колега переглядає ваші потреби.

Як прочитати пароль?

Тоді виникає питання: як я взаємодію зі струною? Ви абсолютно не хочете такого способу, як:

String connectionString = secureConnectionString.ToString()

тому що тепер ти вже туди, з чого ти почав - пароль, якого не можеш позбутися. Ви хочете змусити розробників правильно поводитися з чутливою стрічкою - щоб її можна було витерти з пам'яті.

Ось чому .NET надає три зручні допоміжні функції для переміщення SecureString у некеровану пам'ять:

Ви конвертуєте рядок у керовану блоку пам'яті, обробляєте її та знову стираєте її.

Деякі API приймають SecureStrings . Наприклад, в ADO.net 4.5 SqlConnection.Credential приймає набір SqlCredential :

SqlCredential cred = new SqlCredential(userid, password); //password is SecureString
SqlConnection conn = new SqlConnection(connectionString);
conn.Credential = cred;
conn.Open();

Ви також можете змінити пароль в рядку з'єднання:

SqlConnection.ChangePassword(connectionString, cred, newPassword);

І всередині .NET є багато місць, де вони продовжують приймати звичайну String для цілей сумісності, а потім швидко перетворюють її, перекладаючи її в SecureString.

Як ввести текст у SecureString?

Це все ще залишає проблему:

Як я можу отримати пароль в SecureString в першу чергу?

Це виклик, але справа в тому, щоб ви задумалися про безпеку.

Іноді функціональність вам вже надана. Наприклад, керування WPF PasswordBox може повернути вам введений пароль безпосередньо як SecureString :

Властивість PasswordBox.SecurePassword

Отримує пароль, який наразі зберігається у PasswordBox, як SecureString .

Це корисно, оскільки скрізь, де ви проходили навколо необробленої рядки, тепер у вас є система типу, яка скаржиться на те, що SecureString несумісний зі String. Ви хочете пройти якомога довше, перш ніж перетворювати SecureString назад у звичайний рядок.

Перетворити SecureString досить просто:

  • SecureStringToBSTR
  • PtrToStringBSTR

а саме:

private static string CreateString(SecureString secureString)
{
    IntPtr intPtr = IntPtr.Zero;
    if (secureString == null || secureString.Length == 0)
    {
        return string.Empty;
    }
    string result;
    try
    {
        intPtr = Marshal.SecureStringToBSTR(secureString);
        result = Marshal.PtrToStringBSTR(intPtr);
    }
    finally
    {
        if (intPtr != IntPtr.Zero)
        {
            Marshal.ZeroFreeBSTR(intPtr);
        }
    }
    return result;
}

Вони просто не хочуть, щоб ви це робили.

Але як я можу отримати рядок у SecureString? Ну , що вам потрібно зробити , це зупинка , має пароль в String , в першу чергу. Вам потрібно було мати щось інше. Навіть aChar[] масив був би корисним.

Ось тоді ви можете додати кожен символ і витерти простим текстом, коли закінчите:

for (int i=0; i < PasswordArray.Length; i++)
{
   password.AppendChar(PasswordArray[i]);
   PasswordArray[i] = (Char)0;
}

Вам потрібен ваш пароль, який зберігається в деякій пам'яті, яку ви можете стерти. Завантажте його в SecureString звідти.


tl; dr: SecureString існує, щоб забезпечити еквівалент ZeroMemory .

Деякі люди не бачать сенсу стирання пароля користувача з пам'яті, коли пристрій заблоковано , або витирання натискань клавіш із пам'яті після їх автентифікації . Ці люди не використовують SecureString.


14

Існує дуже мало сценаріїв, де можна з розумом використовувати SecureString в поточній версії Framework. Це дійсно корисно лише для взаємодії з керованими API - ви можете маршалити його за допомогою Marshal.SecureStringToGlobalAllocUnicode.

Як тільки ви перетворите його в / з System.String, ви перемогли його призначення.

Зразок MSDN генерує символ SecureString одночасно з введення консолі і передає захищену рядок до некерованого API. Це доволі суперечливо і нереально.

Ви можете очікувати, що майбутні версії .NET матимуть більшу підтримку SecureString, що зробить його більш корисним, наприклад:

  • Консоль SecureString.ReadLineSecure () або подібний до читання введення консолі в SecureString без усього зведеного коду у зразку.

  • Заміна WinForms TextBox, яка зберігає його властивість TextBox.Text як безпечний рядок, щоб паролі можна було безпечно вводити.

  • Розширення до API, пов’язаних із безпекою, щоб паролі передавались як SecureString.

Без вищезазначеного SecureString матиме обмежене значення.


12

Я вважаю, що причина, чому ви повинні робити додавання символів замість однієї плоскої інстанції, полягає в тому, що у фоновому режимі, передаючи "пароль" конструктору SecureString, вводиться ця пам'ять "пароль" у пам'ять, переконуючи мету захищеного рядка.

Додаючи, ви лише поміщаєте персонаж за раз у пам’ять, який, як правило, не є сусіднім один з одним фізично, що ускладнює реконструкцію початкового рядка. Я можу помилитися тут, але саме так мені це було пояснено.

Мета класу - не допустити відкриття захищених даних через дамп пам'яті або подібний інструмент.


11

MS виявила, що в певних випадках збою сервера (робочого столу, будь-якого іншого) траплялися випадки, коли середовище виконання буде робити дамп пам'яті, викриваючи вміст того, що є в пам'яті. Secure String зашифровує її в пам’яті, щоб не допустити можливість зловмиснику отримати вміст рядка.


5

Однією з найбільших переваг SecureString є те, що передбачається уникнути можливості зберігання ваших даних на диску через кешування сторінок. Якщо у вас є пароль в пам'яті, а потім завантажуєте велику програму або набір даних, ваш пароль може бути записаний у файл swap, коли ваша програма вилучається з пам'яті. Що стосується SecureString, принаймні дані не будуть нескінченно сидіти на диску на чіткому тексті.


4

Я думаю, це тому, що рядок призначений для захисту, тобто хакер не повинен її читати. Якщо ініціалізувати його за допомогою рядка, хакер може прочитати початковий рядок.


4

Отже, як зазначено в описі, значення зберігається в зашифрованому вигляді, тобто дамп пам'яті вашого процесу не розкриє значення рядка (без досить серйозної роботи).

Причина, що ви не можете просто сконструювати SecureString з постійної рядка, полягає в тому, що тоді у вас буде незашифрована версія рядка в пам'яті. Обмеження у створенні рядка по шматочках знижує ризик збереження всієї струни в пам'яті одразу.


2
Якщо вони обмежують побудову з постійного рядка, то рядок foreach (char c у "пароль" .ToCharArray ()) переможе це, ні? Це повинно бути пропущено.AppendChar ('p'); pass.AppendChar ('a'); тощо?
Річард Морган

Так, ви можете досить легко викинути те, що забезпечує вам маленький захист SecureString. Вони намагаються зробити це важким, щоб повністю вистрілити собі в ногу. Очевидно, що має бути якийсь спосіб отримати значення в SecureString і вийти з нього, інакше ви нічого не можете використати.
Марк Бессі

1

Інший випадок використання, коли ви працюєте з платіжними програмами (POS), і ви просто не можете використовувати незмінні структури даних для зберігання конфіденційних даних, оскільки ви ретельний розробник. Наприклад: якщо я зберігатиму чутливі дані картки або метадані авторизації у незмінному рядку, завжди буде такий випадок, коли ці дані будуть доступні в пам'яті протягом значного часу після їх викидання. Я не можу просто переписати його. Ще одна величезна перевага, коли такі чутливі дані зберігаються в зашифрованій пам'яті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.