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.
char[]
lokalizacjach odcina tę linię ataku, co jest niemożliwe podczas korzystaniaString
.Podczas gdy inne sugestie wydają się aktualne, istnieje jeszcze jeden dobry powód. W przypadku zwykłego
String
masz 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:
Wydruki:
źródło
toString
toclassname@hashcode
.[C
reprezentujechar[]
, reszta to szesnastkowy kod skrótu.Password
do tego typ zajęć. Jest mniej niejasny i trudniej gdzieś przypadkowo przejść.Aby zacytować oficjalny dokument, przewodnik po architekturze Java Cryptography Architecture mówi o haseł
char[]
vs.String
hasłach (o szyfrowaniu opartym na hasłach, ale bardziej ogólnie dotyczy haseł):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):
źródło
String
jest całkiem dobrze zrozumiany i jak zachowuje się w JVM ... istnieją dobre powody, aby używać gochar[]
zamiastString
zajmować się hasłami w bezpieczny sposób.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.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ślichar[]
jest używane, po usunięciu danych z zer, hasło jest bezpieczne.źródło
HttpServletRequest
obiektu 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.)intern()
dowolnych ciągów. Ale masz rację, ponieważString
instancje istnieją przede wszystkim (dopóki nie zostaną zebrane), achar[]
później przekształcenie ich w tablice nie zmieni tego.new String()
lubStringBuilder.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.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…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.
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.
źródło
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 wString
ten sposób edytować wewnętrznej wartości .Ale to nie jest dobra odpowiedź; dlaczego nie upewnić się, że odniesienie do
char[]
lubString
nie ucieka? Wtedy nie ma problemu z bezpieczeństwem. Ale chodzi o to, żeString
przedmioty możnaintern()
edytować teoretycznie i utrzymywać przy życiu w stałej puli. Przypuszczam, że użyciechar[]
zabrania tej możliwości.źródło
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 dochar[]
pola może odwoływać się pole statyczne.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
PrivateKey
z 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.
źródło
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żePKCS11
lepsze 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 swoichPrivateKey
wystąpień, ale to nie powinno być konieczne. (Nie jestem pewien, coWINDOWS-MY
robi KeyStore w systemie Windows.)Jak stwierdza Jon Skeet, nie ma innego wyjścia niż użycie odbicia.
Jeśli jednak możesz się zastanowić, możesz to zrobić.
po uruchomieniu
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.
źródło
'***********'
.Scanner
wewnętrznym buforze, a ponieważ go nie użyłeśSystem.console().readPassword()
, w czytelnej formie w oknie konsoli. Jednak w najbardziej praktycznych przypadkach użyciausePassword
faktycznym 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…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.
Referencje z tego bloga . Mam nadzieję, że to pomoże.
źródło
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.
Więcej zasobów na temat ataków w czasie:
źródło
Arrays.equals
dlachar[]
jest tak wysokie, jak naString.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.JPasswordField
Używaniachar[]
zamiastString
(gdzie ibyte[]
tak wykorzystują faktyczne algorytmy ).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.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.
źródło
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.
źródło
strings
ORAZString
pula (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ł,Strings
czy 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 wconstant_pool
lub stosie, przenosił je do permgenu. Pracowałem nad kilkoma aplikacjami z „pełzającym permgenem” do 1.7. To był prawdziwy problem.constant_pool
który znajdował się w permgen, a następnie, jeśli łańcuch był używany więcej niż jeden raz, byłby internowany.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ć: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.
źródło
request.getPassword()
już nie tworzy ciągu i nie dodaje go do puli?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łujesztoCharArray()
, 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.Arrays.fill(pass, '0');
Krótka i bezpośrednia odpowiedź byłaby możliwa, ponieważ
char[]
można ją modyfikować, aString
obiekty nie.Strings
w 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.
String
Moż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ść.
źródło
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.
źródło
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.
źródło
Ciąg sprawy:
Sprawa CHAR ARRAY:
źródło