Dlaczego w hasłach char [] jest lepszy niż String?

3422

W Swing pole hasła ma metodę getPassword()(zwraca char[]) zamiast zwykłej metody getText()(zwraca String). Podobnie natrafiłem na sugestię, aby nie używać Stringdo obsługi haseł.

Dlaczego Stringstanowi zagrożenie dla bezpieczeństwa, jeśli chodzi o hasła? Jest niewygodny w użyciu char[].

Zawstydzony
źródło

Odpowiedzi:

4288

Ciągi są niezmienne . Oznacza to, że po utworzeniu String, jeśli inny proces może zrzucić pamięć, nie ma mowy (oprócz refleksji ), aby pozbyć się danych, zanim rozpocznie się odśmiecanie .

Za pomocą tablicy możesz jawnie wyczyścić dane po zakończeniu pracy. Możesz zastąpić tablicę czymkolwiek, co chcesz, a hasło nie będzie nigdzie w systemie, nawet przed wyrzucaniem elementów bezużytecznych.

Tak, to jest obawa dotycząca bezpieczeństwa - ale nawet użycie char[]ogranicza okno szansy atakującego i dotyczy tylko tego rodzaju ataku.

Jak zauważono w komentarzach, możliwe jest, że tablice przenoszone przez śmieciarz pozostawiają zbłąkane kopie danych w pamięci. Wierzę, że jest to specyficzne dla implementacji - śmieciarz może wyczyścić całą pamięć w miarę upływu czasu, aby uniknąć tego rodzaju rzeczy. Nawet jeśli tak jest, wciąż jest czas, w którym char[]zawiera rzeczywiste postacie jako okno ataku.

Jon Skeet
źródło
3
Jeśli proces ma dostęp do pamięci aplikacji, oznacza to już naruszenie bezpieczeństwa, prawda?
Yeti
5
@Yeti: Tak, ale to nie jest tak, że jest czarno-biały. Jeśli mogą uzyskać tylko migawkę pamięci, to chcesz zmniejszyć, ile szkód może wyrządzić migawka, lub zmniejszyć okno, w którym można wykonać naprawdę poważną migawkę.
Jon Skeet
11
Powszechną metodą ataku jest uruchomienie procesu, który przydziela dużo pamięci, a następnie skanuje ją w poszukiwaniu pożytecznych danych, takich jak hasła. Proces nie potrzebuje magicznego dostępu do przestrzeni pamięci innego procesu; polega tylko na tym, że inne procesy giną bez uprzedniego wyczyszczenia poufnych danych, a system operacyjny nie usuwa również pamięci (ani buforów stron) przed udostępnieniem jej dla nowego procesu. Usunięcie haseł przechowywanych w char[]lokalizacjach odcina tę linię ataku, co jest niemożliwe podczas korzystania String.
Ted Hopp,
Jeśli system operacyjny nie wyczyści pamięci przed przekazaniem jej do innego procesu, system operacyjny ma poważne problemy z bezpieczeństwem! Jednak technicznie czyszczenie jest często wykonywane przy użyciu sztuczek w trybie chronionym, a jeśli procesor jest uszkodzony (np. Intel Meldown), nadal można odczytać starą zawartość pamięci.
Mikko Rantalainen
1221

Podczas gdy inne sugestie wydają się aktualne, istnieje jeszcze jeden dobry powód. W przypadku zwykłego Stringmasz znacznie większe szanse na przypadkowe wydrukowanie hasła do dzienników , monitorów lub innego niebezpiecznego miejsca. char[]jest mniej wrażliwy.

Rozważ to:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

Wydruki:

