Kiedy potrzebuję SecureString w .NET?

179

Usiłuję zrozumieć cel SecureString .NET. Z MSDN:

Instancja klasy System.String jest zarówno niezmienna, jak i gdy nie jest już potrzebna, nie może być programowo zaplanowana do odśmiecania; oznacza to, że wystąpienie jest tylko do odczytu po utworzeniu i nie można przewidzieć, kiedy wystąpienie zostanie usunięte z pamięci komputera. W konsekwencji, jeśli obiekt String zawiera poufne informacje, takie jak hasło, numer karty kredytowej lub dane osobowe, istnieje ryzyko, że informacje mogą zostać ujawnione po ich użyciu, ponieważ aplikacja nie może usunąć danych z pamięci komputera.

Obiekt SecureString jest podobny do obiektu String, ponieważ ma wartość tekstową. Jednak wartość obiektu SecureString jest automatycznie szyfrowana, może być modyfikowana, dopóki aplikacja nie oznaczy go jako tylko do odczytu, i może zostać usunięta z pamięci komputera przez aplikację lub moduł czyszczenia pamięci .NET Framework.

Wartość wystąpienia SecureString jest automatycznie szyfrowana przy inicjowaniu wystąpienia lub po modyfikacji wartości. Twoja aplikacja może uczynić instancję niezmienną i zapobiec dalszej modyfikacji przez wywołanie metody MakeReadOnly.

Czy automatyczne szyfrowanie to duża wypłata?

I dlaczego nie mogę po prostu powiedzieć:

SecureString password = new SecureString("password");

zamiast

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

Jakiego aspektu SecureString mi brakuje?

Richard Morgan
źródło
3
11 lat później MS nie zaleca już SecureStringnowego oprogramowania: github.com/dotnet/platform-compat/blob/master/docs/DE0001.md
Matt Thomas

Odpowiedzi:

4

Przestałbym używać SecureString. Wygląda na to, że chłopaki PG rezygnują z tego wsparcia. Być może nawet wyciągniesz go w przyszłości - https://github.com/dotnet/apireviews/tree/master/2015-07-14-securestring .

Powinniśmy usunąć szyfrowanie z SecureString na wszystkich platformach .NET Core - Powinniśmy zdezaktualizować SecureString - Prawdopodobnie nie powinniśmy ujawniać SecureString w .NET Core

Joe Healy
źródło
1
Link jest martwy, ale wygląda na to, że nadal działa : docs.microsoft.com/en-us/dotnet/api/ ... .. z kilkoma bardzo słabymi wskazówkami na temat ścieżki naprzód wykonaj polecenie „nie używaj poświadczeń” - github.com/dotnet/platform- kompatybil / blob / master / docs / DE0001.md .. nie waż się też użyć hasła do ochrony klucza prywatnego swoich certyfikatów!
felickz
1
Po 11 latach ta odpowiedź wydaje się teraz „nowa” poprawna. Linki wydają się przestarzałe, ale wytyczne od MS brzmią: SecureString nie powinien być używany
Richard Morgan
109

Niektóre części frameworka, które obecnie używają SecureString:

Głównym celem jest zmniejszenie powierzchni ataku, a nie jej wyeliminowanie. SecureStringssą „przypięte” do pamięci RAM, więc Garbage Collector nie będzie go przesuwał ani kopiował. Daje również pewność, że zwykły tekst nie zostanie zapisany w pliku wymiany lub w zrzutach pamięci. Szyfrowanie bardziej przypomina zaciemnianie i nie powstrzyma jednak określonego hakera, który byłby w stanie znaleźć klucz symetryczny użyty do jego zaszyfrowania i odszyfrowania.

Jak powiedzieli inni, powodem, dla którego musisz stworzyć SecureStringznak po znaku, jest pierwsza oczywista wada polegająca na zrobieniu czegoś innego: prawdopodobnie masz już tajną wartość jako zwykły ciąg znaków, więc o co chodzi?