String: Password
Array: [C@5829428e
Konrad Garus
źródło
40
@voo, ale wątpię, byś logował się przez bezpośrednie pisanie w celu przesyłania strumieniowego i konkatenacji. rejestrowanie przekształciłoby char [] w dobry wynik
bestsss
41
@ Thr4wn Domyślna implementacja toStringto classname@hashcode. [Creprezentuje char[], reszta to szesnastkowy kod skrótu.
Konrad Garus
15
Ciekawy pomysł. Chciałbym zauważyć, że nie jest to transponowane do Scali, która ma znaczenie dla ciągów tablic.
mauhiz
37
Napiszę Passworddo tego typ zajęć. Jest mniej niejasny i trudniej gdzieś przypadkowo przejść.
prawy
8
Dlaczego ktoś miałby zakładać, że tablica znaków będzie rzutowana jako obiekt? Nie jestem pewien, dlaczego rozumiem, dlaczego każdy lubi tę odpowiedź. Załóżmy, że zrobiłeś to: System.out.println („Password” .toCharArray ());
GC_
680

Aby zacytować oficjalny dokument, przewodnik po architekturze Java Cryptography Architecture mówi o haseł char[]vs. Stringhasłach (o szyfrowaniu opartym na hasłach, ale bardziej ogólnie dotyczy haseł):

Logiczne wydaje się gromadzenie i przechowywanie hasła w obiekcie typu java.lang.String. Jednak poniższe ostrzeżenia Objecttypu Stringsą niezmienne, tzn. Nie zdefiniowano żadnych metod, które pozwalają na zmianę (nadpisanie) lub wyzerowanie zawartości String po użyciu. Ta funkcja sprawia, że Stringobiekty nie nadają się do przechowywania poufnych informacji, takich jak hasła użytkowników. Zamiast tego zawsze należy gromadzić i przechowywać poufne informacje w chartablicy.

Wytyczna 2-2 Wytycznych bezpiecznego kodowania dla języka programowania Java, wersja 4.0 również mówi coś podobnego (chociaż pierwotnie jest to w kontekście rejestrowania):

Wytyczna 2-2: Nie rejestruj bardzo poufnych informacji

Niektóre informacje, takie jak numery ubezpieczenia społecznego (SSN) i hasła, są bardzo poufne. Informacje te nie powinny być przechowywane dłużej niż to konieczne ani w miejscu, w którym mogą być widoczne, nawet przez administratorów. Na przykład nie należy go wysyłać do plików dziennika, a jego obecność nie powinna być wykrywalna podczas wyszukiwania. Niektóre dane przejściowe mogą być przechowywane w zmiennych strukturach danych, takich jak tablice znaków, i usuwane natychmiast po użyciu. Czyszczenie struktur danych ma zmniejszoną skuteczność w typowych systemach wykonawczych Java, ponieważ obiekty są przenoszone w pamięci w sposób przezroczysty do programisty.

Wytyczne te mają również wpływ na implementację i wykorzystanie bibliotek niższego poziomu, które nie mają wiedzy semantycznej na temat danych, z którymi mają do czynienia. Na przykład, niskopoziomowa biblioteka analizująca ciąg znaków może rejestrować tekst, na którym działa. Aplikacja może analizować SSN z biblioteką. Stwarza to sytuację, w której SSN są dostępne dla administratorów z dostępem do plików dziennika.

Bruno
źródło
4
to jest dokładnie błędne / fałszywe odniesienie, o którym mówię poniżej odpowiedzi Jona, jest to dobrze znane źródło z dużą ilością krytyki.
bestsss
37
@ bestest, czy możesz również zacytować referencje?
user961954,
10
@Bestass przepraszam, ale Stringjest całkiem dobrze zrozumiany i jak zachowuje się w JVM ... istnieją dobre powody, aby używać go char[]zamiast Stringzajmować się hasłami w bezpieczny sposób.
SnakeDoc
3
po raz kolejny hasło jest przekazywane do przeglądarki jako ciąg znaków jako „ciąg znaków”, a nie znak? więc bez względu na to, co robisz, jest to ciąg, w którym to momencie należy na niego działać i odrzucić, nigdy nie przechowywać w pamięci?
Dawesi
3
@Dawesi - At which point- jest to specyficzne dla aplikacji, ale ogólną zasadą jest, aby to zrobić, gdy tylko zdobędziesz coś, co powinno być hasłem (zwykły tekst lub w inny sposób). Otrzymujesz go z przeglądarki na przykład jako część żądania HTTP. Nie możesz kontrolować dostawy, ale możesz kontrolować własne przechowywanie, więc jak tylko je zdobędziesz, umieść je w char [], zrób to, co musisz zrobić, a następnie ustaw wszystkie na „0” i pozwól gc odzyskaj to.
luis.espinal
354

Tablice znaków ( char[]) można wyczyścić po użyciu, ustawiając każdy znak na zero, a nie Ciągi znaków. Jeśli ktoś może w jakiś sposób zobaczyć obraz pamięci, może zobaczyć hasło w postaci zwykłego tekstu, jeśli używane są ciągi, ale jeśli char[]jest używane, po usunięciu danych z zer, hasło jest bezpieczne.

alephx
źródło
13
Domyślnie niezabezpieczone. Jeśli mówimy o aplikacji internetowej, większość kontenerów internetowych przekaże hasło do HttpServletRequestobiektu w postaci zwykłego tekstu. Jeśli wersja JVM ma wersję 1.6 lub niższą, będzie w przestrzeni permgenowej. Jeśli jest w wersji 1.7, będzie nadal czytelny, dopóki nie zostanie zebrany. (Ilekroć to jest.)
avgvstvs
4
@avgvstvs: łańcuchy nie są automatycznie przenoszone do przestrzeni permgenów, co dotyczy tylko łańcuchów internowanych. Poza tym przestrzeń permgenowa podlega również odśmiecaniu, tylko w mniejszym stopniu. Prawdziwym problemem z przestrzenią permgenową jest jej stała wielkość, co jest dokładnie powodem, dla którego nikt nie powinien bezmyślnie wywoływać intern()dowolnych ciągów. Ale masz rację, ponieważ Stringinstancje istnieją przede wszystkim (dopóki nie zostaną zebrane), a char[]później przekształcenie ich w tablice nie zmieni tego.
Holger,
3
@Holger patrz docs.oracle.com/javase/specs/jvms/se6/html/... "W przeciwnym razie tworzona jest nowa instancja klasy String zawierająca sekwencję znaków Unicode podaną przez strukturę CONSTANT_String_info; ta instancja klasy jest wynikiem wyprowadzenie literału łańcucha. W końcu wywoływana jest metoda intern nowej instancji String. " W 1.6 JVM wywoływałby dla ciebie stażystę, gdy wykryje identyczne sekwencje.
avgvstvs
3
@Holger, masz rację I połączyłem stałą pulę i pulę ciągów, ale nieprawdą jest również to, że przestrzeń permgenowa dotyczyła tylko internowanych łańcuchów. Przed wersją 1.7 zarówno stała_pula, jak i ciąg_początkowa znajdowały się w przestrzeni permgenowej. Oznacza to, że jedyną klasą ciągów, które zostały przydzielone do stosu, były, jak powiedziałeś, new String()lub StringBuilder.toString() zarządzałem aplikacjami z dużą liczbą stałych ciągów, w wyniku czego mieliśmy wiele pełzania permgenów. Do 1.7.
avgvstvs,
5
@avgvstvs: cóż, stałe łańcuchowe są, jak nakazuje JLS, zawsze internalizowane, stąd stwierdzenie, że internowane łańcuchy znalazły się w przestrzeni permgenowej, stosowane niejawnie do stałych łańcuchowych. Jedyna różnica polega na tym, że stałe łańcucha zostały w pierwszej kolejności utworzone w przestrzeni permgenowej, podczas gdy wywołanie intern()dowolnego ciągu może spowodować przydzielenie równoważnego ciągu w przestrzeni permgenowej. Ten ostatni mógłby dostać GC'ed, gdyby nie istniał dosłowny ciąg tych samych treści dzielących ten obiekt…
Holger
220

Niektórzy uważają, że musisz zastąpić pamięć używaną do przechowywania hasła, gdy już go nie potrzebujesz. Skraca to czas, w którym atakujący musi odczytać hasło z twojego systemu i całkowicie ignoruje fakt, że atakujący potrzebuje już wystarczającego dostępu do przejęcia pamięci JVM, aby to zrobić. Atakujący z takim dostępem może złapać twoje kluczowe wydarzenia, co czyni je całkowicie bezużytecznymi (AFAIK, więc popraw mnie, jeśli się mylę).

Aktualizacja

Dzięki komentarzom muszę zaktualizować swoją odpowiedź. Najwyraźniej istnieją dwa przypadki, w których można dodać (bardzo) niewielką poprawę bezpieczeństwa, ponieważ skraca czas, w którym hasło może wylądować na dysku twardym. Nadal uważam, że to przesada w większości przypadków użycia.

  • Twój system docelowy może być źle skonfigurowany lub musisz założyć, że tak jest, i musisz być paranoikiem na temat zrzutów rdzenia (może to być poprawne, jeśli systemami nie zarządza administrator).
  • Twoje oprogramowanie musi być zbyt paranoiczne, aby zapobiec wyciekom danych, gdy atakujący uzyskuje dostęp do sprzętu - używając takich rzeczy, jak TrueCrypt (wycofany z produkcji), VeraCrypt lub CipherShed .

Jeśli to możliwe, wyłączenie zrzutów pamięci i pliku wymiany rozwiązałoby oba problemy. Wymagałyby one jednak uprawnień administratora i mogą zmniejszyć funkcjonalność (mniej pamięci do użycia), a pobieranie pamięci RAM z działającego systemu nadal byłoby istotnym problemem.

josefx
źródło
33
Zastąpiłbym słowo „całkowicie bezużyteczne” słowem „tylko niewielka poprawa bezpieczeństwa”. Na przykład możesz uzyskać dostęp do zrzutu pamięci, jeśli akurat masz dostęp do odczytu do katalogu tmp, źle skonfigurowanego komputera i awarii aplikacji. W takim przypadku nie będzie w stanie zainstalować keyloggera, ale mógłby analizować zrzutu rdzenia.
Joachim Sauer
44
Wymazywanie niezaszyfrowanych danych z pamięci, jak tylko to zrobisz, jest uważane za najlepszą praktykę, nie dlatego, że jest niezawodne (nie jest); ale ponieważ zmniejsza poziom narażenia na zagrożenie. Robiąc to, nie można zapobiec atakom w czasie rzeczywistym; ale ponieważ służy on jako narzędzie do łagodzenia szkód, znacznie zmniejszając ilość danych, które są ujawniane podczas retroaktywnego ataku na migawkę pamięci (np. kopia pamięci aplikacji, która została zapisana w pliku wymiany lub została odczytana z szarpanej pamięci z działającego serwera i przeniesiony na inny, zanim jego stan się nie powiedzie).
Dan Is Fiddling By Firelight
9
Zwykle zgadzam się z postawą tej odpowiedzi. Zaryzykowałbym zaproponowanie, aby większość przypadków naruszenia bezpieczeństwa następowała na znacznie wyższym poziomie abstrakcji niż fragmenty pamięci. Pewnie, istnieją prawdopodobnie scenariusze w systemach hiper-bezpiecznych systemów obronnych, w których może to budzić poważne obawy, ale poważne myślenie na tym poziomie to przesada w przypadku 99% aplikacji, w których wykorzystuje się platformę .NET lub Java (ponieważ jest to związane ze zbieraniem pamięci).
kingdango
10
Po penetracji pamięci serwera przez Heartbleed, ujawniając hasła, zastąpiłbym ciąg „tylko niewielka poprawa bezpieczeństwa” przez „absolutnie niezbędny, aby nie używać ciągu dla haseł, ale zamiast tego użyj znaku char []”.
Peter vdL
9
@PetervdL heartbleed zezwalał tylko na odczyt określonej, ponownie użytej kolekcji buforów (używanych zarówno dla danych o krytycznym znaczeniu dla bezpieczeństwa, jak i sieciowych we / wy bez czyszczenia pomiędzy - ze względu na wydajność), nie można połączyć tego z łańcuchem Java, ponieważ z założenia nie można ich ponownie użyć . Nie można również wczytać do losowej pamięci przy użyciu Javy, aby uzyskać dostęp do zawartości ciągu. Problemy językowe i projektowe, które prowadzą do bicia serca, są po prostu niemożliwe w przypadku ciągów Java.
josefx
86

Nie sądzę, aby była to trafna sugestia, ale mogę przynajmniej zgadywać przyczynę.

Myślę, że motywacją jest chęć upewnienia się, że możesz usunąć wszystkie ślady hasła szybko i pewnie po jego użyciu. Za pomocą char[]możesz na pewno zastąpić każdy element tablicy spacją lub czymś innym. Nie możesz w Stringten sposób edytować wewnętrznej wartości .

Ale to nie jest dobra odpowiedź; dlaczego nie upewnić się, że odniesienie do char[]lub Stringnie ucieka? Wtedy nie ma problemu z bezpieczeństwem. Ale chodzi o to, że Stringprzedmioty można intern()edytować teoretycznie i utrzymywać przy życiu w stałej puli. Przypuszczam, że użycie char[]zabrania tej możliwości.

Sean Owen
źródło
4
Nie powiedziałbym, że problem polega na tym, że twoje referencje „uciekną”. Po prostu ciągi pozostaną niezmodyfikowane w pamięci przez dodatkowy czas, podczas gdy char[]można je modyfikować, a następnie nie ma znaczenia, czy zostaną zebrane, czy nie. A ponieważ internowanie ciągów musi być wykonywane jawnie w przypadku literałów nieliteralnych, to tak samo, jak mówienie, że do char[]pola może odwoływać się pole statyczne.
Groo,
1
czy hasło nie jest zapisane w pamięci jako ciąg znaków z formularza?
Dawesi
66

Odpowiedź została już udzielona, ​​ale chciałbym podzielić się odkrytym niedawno problemem ze standardowymi bibliotekami Java. Choć teraz bardzo dbają o to, aby char[]wszędzie zastępować ciągi hasła (co oczywiście jest dobrą rzeczą), inne ważne dla bezpieczeństwa dane wydają się być pomijane, jeśli chodzi o usuwanie ich z pamięci.

Mam na myśli np . Klasę PrivateKey . Rozważ scenariusz, w którym można załadować prywatny klucz RSA z pliku PKCS # 12, używając go do wykonania pewnych operacji. Teraz w tym przypadku samo wąchanie hasła nie pomoże, o ile fizyczny dostęp do pliku kluczy jest odpowiednio ograniczony. Jako atakujący byłoby znacznie lepiej, gdybyś zdobył klucz bezpośrednio zamiast hasła. Pożądane informacje mogą być wyciekiem rozmaitości, zrzuty rdzenia, sesja debugowania lub pliki wymiany to tylko niektóre przykłady.

I jak się okazuje, nie ma nic, co pozwala usunąć prywatne informacje PrivateKeyz pamięci, ponieważ nie ma interfejsu API, który pozwala wyczyścić bajty tworzące odpowiednią informację.

Jest to zła sytuacja, ponieważ niniejszy dokument opisuje, w jaki sposób tę okoliczność można potencjalnie wykorzystać.

Na przykład biblioteka OpenSSL zastępuje krytyczne sekcje pamięci przed zwolnieniem kluczy prywatnych. Ponieważ Java jest gromadzona w pamięci, potrzebowalibyśmy jawnych metod czyszczenia i unieważniania prywatnych informacji dotyczących kluczy Java, które należy zastosować natychmiast po użyciu klucza.

wyryć
źródło
Jednym ze sposobów jest użycie implementacji PrivateKey, która tak naprawdę nie ładuje swojej prywatnej zawartości do pamięci: na przykład za pomocą tokena sprzętowego PKCS # 11. Być może implementacja oprogramowania PKCS # 11 może zająć się ręcznym czyszczeniem pamięci. Być może PKCS11lepsze jest użycie czegoś takiego jak sklep NSS (który dzieli większość swojej implementacji z typem sklepu w Javie). KeychainStore(OSX magazynów kluczy) ładunki pełną zawartość klucza prywatnego do swoich PrivateKeywystąpień, ale to nie powinno być konieczne. (Nie jestem pewien, co WINDOWS-MYrobi KeyStore w systemie Windows.)
Bruno
@Bruno Oczywiście, tokeny sprzętowe nie cierpią z tego powodu, ale co z sytuacjami, w których jesteś mniej lub bardziej zmuszony do używania klucza oprogramowania? Nie każde wdrożenie ma budżet na HSM. Sklepy kluczy oprogramowania będą w pewnym momencie musiały załadować klucz do pamięci, więc IMO powinniśmy przynajmniej mieć możliwość ponownego wyczyszczenia pamięci do woli.
wytłoczono
Absolutnie zastanawiałem się tylko, czy niektóre implementacje oprogramowania równoważne HSM mogą zachowywać się lepiej pod względem czyszczenia pamięci. Na przykład, gdy używasz uwierzytelniania klienta w Safari / OSX, proces Safari nigdy nie widzi klucza prywatnego, podstawowa biblioteka SSL dostarczona przez system operacyjny komunikuje się bezpośrednio z demonem bezpieczeństwa, który monituje użytkownika o użycie klucza z pęku kluczy. Chociaż wszystko to odbywa się w oprogramowaniu, podobna separacja może pomóc, jeśli podpisywanie zostanie przekazane innej jednostce (nawet opartej na oprogramowaniu), która lepiej rozładuje lub wyczyści pamięć.
Bruno,
@Bruno: Ciekawy pomysł, dodatkowa warstwa pośrednia, która zajmuje się czyszczeniem pamięci, rzeczywiście rozwiązałaby to w sposób przejrzysty. Pisanie opakowania PKCS # 11 dla magazynu kluczy oprogramowania może już załatwić sprawę?
wytłoczono
51

Jak stwierdza Jon Skeet, nie ma innego wyjścia niż użycie odbicia.

Jeśli jednak możesz się zastanowić, możesz to zrobić.

public static void main(String[] args) {
    System.out.println("please enter a password");
    // don't actually do this, this is an example only.
    Scanner in = new Scanner(System.in);
    String password = in.nextLine();
    usePassword(password);

    clearString(password);

    System.out.println("password: '" + password + "'");
}

private static void usePassword(String password) {

}

private static void clearString(String password) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(password);
        Arrays.fill(chars, '*');
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

po uruchomieniu

please enter a password
hello world
password: '***********'

Uwaga: jeśli char [] String [] został skopiowany jako część cyklu GC, istnieje szansa, że ​​poprzednia kopia jest gdzieś w pamięci.

Ta stara kopia nie pojawi się na zrzucie sterty, ale jeśli masz bezpośredni dostęp do surowej pamięci procesu, możesz ją zobaczyć. Zasadniczo powinieneś unikać osób mających taki dostęp.

Peter Lawrey
źródło
2
Lepiej zrobić coś, aby zapobiec wydrukowaniu długości hasła, które otrzymujemy '***********'.
chux - Przywróć Monikę
@chux możesz użyć znaku o zerowej szerokości, chociaż może to być bardziej mylące niż przydatne. Nie można zmienić długości tablicy znaków bez użycia opcji Niebezpieczne. ;)
Peter Lawrey
5
Z powodu deduplikacji napisów w Javie 8, myślę, że zrobienie czegoś takiego może być dość destrukcyjne ... możesz skończyć czyszczeniem innych ciągów w swoim programie, które przez przypadek miały tę samą wartość hasła String. Mało prawdopodobne, ale możliwe ...
zacisk
1
@PeterLawrey Musi być włączony z argumentem JVM, ale już tam jest. Możesz przeczytać o tym tutaj: blog.codecentric.de/en/2014/08/…
jamp
1
Istnieje duża szansa, że ​​hasło nadal znajduje się w Scannerwewnętrznym buforze, a ponieważ go nie użyłeś System.console().readPassword(), w czytelnej formie w oknie konsoli. Jednak w najbardziej praktycznych przypadkach użycia usePasswordfaktycznym problemem jest czas wykonania. Na przykład podczas nawiązywania połączenia z inną maszyną zajmuje to dużo czasu i informuje atakującego, że jest to właściwy czas, aby wyszukać hasło na stosie. Jedynym rozwiązaniem jest uniemożliwienie atakującym odczytania pamięci sterty…
Holger
42

Oto wszystkie powody, dla których dla hasła należy wybrać tablicę char [] zamiast String .

1. Ponieważ w Javie ciągi są niezmienne, jeśli przechowujesz hasło jako zwykły tekst, będzie ono dostępne w pamięci do momentu wyczyszczenia go przez moduł zbierający śmieci, a ponieważ ciąg jest używany w puli ciągów do ponownego użycia, istnieje duża szansa, że ​​będzie pozostają w pamięci przez długi czas, co stanowi zagrożenie dla bezpieczeństwa.

Ponieważ każdy, kto ma dostęp do zrzutu pamięci, może znaleźć hasło w postaci zwykłego tekstu, to kolejny powód, dla którego zawsze należy używać hasła szyfrowanego zamiast zwykłego tekstu. Ponieważ ciągi są niezmienne, nie ma możliwości zmiany zawartości ciągów, ponieważ każda zmiana spowoduje powstanie nowego ciągu, a jeśli użyjesz znaku char [], możesz ustawić wszystkie elementy jako puste lub zerowe. Przechowywanie hasła w tablicy znaków wyraźnie zmniejsza ryzyko związane z kradzieżą hasła.

2. Sama Java zaleca stosowanie metody getPassword () JPasswordField, która zwraca char [], zamiast przestarzałej metody getText (), która zwraca hasła w postaci czystego tekstu, podając względy bezpieczeństwa. Warto postępować zgodnie z radami zespołu Java i stosować się do standardów, a nie przeciwstawiać się im.

3. W przypadku Ciągu zawsze istnieje ryzyko drukowania zwykłego tekstu w pliku dziennika lub konsoli, ale jeśli użyjesz Tablicy, nie wydrukujesz zawartości tablicy, ale zamiast tego zostanie wydrukowana jej lokalizacja pamięci. Choć nie jest to prawdziwy powód, nadal ma sens.

String strPassword="Unknown";
char[] charPassword= new char[]{'U','n','k','w','o','n'};
System.out.println("String password: " + strPassword);
System.out.println("Character password: " + charPassword);

String password: Unknown
Character password: [C@110b053

Referencje z tego bloga . Mam nadzieję, że to pomoże.

Istota ludzka
źródło
10
To jest zbędne. Ta odpowiedź jest dokładną wersją odpowiedzi napisanej przez @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Nie kopiuj ani nie kopiuj dwukrotnie tej samej odpowiedzi.
Lucky
Jaka jest różnica między 1.) System.out.println („Hasło postaci:” + charPassword); oraz 2.) System.out.println (charPassword); Ponieważ daje to samo „nieznane” co wynik.
Vaibhav_Sharma
3
@Lucky Niestety starsza odpowiedź, do której prowadziłeś link, była plagiatem z tego samego bloga co ta odpowiedź, a teraz została usunięta. Zobacz meta.stackoverflow.com/questions/389144/… . Ta odpowiedź po prostu wycięła i wkleiła z tego samego bloga i nic nie dodała, więc powinien to być po prostu komentarz prowadzący do oryginalnego źródła.
skomisa
41