SecureStrings to pierwszy krok w rozwiązaniu problemu z kurczakiem i jajkiem, więc nawet jeśli większość obecnych scenariuszy wymaga przekształcenia ich z powrotem w regularne ciągi, aby w ogóle z nich skorzystać, ich istnienie w strukturze oznacza teraz lepsze wsparcie dla nich w przyszłość - przynajmniej do punktu, w którym twój program nie musi być słabym ogniwem.

Chris Wenham
źródło
2
Natknąłem się na to, patrząc na właściwość Hasło ProcessStartInfo; nawet nie zwracając uwagi na typ, po prostu ustawiłem go na zwykły ciąg, dopóki kompilator nie zaszczekał na mnie.
Richard Morgan
Znalezienie symetrycznego klucza szyfrującego nie będzie łatwe, ponieważ SecureString jest oparty na DPAPI, który nie przechowuje dokładnie klucza w postaci zwykłego tekstu ...
AviD
1
Ponadto nie jest to tak bardzo problem z kurczakiem i jajami, ponieważ nie zastępuje szyfrowania w pamięci masowej - ale jest obejściem niezmiennych, zarządzanych ciągów .NET.
AviD
2
„prawdopodobnie masz już tajną wartość jako zwykły ciąg znaków, więc o co chodzi?” Czy istnieje odpowiedź na to pytanie? Wydaje się, że w najlepszym wypadku jest to rozwiązanie „lepsze niż nic”, jeśli zamierzasz przechowywać hasło w pamięci przez dłuższy czas.
xr280xr
2
Co powiesz na kilka prostych przykładów użycia kodu? Wierzę, że lepiej zrozumiałbym, jak i kiedy go używać.
codea
37

Edycja : Nie używaj SecureString

Obecne wytyczne mówią teraz, że klasa nie powinna być używana. Szczegóły można znaleźć pod tym linkiem: https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md

Z artykułu:

DE0001: Nie należy używać SecureString

Motywacja

  • Celem SecureStringjest uniknięcie przechowywania sekretów w pamięci procesu jako zwykłego tekstu.
  • Jednak nawet w systemie Windows SecureStringnie istnieje jako koncepcja systemu operacyjnego.
    • Po prostu sprawia, że ​​okno skraca zwykły tekst; nie zapobiega to w pełni, ponieważ .NET wciąż musi przekonwertować ciąg na zwykły tekst.
    • Zaletą jest to, że zwykła reprezentacja tekstu nie kręci się jako przykład System.String- czas życia natywnego bufora jest krótszy.
  • Zawartość tablicy jest niezaszyfrowana, z wyjątkiem .NET Framework.
    • W .NET Framework zawartość wewnętrznej tablicy znaków jest szyfrowana. .NET nie obsługuje szyfrowania we wszystkich środowiskach z powodu brakujących interfejsów API lub problemów z zarządzaniem kluczami.

Rekomendacje

Nie używaj SecureString do nowego kodu. Podczas przenoszenia kodu do .NET Core należy wziąć pod uwagę, że zawartość tablicy nie jest zaszyfrowana w pamięci.

Ogólne podejście do obsługi poświadczeń polega na unikaniu ich i poleganiu na innych sposobach uwierzytelnienia, takich jak certyfikaty lub uwierzytelnianie systemu Windows.

Zakończ edycję: oryginalne podsumowanie poniżej

Wiele świetnych odpowiedzi; oto krótkie streszczenie tego, co zostało omówione.

Microsoft wdrożył klasę SecureString, aby zapewnić lepsze bezpieczeństwo poufnych informacji (takich jak karty kredytowe, hasła itp.). Automatycznie zapewnia:

  • szyfrowanie (w przypadku zrzutów pamięci lub buforowania strony)
  • przypinanie w pamięci
  • możliwość oznaczenia jako tylko do odczytu (aby zapobiec dalszym modyfikacjom)
  • bezpieczna konstrukcja, NIE pozwalając na przekazanie stałego ciągu