Edycja: Wracając do tej odpowiedzi po roku badań nad bezpieczeństwem, zdaję sobie sprawę, że ma to raczej niefortunną implikację, że kiedykolwiek faktycznie porównałbyś hasła w postaci zwykłego tekstu. Proszę nie. Użyj bezpiecznego skrótu jednokierunkowego z solą i rozsądną liczbą iteracji . Zastanów się nad użyciem biblioteki: trudno jest to naprawić!

Oryginalna odpowiedź: co z faktem, że String.equals () używa oceny zwarcia , a zatem jest podatny na atak czasowy? Może to być mało prawdopodobne, ale teoretycznie możesz porównać czas porównania hasła w celu ustalenia prawidłowej sekwencji znaków.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // Quits here if Strings are different lengths.
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // Quits here at first different character.
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Więcej zasobów na temat ataków w czasie:

Teoria grafów
źródło
Ale może to być również w porównaniu char [], gdzieś my robilibyśmy to samo również w przypadku sprawdzania poprawności hasła. więc w jaki sposób char [] jest lepszy niż string?
Mohit Kanwar
3
Masz absolutną rację, błąd można popełnić w obu kierunkach. Najważniejsza jest tutaj znajomość problemu, biorąc pod uwagę, że nie ma wyraźnej metody porównywania haseł w Javie dla haseł opartych na ciągach znaków lub znaków char []. Powiedziałbym, że pokusa, aby użyć porównywania () dla Strings, jest dobrym powodem, aby przejść do char []. W ten sposób masz przynajmniej kontrolę nad sposobem przeprowadzania porównania (bez przedłużania String, co jest imo bólu).
Teoria wykresów
Poza tym porównywanie haseł w postaci zwykłego tekstu nie jest słuszne zresztą, pokusa, by wykorzystać Arrays.equalsdla char[]jest tak wysokie, jak na String.equals. Jeśli ktoś się tym przejmował, istniała dedykowana klasa kluczy enkapsulująca rzeczywiste hasło i zajmująca się problemami - och, czekaj, prawdziwe pakiety bezpieczeństwa mają dedykowane klasy kluczy, to pytania i odpowiedzi dotyczą tylko nawyku poza nimi, np. JPasswordFieldUżywania char[]zamiast String(gdzie i byte[]tak wykorzystują faktyczne algorytmy ).
Holger,
Oprogramowanie związane z bezpieczeństwem powinno zrobić coś takiego sleep(secureRandom.nextInt())przed odrzuceniem próby zalogowania, co nie tylko eliminuje możliwość ataków w czasie, ale także przeciwdziała próbom użycia siły.
Holger,
36

Nie ma nic, co daje tablica char vs String, chyba że wyczyścisz ją ręcznie po użyciu, a ja nie widziałem, żeby ktoś to robił. Więc dla mnie preferencja char [] vs String jest nieco przesadzona.

Spójrz na szeroko używaną bibliotekę Spring Security tutaj i zadaj sobie pytanie - czy faceci Spring Security nie są niekompetentni lub hasła char [] po prostu nie mają większego sensu. Gdy jakiś paskudny haker pobierze zrzuty pamięci RAM, upewnij się, że otrzyma wszystkie hasła, nawet jeśli używasz zaawansowanych sposobów ich ukrywania.

Jednak Java zmienia się cały czas, a niektóre przerażające funkcje, takie jak funkcja deduplikacji ciągów w Javie 8, mogą internować obiekty String bez twojej wiedzy. Ale to inna rozmowa.

Oleg Mikheev
źródło
Dlaczego deduplikacja ciągów jest przerażająca? Ma to zastosowanie tylko wtedy, gdy istnieją co najmniej dwa ciągi o tej samej zawartości, więc jakie niebezpieczeństwo mogłoby powstać, gdyby te dwa już identyczne ciągi miały tę samą tablicę? Albo zapytajmy na odwrót: jeśli nie ma deduplikacji ciągów, jaka korzyść wynika z faktu, że oba ciągi mają odrębną tablicę (o tej samej zawartości)? W obu przypadkach tablica tych treści będzie żyła przynajmniej tak długo, jak długo będzie żył Sznur tej zawartości…
Holger
@Holger wszystko, co wymyka się spod kontroli, stanowi potencjalne ryzyko ... na przykład, jeśli dwóch użytkowników ma to samo hasło, ta wspaniała funkcja zapisze je wszystkie w jednym znaku [], dzięki czemu będzie oczywiste, że są takie same, nie jestem pewien, czy to ogromne ryzyko, ale nadal
Oleg Mikheev
Jeśli masz dostęp do pamięci sterty i obu instancji ciągów, nie ma znaczenia, czy ciągi wskazują tę samą tablicę, czy dwie tablice tej samej zawartości, każda jest łatwa do znalezienia. Zwłaszcza, że ​​i tak nie ma to znaczenia. Jeśli jesteś w tym momencie, łapiesz oba hasła, identyczne czy nie. Rzeczywisty błąd polega na używaniu haseł w postaci jawnego tekstu zamiast solonych skrótów.
Holger,
@Holger, aby zweryfikować hasło, musi przez pewien czas pozostawać w pamięci tekstem jawnym, 10 ms, nawet jeśli ma to na celu jedynie utworzenie z niego solonego skrótu. Następnie, jeśli zdarzy się, że w pamięci przechowywane są dwa identyczne hasła nawet przez 10 ms, może dojść do deduplikacji. Jeśli naprawdę zawiera łańcuchy, są one przechowywane w pamięci przez znacznie dłuższy czas. System, który nie uruchamia się ponownie przez miesiące, zbierze wiele z nich. Tylko teoretyzowanie.
Oleg Mikheev
Wygląda na to, że masz fundamentalne nieporozumienie dotyczące deduplikacji ciągów. Nie „internuje łańcuchów”, a jedynie pozwala, aby łańcuchy o tej samej zawartości wskazywały tę samą tablicę, co faktycznie zmniejsza liczbę instancji tablicy zawierających hasło w postaci zwykłego tekstu, ponieważ wszystkie instancje tablicy oprócz jednej można odzyskać i zastąpić przez inne przedmioty natychmiast. Ciągi te są nadal zbierane, jak każdy inny ciąg. Może to pomaga, jeśli zrozumiesz, że usuwanie duplikatów jest faktycznie wykonywane przez moduł wyrzucania elementów bezużytecznych, dla łańcuchów, które przetrwały tylko kilka cykli GC.
Holger
30