Obecnie SecureString jest ograniczony w użyciu, ale oczekuje lepszej adaptacji w przyszłości.

W oparciu o te informacje konstruktor SecureString nie powinien po prostu pobierać ciągu i kroić go na tablicę znaków, ponieważ wypisanie ciągu nie spełnia celu funkcji SecureString.

Dodatkowe informacje:

  • postu z .NET Bezpieczeństwa blogu mówisz tak samo jak tu opisany.
  • I jeszcze jeden, przeglądając go i wspominając o narzędziu, które MOŻE zrzucić zawartość SecureString.

Edycja: Trudno było wybrać najlepszą odpowiedź, ponieważ w wielu jest dobra informacja; szkoda, że ​​nie ma opcji odpowiedzi wspomaganej.

Richard Morgan
źródło
19

Krótka odpowiedź

dlaczego nie mogę po prostu powiedzieć:

SecureString password = new SecureString("password");

Ponieważ teraz masz passwordw pamięci; bez możliwości jego wyczyszczenia - właśnie o to chodzi w SecureString .

Długa odpowiedź

Powodem SecureString istnieje dlatego, że nie można używać ZeroMemory wytrzeć poufnych danych, gdy jesteś z nim zrobić. Istnieje on do rozwiązania problemu, który istnieje , ponieważ CLR.

W zwykłej natywnej aplikacji dzwoniłbyś SecureZeroMemory:

Wypełnia blok pamięci zerami.

Uwaga : SecureZeroMemory jest identyczny ZeroMemory, z wyjątkiem tego , że kompilator go nie zoptymalizuje.

Problem polega na tym, że nie można dzwonić ZeroMemoryani korzystać z SecureZeroMemoryplatformy .NET. A w .NET ciągi są niezmienne; nie możesz nawet zastąpić zawartości ciągu, jak w innych językach:

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

Więc co możesz zrobić? W jaki sposób zapewniamy w .NET możliwość czyszczenia hasła lub numeru karty kredytowej z pamięci, gdy skończymy z tym?

Jedynym sposobem, w jaki można to zrobić, byłoby umieszczenie łańcucha w jakimś rodzimym bloku pamięci, do którego można następnie wywołać ZeroMemory. Natywny obiekt pamięci, taki jak:

  • a BSTR
  • HGLOBAL
  • CoTaskMem niezarządzana pamięć

SecureString przywraca utraconą zdolność

W .NET ciągi nie mogą być czyszczone, gdy już z nimi skończysz:

  • są niezmienne; nie można zastąpić ich zawartości
  • nie możesz Disposez nich
  • ich sprzątanie jest na łasce śmieciarza

SecureString istnieje jako sposób na ominięcie bezpieczeństwa ciągów i być w stanie zagwarantować ich czyszczenie w razie potrzeby.

Zadałeś pytanie:

dlaczego nie mogę po prostu powiedzieć:

SecureString password = new SecureString("password");

Ponieważ teraz masz passwordw pamięci; bez możliwości jego wyczyszczenia. Utknął tam, dopóki CLR nie zdecyduje się ponownie użyć tej pamięci. Umieściłeś nas tam, gdzie zaczęliśmy; działająca aplikacja z hasłem, którego nie możemy się pozbyć i gdzie zrzut pamięci (lub Process Monitor) może zobaczyć hasło.

SecureString używa Data Protection API do przechowywania łańcucha zaszyfrowanego w pamięci; w ten sposób łańcuch nie będzie istniał w plikach wymiany, zrzutach awaryjnych, a nawet w oknie zmiennych lokalnych, gdy kolega powinien się nad nim zastanowić.

Jak odczytać hasło?

Następnie pojawia się pytanie: jak wchodzić w interakcje z ciągiem? Absolutnie nie chcesz takiej metody jak:

String connectionString = secureConnectionString.ToString()