Ciągi są niezmienne i nie można ich zmienić po ich utworzeniu. Utworzenie hasła jako ciągu pozostawi zbłąkane odwołania do hasła na stercie lub w puli ciągów. Teraz, jeśli ktoś zrobi kupę procesu Java i dokładnie przejrzy, może odgadnąć hasła. Oczywiście te nieużywane ciągi będą zbierane śmieci, ale zależy to od momentu uruchomienia GC.

Z drugiej strony char [] są zmienne, gdy tylko uwierzytelnienie zostanie wykonane, możesz zastąpić je dowolnymi znakami, takimi jak wszystkie litery M lub odwrotne ukośniki. Teraz nawet jeśli ktoś zrzuci stos, może nie być w stanie uzyskać haseł, które nie są aktualnie używane. Daje to większą kontrolę w tym sensie, jak samodzielne czyszczenie zawartości obiektu w porównaniu z oczekiwaniem na GC.

Maniak
źródło
Zostaną one zapisane w GC tylko wtedy, gdy JVM ma> 1,6. Przed 1.7 wszystkie ciągi były przechowywane w permgen.
avgvstvs
@avgvstvs: „wszystkie ciągi zostały zapisane w permgen” jest po prostu błędne. Przechowywano tam tylko wewnętrzne łańcuchy, a jeśli nie pochodziły z literałów łańcuchowych, do których odwołuje się kod, to nadal były śmieciami. Po prostu o tym pomyśl. Jeśli ciągi zasadniczo nigdy nie były GCed w JVM przed wersją 1.7, to jak każda aplikacja Java mogłaby przetrwać dłużej niż kilka minut?
Holger,
@Holger To jest fałsz. Internowane stringsORAZ Stringpula (pula poprzednio używanych ciągów) ZARÓWNO była przechowywana w Permgen przed 1.7. Zobacz także sekcję 5.1: docs.oracle.com/javase/specs/jvms/se6/html/ ... JVM zawsze sprawdzał, Stringsczy jest to ta sama wartość referencyjna, i wzywałbyString.intern() CIEBIE. Rezultat był taki, że za każdym razem, gdy JVM wykrył identyczne ciągi w constant_poollub stosie, przenosił je do permgenu. Pracowałem nad kilkoma aplikacjami z „pełzającym permgenem” do 1.7. To był prawdziwy problem.
avgvstvs
Podsumowując: do 1.7 łańcuchy zaczynały się na stosie, kiedy były używane, były umieszczane w tym, constant_poolktóry znajdował się w permgen, a następnie, jeśli łańcuch był używany więcej niż jeden raz, byłby internowany.
avgvstvs
@avgvstvs: Nie ma „puli wcześniej używanych ciągów”. Rzucasz razem zupełnie różne rzeczy. Istnieje jedna pula ciągów wykonawczych zawierająca literały ciągów i jawnie internowany ciąg znaków, ale żadna inna. Każda klasa ma stałą pulę zawierającą stałe czasu kompilacji . Te ciągi są automatycznie dodawane do puli środowiska wykonawczego, ale tylko te , nie każdy ciąg.
Holger,
21

Ciąg jest niezmienny i trafia do puli ciągów. Raz napisanego tekstu nie można go zastąpić.

char[] to tablica, którą należy zastąpić po użyciu hasła, i tak należy to zrobić:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {

Arrays.fill(pass, '0');
}

Jednym ze scenariuszy, w którym osoba atakująca może z niego skorzystać, jest zrzut awaryjny - gdy JVM ulegnie awarii i wygeneruje zrzut pamięci - zobaczysz hasło.

Nie musi to być złośliwy zewnętrzny atakujący. Może to być użytkownik pomocy technicznej, który ma dostęp do serwera w celach monitorowania. Mógł zajrzeć do zrzutu awaryjnego i znaleźć hasła.

ACV
źródło
2
ch = null; nie możesz tego zrobić
Jugerten,
Ale czy request.getPassword()już nie tworzy ciągu i nie dodaje go do puli?
Tvde1,
1
ch = '0'zmienia zmienną lokalną ch; nie ma to wpływu na tablicę. W każdym razie twój przykład jest bezcelowy, zaczynasz od instancji ciągu, którą wywołujesz toCharArray(), tworząc nową tablicę, a nawet gdy poprawnie nadpisujesz nową tablicę, nie zmienia ona instancji ciągu, więc nie ma przewagi nad samym ciągiem instancja.
Holger
@Holger dzięki. Poprawiono kod czyszczenia tablicy char.
ACV
3
Możesz po prostu użyćArrays.fill(pass, '0');
Holger
18