ponieważ teraz wracasz do miejsca, w którym zacząłeś - hasła, którego nie możesz się pozbyć. Chcesz zmusić programistów do prawidłowego obchodzenia się z wrażliwym łańcuchem - aby mógł zostać usunięty z pamięci.

Właśnie dlatego .NET zapewnia trzy przydatne funkcje pomocnicze, które pozwalają wprowadzić SecureString do niezarządzanej pamięci:

Przekształcasz ciąg w niezarządzany obiekt blob pamięci, obsłużysz go, a następnie wyczyścisz ponownie.

Niektóre interfejsy API akceptują SecureStrings . Na przykład w ADO.net 4.5 SqlConnection.Credential przyjmuje zestaw SqlCredential :

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

Możesz także zmienić hasło w ciągu połączenia:

SqlConnection.ChangePassword(connectionString, cred, newPassword);

Wewnątrz .NET znajduje się wiele miejsc, w których nadal akceptują zwykły ciąg znaków dla celów kompatybilności, a następnie szybko odwracają go i umieszczają w SecureString.

Jak wstawić tekst do SecureString?

Pozostaje problem:

Jak w ogóle uzyskać hasło do SecureString?

To wyzwanie, ale chodzi o to, abyś pomyślał o bezpieczeństwie.

Czasami ta funkcja jest już dla Ciebie dostępna. Na przykład kontrolka WPF PasswordBox może zwrócić ci wprowadzone hasło bezpośrednio jako SecureString :

PasswordBox.SecurePassword Właściwość

Pobiera hasło aktualnie przechowywane przez PasswordBox jako SecureString .

Jest to pomocne, ponieważ wszędzie tam, gdzie przekazywano nieprzetworzony ciąg znaków, teraz system typów narzeka, że ​​SecureString jest niezgodny z ciągiem znaków. Chcesz przejść tak długo, jak to możliwe, zanim będziesz musiał przekonwertować SecureString z powrotem na zwykły ciąg.

Konwersja SecureString jest dość łatwa:

  • SecureStringToBSTR
  • PtrToStringBSTR

jak w:

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;
}

Oni naprawdę nie chcą, żebyś to robił.

Ale jak uzyskać ciąg do SecureString? Cóż, musisz przede wszystkim przestać mieć hasło w ciągu znaków . Musiałeś mieć to w czymś innym. Nawet aChar[] tablica byłaby pomocna.

Właśnie wtedy możesz dołączyć każdą postać i wyczyścić tekst jawny, gdy skończysz:

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

Musisz hasła zapisanego w jakiejś pamięci, które można wymazać. Załaduj go do SecureString stamtąd.


tl; dr: SecureString istnieje w celu zapewnienia odpowiednika ZeroMemory .

Niektóre osoby nie widzą sensu wymazywania hasła użytkownika z pamięci, gdy urządzenie jest zablokowane , ani wymazywania wymazania klawiszy z pamięci po ich uwierzytelnieniu . Ci ludzie nie używają SecureString.

Ian Boyd
źródło
14

Istnieje bardzo niewiele scenariuszy, w których można rozsądnie korzystać z SecureString w bieżącej wersji Framework. Jest to naprawdę przydatne tylko w przypadku interakcji z niezarządzanymi interfejsami API - możesz je uporządkować za pomocą Marshal.SecureStringToGlobalAllocUnicode.

Jak tylko skonwertujesz go na / z System.String, pokonałeś jego cel.

Próbka MSDN generuje znak SecureString naraz z danych wejściowych konsoli i przekazuje bezpieczny ciąg do niezarządzanego interfejsu API. Jest raczej zawiły i nierealny.

Można się spodziewać, że przyszłe wersje platformy .NET będą miały większą obsługę SecureString, dzięki czemu będzie bardziej przydatna, np .:

  • SecureString Console.ReadLineSecure () lub podobny do odczytu danych wejściowych konsoli do SecureString bez całego skomplikowanego kodu w próbce.

  • Zastępowanie WinForm TextBox, która przechowuje swoją właściwość TextBox.Text jako bezpieczny ciąg znaków, aby umożliwić bezpieczne wprowadzanie haseł.

  • Rozszerzenia interfejsów API związanych z bezpieczeństwem, aby umożliwić przekazywanie haseł jako SecureString.