Krótka i bezpośrednia odpowiedź byłaby możliwa, ponieważ char[]można ją modyfikować, a Stringobiekty nie.

Stringsw Javie są niezmienne obiekty. Dlatego nie można ich modyfikować po utworzeniu, dlatego jedynym sposobem na usunięcie ich zawartości z pamięci jest zebranie śmieci. Dopiero wtedy pamięć uwolniona przez obiekt będzie mogła zostać nadpisana, a dane znikną.

Teraz wyrzucanie elementów bezużytecznych w Javie nie odbywa się w żadnym gwarantowanym okresie. StringMoże zatem utrzymywać w pamięci przez długi czas, a jeśli proces ulega awarii w tym czasie, treść napisu może skończyć się w wysypisko pamięci lub jakiegoś dziennika.

Dzięki tablicy znaków możesz odczytać hasło, zakończyć pracę z nim tak szybko, jak to możliwe, a następnie natychmiast zmienić jego zawartość.

Pritam Banerjee
źródło
@fallenidol Wcale nie. Przeczytaj uważnie, a znajdziesz różnice.
Pritam Banerjee,
12

Ciąg w java jest niezmienny. Tak więc, gdy ciąg zostanie utworzony, pozostanie w pamięci, dopóki nie zostanie wyrzucony. Tak więc każdy, kto ma dostęp do pamięci, może odczytać wartość ciągu.
Jeśli wartość ciągu zostanie zmodyfikowana, spowoduje to utworzenie nowego ciągu. Tak więc zarówno oryginalna, jak i zmodyfikowana wartość pozostają w pamięci, dopóki nie zostaną wyrzucone.