Bez powyższego SecureString będzie miał ograniczoną wartość.

Joe
źródło
12

Uważam, że powodem dodawania znaków zamiast jednej płaskiej instancji jest to, że w tle przekazanie „hasła” do konstruktora SecureString umieszcza ten ciąg „hasła” w pamięci, pokonując cel bezpiecznego ciągu.

Dołączając, zapisujesz tylko znak na raz, który również nie jest fizycznie przylegający do siebie, co znacznie utrudnia odtworzenie oryginalnego łańcucha. Mogłem się tutaj mylić, ale tak mi to wyjaśniono.

Celem tej klasy jest zapobieganie ujawnieniu bezpiecznych danych za pomocą zrzutu pamięci lub podobnego narzędzia.

JoshReedSchramm
źródło
11

MS odkryło, że w niektórych przypadkach powodujących awarię serwera (pulpitu, cokolwiek) zdarzały się sytuacje, w których środowisko wykonawcze wykonywałoby zrzut pamięci odsłaniający zawartość pamięci. Bezpieczny ciąg szyfruje go w pamięci, aby uniemożliwić osobie atakującej odzyskanie zawartości ciągu.

kemiller2002
źródło
5

Jedną z wielkich zalet SecureString jest to, że ma on unikać możliwości przechowywania danych na dysku z powodu buforowania stron. Jeśli masz hasło w pamięci, a następnie załadujesz duży program lub zestaw danych, hasło może zostać zapisane do pliku wymiany, gdy program zostanie stronicowany z pamięci. Dzięki SecureString przynajmniej dane nie będą znajdować się na dysku w nieskończoność w postaci czystego tekstu.

Jason Z
źródło
4

Chyba dlatego, że ciąg ma być bezpieczny, tzn. Haker nie powinien być w stanie go odczytać. Jeśli zainicjujesz go za pomocą łańcucha, haker może odczytać oryginalny łańcuch.

OregonGhost
źródło
4

Cóż, zgodnie z opisem, wartość jest przechowywana w postaci zaszyfrowanej, co oznacza, że ​​zrzut pamięci procesu nie ujawni wartości łańcucha (bez dość poważnej pracy).

Powodem nie można po prostu skonstruować SecureString z ciągiem stałym jest bo wtedy byłoby mieć niezaszyfrowane wersję napisu w pamięci. Ograniczenie cię do tworzenia łańcucha w kawałkach zmniejsza ryzyko, że cały łańcuch będzie w pamięci na raz.

Mark Bessey
źródło
2
Jeśli ograniczają konstrukcję ze stałego łańcucha, to foreach linii (char c w „password” .ToCharArray ()) pokonałby to, nie? Powinno być pass.AppendChar ('p'); pass.AppendChar („a”); itp.?
Richard Morgan
Tak, możesz z łatwością wyrzucić to, co daje ci niewielka ochrona SecureString. Próbują utrudnić całkowite zastrzelenie się w stopę. Oczywiście musi istnieć jakiś sposób, aby uzyskać wartość do i od SecureString, inaczej nie można użyć go do niczego.
Mark Bessey,
1

Innym przykładem użycia jest praca z aplikacjami płatniczymi (POS) i po prostu nie można używać niezmiennych struktur danych do przechowywania wrażliwych danych, ponieważ jesteś ostrożnym programistą. Na przykład: jeśli będę przechowywać wrażliwe dane karty lub metadane autoryzacji w niezmiennym ciągu, zawsze będzie tak, że dane te będą dostępne w pamięci przez znaczny czas po ich odrzuceniu. Nie mogę tego po prostu zastąpić. Kolejna ogromna zaleta, w której takie wrażliwe dane są przechowywane w pamięci zaszyfrowane.

rzymski
źródło