Za pomocą tablicy znaków zawartość tablicy można modyfikować lub usuwać po podaniu celu hasła. Oryginalna zawartość tablicy nie zostanie odnaleziona w pamięci po jej modyfikacji, a nawet zanim rozpocznie się odśmiecanie.

Ze względów bezpieczeństwa lepiej przechowywać hasło jako tablicę znaków.

Saathvik
źródło
2

Można dyskutować, czy w tym celu należy użyć String, czy Char [], ponieważ oba mają swoje zalety i wady. To zależy od potrzeb użytkownika.

Ponieważ ciągi w Javie są niezmienne, ilekroć ktoś próbuje manipulować ciągiem, tworzy nowy obiekt, a istniejący ciąg pozostaje nienaruszony. Może to być postrzegane jako zaleta przechowywania hasła jako ciągu, ale obiekt pozostaje w pamięci nawet po użyciu. Jeśli więc ktoś w jakiś sposób uzyskał lokalizację pamięci obiektu, osoba ta może łatwo prześledzić hasło przechowywane w tej lokalizacji.

Char [] jest zmienny, ale ma tę zaletę, że po jego użyciu programista może jawnie wyczyścić tablicę lub zastąpić wartości. Kiedy jest już używany, jest czyszczony i nikt nigdy nie może wiedzieć o przechowywanych informacjach.

W oparciu o powyższe okoliczności można dowiedzieć się, czy wybrać String, czy Char [], aby spełnić ich wymagania.

Neeraj
źródło
0

Ciąg sprawy:

    String password = "ill stay in StringPool after Death !!!";
    // some long code goes
    // ...Now I want to remove traces of password
    password = null;
    password = "";
    // above attempts wil change value of password
    // but the actual password can be traced from String pool through memory dump, if not garbage collected

Sprawa CHAR ARRAY:

    char[] passArray = {'p','a','s','s','w','o','r','d'};
    // some long code goes
    // ...Now I want to remove traces of password
    for (int i=0; i<passArray.length;i++){
        passArray[i] = 'x';
    }
    // Now you ACTUALLY DESTROYED traces of password form memory
Aditya Rewari
źródło