Perché char [] è preferito su String per le password?


3422

In Swing, il campo password ha un metodo getPassword()(restituisce char[]) invece del solito metodo getText()(restituisce String). Allo stesso modo, ho trovato un suggerimento di non utilizzare Stringper gestire le password.

Perché Stringrappresenta una minaccia per la sicurezza quando si tratta di password? Sembra scomodo da usare char[].

Risposte:


4290

Le stringhe sono immutabili . Ciò significa che, una volta creato String, se un altro processo può scaricare la memoria, non c'è modo (a parte la riflessione ) di liberarsi dei dati prima della garbage collection .

Con un array, puoi cancellare esplicitamente i dati dopo aver finito con esso. Puoi sovrascrivere l'array con qualsiasi cosa ti piaccia e la password non sarà presente in nessun punto del sistema, nemmeno prima della garbage collection.

Quindi sì, questo è un problema di sicurezza, ma anche l'utilizzochar[] riduce solo la finestra di opportunità per un attaccante, ed è solo per questo specifico tipo di attacco.

Come notato nei commenti, è possibile che gli array che vengono spostati dal Garbage Collector lascino in memoria copie vaganti dei dati. Credo che questo sia specifico dell'implementazione: il Garbage Collector potrebbe cancellare tutta la memoria mentre procede, per evitare questo genere di cose. Anche se lo fa, c'è ancora il tempo durante il quale char[]contiene i personaggi reali come una finestra di attacco.


3
Se un processo ha accesso alla memoria della tua applicazione, allora è già una violazione della sicurezza, giusto?
Yeti,

5
@Yeti: Sì, ma non è come se fosse in bianco e nero. Se riescono a ottenere solo un'istantanea della memoria, si desidera ridurre la quantità di danni che tale istantanea può fare o ridurre la finestra durante la quale è possibile scattare un'istantanea davvero seria.
Jon Skeet,

11
Un metodo di attacco comune è quello di eseguire un processo che alloca molta memoria e quindi la scansiona alla ricerca di dati utili rimasti come le password. Il processo non ha bisogno di alcun accesso magico allo spazio di memoria di un altro processo; si basa solo sulla morte di altri processi senza prima cancellare i dati sensibili e anche il sistema operativo non cancella la memoria (o i buffer di pagina) prima di renderlo disponibile per un nuovo processo. La cancellazione delle password archiviate in char[]posizioni interrompe quella linea di attacco, cosa impossibile durante l'utilizzo String.
Ted Hopp,

Se il sistema operativo non cancella la memoria prima di assegnarla a un altro processo, il sistema operativo presenta importanti problemi di sicurezza! Tuttavia, tecnicamente la cancellazione viene spesso eseguita con trucchi in modalità protetta e se la CPU è rotta (ad esempio Intel Meldown) è ancora possibile leggere vecchi contenuti di memoria.
Mikko Rantalainen, il

1222

Mentre altri suggerimenti qui sembrano validi, c'è un'altra buona ragione. Con Plain Stringhai molte più possibilità di stampare accidentalmente la password su registri , monitor o altri luoghi non sicuri. char[]è meno vulnerabile.

Considera questo:

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

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

stampe:

String: Password
Array: [C@5829428e

40
@voo, ma dubito che accederai tramite la scrittura diretta allo streaming e alla concatenazione. il framework di logging trasformerebbe il char [] in un buon output
bestsss

41
@ Thr4wn L'implementazione predefinita di toStringè classname@hashcode. [Crappresenta char[], il resto è un codice hash esadecimale.
Konrad Garus,

15
Idea interessante. Vorrei sottolineare che ciò non traspone alla Scala che ha una stringa significativa per gli array.
Mauhiz,

37
Scriverei un Passwordtipo di classe per questo. È meno oscuro e più difficile passare accidentalmente da qualche parte.
bendaggio

8
Perché qualcuno dovrebbe presumere che l'array di caratteri verrà lanciato come un oggetto? Non sono sicuro di capire perché a tutti piace questa risposta. Supponiamo di aver fatto questo: System.out.println ("Password" .toCharArray ());
GC_

680

Per citare un documento ufficiale, la guida Java Cryptography Architecture dice questo su char[]vs. Stringpassword (sulla crittografia basata su password, ma questo è più in generale sulle password ovviamente):

Sembrerebbe logico raccogliere e archiviare la password in un oggetto di tipo java.lang.String. Tuttavia, ecco l'avvertenza: Objecti tipi di s Stringsono immutabili, ovvero non esistono metodi definiti che consentono di modificare (sovrascrivere) o azzerare il contenuto di un String post-utilizzo. Questa funzione rende gli Stringoggetti non idonei per la memorizzazione di informazioni sensibili alla sicurezza come le password degli utenti. Dovresti sempre raccogliere e archiviare le informazioni sensibili alla sicurezza in un chararray.

La Linea guida 2-2 delle Linee guida per la codifica sicura per il linguaggio di programmazione Java, versione 4.0 dice anche qualcosa di simile (sebbene sia originariamente nel contesto della registrazione):

Linea guida 2-2: non registrare informazioni altamente sensibili

Alcune informazioni, come i numeri di previdenza sociale (SSN) e le password, sono altamente sensibili. Queste informazioni non devono essere conservate più a lungo del necessario né dove possono essere visualizzate, neppure dagli amministratori. Ad esempio, non dovrebbe essere inviato ai file di registro e la sua presenza non dovrebbe essere rilevabile attraverso le ricerche. Alcuni dati transitori possono essere conservati in strutture di dati mutabili, come ad esempio array di caratteri, e cancellati immediatamente dopo l'uso. L'eliminazione delle strutture di dati ha ridotto l'efficacia sui tipici sistemi di runtime Java poiché gli oggetti vengono spostati in memoria in modo trasparente al programmatore.

Questa linea guida ha anche implicazioni per l'implementazione e l'uso di librerie di livello inferiore che non hanno una conoscenza semantica dei dati con cui hanno a che fare. Ad esempio, una libreria di analisi delle stringhe di basso livello può registrare il testo su cui lavora. Un'applicazione può analizzare un SSN con la libreria. Ciò crea una situazione in cui gli SSN sono disponibili per gli amministratori con accesso ai file di registro.


4
questo è esattamente il riferimento imperfetto / falso di cui parlo sotto la risposta di Jon, è una fonte ben nota con molte critiche.
bestsss

37
@bestass puoi per favore citare anche un riferimento?
user961954,

10
@bestass mi dispiace, ma Stringè abbastanza ben compreso e come si comporta nella JVM ... ci sono buoni motivi da usare char[]al posto di Stringquando si tratta di password in modo sicuro.
SnakeDoc,

3
ancora una volta anche se la password viene passata come stringa dal browser alla richiesta come 'stringa' non char? quindi, qualunque cosa tu faccia, è una stringa, a che punto deve essere attivata e scartata, mai memorizzata?
Dawesi,

3
@Dawesi - At which point- è specifico per l'applicazione, ma la regola generale è di farlo non appena si ottiene una sospensione di qualcosa che dovrebbe essere una password (testo normale o altro). Lo ottieni dal browser come parte di una richiesta HTTP, ad esempio. Non puoi controllare la consegna, ma puoi controllare il tuo spazio di archiviazione, quindi non appena lo ottieni, mettilo in un carattere [], fai quello che devi fare con esso, quindi imposta tutto su '0' e lascia che gc reclamarlo.
luis.espinal,

354

Le matrici di caratteri ( char[]) possono essere cancellate dopo l'uso impostando ogni carattere su zero e Stringhe no. Se qualcuno può in qualche modo vedere l'immagine di memoria, può vedere una password in testo semplice se vengono utilizzate le stringhe, ma se char[]viene utilizzata, dopo aver eliminato i dati con 0, la password è sicura.


13
Non sicuro per impostazione predefinita. Se stiamo parlando di un'applicazione web, la maggior parte dei container web passerà la password HttpServletRequestnell'oggetto in testo normale. Se la versione JVM è 1.6 o precedente, sarà nello spazio permgen. Se è in 1.7, sarà comunque leggibile fino a quando non verrà raccolto. (Ogni volta che lo è.)
avgvstvs,

4
@avgvstvs: le stringhe non vengono spostate automaticamente nello spazio permgen, questo vale solo per le stringhe internate. Oltre a ciò, lo spazio permgen è anche soggetto alla raccolta dei rifiuti, solo a un ritmo inferiore. Il vero problema con lo spazio permgen è la sua dimensione fissa, che è esattamente il motivo per cui nessuno dovrebbe chiamare senza intern()pensare stringhe arbitrarie. Ma hai ragione nel fatto che le Stringistanze esistono in primo luogo (fino a quando non vengono raccolte) e trasformarle in char[]array in seguito non le modifica.
Holger

3
@Holger vedi docs.oracle.com/javase/specs/jvms/se6/html/… "In caso contrario, viene creata una nuova istanza della classe String contenente la sequenza di caratteri Unicode fornita dalla struttura CONSTANT_String_info; tale istanza della classe è il risultato di derivazione letterale stringa. Infine, viene invocato il metodo intern della nuova istanza String. " In 1.6, la JVM chiamerebbe intern per te quando rileva sequenze identiche.
avgvstvs

3
@Holger, hai ragione, ho combinato il pool costante e il pool di stringhe, ma è anche falso che lo spazio permgen si applicava solo alle stringhe internate. Prima dell'1.7, sia constant_pool che string_pool risiedevano nello spazio permgen. Ciò significa che l'unica classe di stringhe allocata all'heap era come hai detto tu, new String()oppure StringBuilder.toString() ho gestito le applicazioni con molte costanti di stringa e di conseguenza abbiamo avuto un sacco di creep di permgen. Fino all'1.7.
avgvstvs

5
@avgvstvs: bene, le costanti di stringa sono, come i mandati di JLS, sempre internate, quindi l'affermazione che le stringhe internate sono finite nello spazio permgen, applicate implicitamente alle costanti di stringa. L'unica differenza è che le costanti di stringa sono state create nello spazio permgen in primo luogo, mentre la chiamata intern()a una stringa arbitraria potrebbe causare l'allocazione di una stringa equivalente nello spazio permgen. Quest'ultimo potrebbe ottenere GC, se non ci fosse una stringa letterale dello stesso contenuto che condivideva quell'oggetto ...
Holger

220

Alcune persone credono che sia necessario sovrascrivere la memoria utilizzata per archiviare la password quando non è più necessaria. Ciò riduce la finestra temporale in cui un utente malintenzionato deve leggere la password dal sistema e ignora completamente il fatto che l'attaccante ha già bisogno di accesso sufficiente per dirottare la memoria JVM per farlo. Un aggressore con così tanto accesso può catturare i tuoi eventi chiave rendendolo completamente inutile (AFAIK, quindi per favore correggimi se sbaglio).

Aggiornare

Grazie ai commenti devo aggiornare la mia risposta. Apparentemente ci sono due casi in cui questo può aggiungere un (molto) lieve miglioramento della sicurezza in quanto riduce il tempo in cui una password potrebbe atterrare sul disco rigido. Penso comunque che sia eccessivo per la maggior parte dei casi d'uso.

  • Il tuo sistema di destinazione potrebbe essere configurato in modo errato o devi presumere che lo sia e devi essere paranoico sui dump principali (può essere valido se i sistemi non sono gestiti da un amministratore).
  • Il tuo software deve essere eccessivamente paranoico per impedire la perdita di dati con l'attaccante che ottiene l'accesso all'hardware, usando cose come TrueCrypt (fuori produzione), VeraCrypt o CipherShed .

Se possibile, disabilitare i core dump e il file di scambio si occuperebbe di entrambi i problemi. Tuttavia, richiederebbero i diritti di amministratore e potrebbero ridurre la funzionalità (meno memoria da utilizzare) e estrarre RAM da un sistema in esecuzione sarebbe comunque una preoccupazione valida.


33
Sostituirei "completamente inutile" con "solo un piccolo miglioramento della sicurezza". Ad esempio, è possibile ottenere l'accesso a un dump della memoria se si ha accesso in lettura a una directory tmp, una macchina mal configurata e un arresto anomalo nell'applicazione. In quel caso non sarebbe in grado di installare un keylogger, ma si potrebbe analizzare il core dump.
Joachim Sauer,

44
Cancellare i dati non crittografati dalla memoria non appena hai finito è considerata una buona pratica non perché è infallibile (non lo è); ma perché riduce il livello di esposizione alle minacce. In questo modo non si prevengono gli attacchi in tempo reale; ma poiché serve uno strumento di mitigazione del danno riducendo in modo significativo la quantità di dati esposti in un attacco retroattivo su un'istantanea della memoria (ad esempio una copia della memoria delle app che è stata scritta in un file di scambio o che è stata letta dalla memoria strappata da un server in esecuzione e spostato su un altro prima che il suo stato non riuscisse).
Dan Fiddling By Firelight,

9
Tendo a concordare con l'atteggiamento di questa risposta. Mi azzarderei a proporre che la maggior parte delle violazioni della sicurezza delle conseguenze si verifichi a un livello di astrazione molto più elevato rispetto ai bit in memoria. Certo, ci sono probabilmente scenari nei sistemi di difesa iper-sicuri in cui questo può essere di grande preoccupazione ma pensare seriamente a questo livello è eccessivo per il 99% delle applicazioni in cui .NET o Java vengono sfruttate (in quanto legate alla garbage collection).
Kingdango,

10
Dopo la penetrazione di Heartbleed nella memoria del server, rivelando le password, sostituirei la stringa "solo un piccolo miglioramento della sicurezza" con "assolutamente essenziale per non usare String per le password, ma utilizzare invece un char []".
Peter vdL,

9
@PetervdL heartbleed ha consentito solo la lettura di una specifica raccolta di buffer riutilizzata (utilizzata sia per i dati critici per la sicurezza sia per l'I / O di rete senza compensazione tra - per motivi di prestazioni), non è possibile combinare questo con una stringa Java poiché sono progettualmente non riutilizzabili . Né puoi leggere nella memoria casuale usando Java per ottenere il contenuto di una stringa. I problemi di linguaggio e design che portano al cuore sono semplicemente impossibili con Java Strings.
josefx,

86

Non penso che questo sia un suggerimento valido, ma posso almeno indovinare il motivo.

Penso che la motivazione sia voler assicurarsi di poter cancellare prontamente e con certezza ogni traccia della password in memoria dopo che è stata utilizzata. Con un char[]puoi sovrascrivere ogni elemento dell'array con uno spazio vuoto o qualcosa di sicuro. Non è possibile modificare il valore interno di Stringquel modo.

Ma quella da sola non è una buona risposta; perché non assicurarsi semplicemente un riferimento a char[]o Stringnon scappare? Quindi non ci sono problemi di sicurezza. Ma il fatto è che gli Stringoggetti possono essere intern()editati in teoria e mantenuti vivi all'interno del pool costante. Suppongo che l'uso char[]proibisca questa possibilità.


4
Non direi che il problema è che i tuoi riferimenti "scapperanno" o meno. È solo che le stringhe rimarranno immodificate nella memoria per qualche tempo aggiuntivo, mentre char[]possono essere modificate, e quindi è irrilevante se vengono raccolte o meno. E poiché l'internazionalizzazione delle stringhe deve essere eseguita esplicitamente per i non-letterali, è come dire che a char[]può essere referenziato un campo statico.
Groo,

1
la password non è in memoria come stringa dal post del modulo?
Dawesi,

66

La risposta è già stata data, ma vorrei condividere un problema che ho scoperto di recente con le librerie standard Java. Mentre ora si prendono molta cura di sostituire le stringhe di password con char[]ovunque (il che ovviamente è una buona cosa), altri dati critici per la sicurezza sembrano essere trascurati quando si tratta di cancellarli dalla memoria.

Sto pensando ad esempio alla classe PrivateKey . Considerare uno scenario in cui caricare una chiave RSA privata da un file PKCS # 12, utilizzandolo per eseguire alcune operazioni. Ora, in questo caso, annusare la password da sola non sarebbe di grande aiuto fintanto che l'accesso fisico al file chiave è adeguatamente limitato. Come attaccante, staresti molto meglio se avessi ottenuto la chiave direttamente invece della password. Le informazioni desiderate possono essere trapelate molteplici, core dump, una sessione di debugger o file di scambio sono solo alcuni esempi.

E a quanto pare, non c'è nulla che ti consenta di cancellare le informazioni private di un PrivateKeydalla memoria, perché non esiste un'API che ti consente di cancellare i byte che formano le informazioni corrispondenti.

Questa è una brutta situazione, poiché questo documento descrive come questa circostanza potrebbe essere potenzialmente sfruttata.

La libreria OpenSSL, ad esempio, sovrascrive le sezioni di memoria critica prima che vengano liberate le chiavi private. Poiché Java viene raccolto in modo inutile, avremmo bisogno di metodi espliciti per cancellare e invalidare le informazioni private per le chiavi Java, che devono essere applicate immediatamente dopo aver usato la chiave.


Un modo per aggirare questo è usare un'implementazione PrivateKeyche in realtà non carica il suo contenuto privato in memoria: ad esempio tramite un token hardware PKCS # 11. Forse un'implementazione software di PKCS # 11 potrebbe occuparsi della pulizia manuale della memoria. Forse usare qualcosa come l'archivio NSS (che condivide la maggior parte della sua implementazione con il PKCS11tipo di archivio in Java) è meglio. Il KeychainStore(keystore OSX) carica l'intero contenuto della chiave privata nelle sue PrivateKeyistanze, ma non dovrebbe essere necessario. (Non sono sicuro di cosa WINDOWS-MYfaccia il KeyStore su Windows.)
Bruno

@Bruno Certo, i token basati su hardware non ne soffrono, ma che dire delle situazioni in cui sei più o meno costretto a usare una chiave software? Non ogni distribuzione ha il budget per permettersi un HSM. A un certo punto gli archivi delle chiavi del software dovranno caricare la chiave in memoria, quindi IMO dovremmo almeno avere la possibilità di cancellare nuovamente la memoria a piacimento.
Rilievo

Assolutamente, mi chiedevo solo se alcune implementazioni software equivalenti a un HSM potessero comportarsi meglio in termini di pulizia della memoria. Ad esempio, quando si utilizza client-auth con Safari / OSX, il processo Safari in realtà non vede mai la chiave privata, la libreria SSL sottostante fornita dal sistema operativo parla direttamente al demone della sicurezza che richiede all'utente di utilizzare la chiave dal portachiavi. Sebbene tutto ciò avvenga nel software, una separazione simile come questa può aiutare se la firma è delegata a un'entità diversa (anche basata su software) che scarica o cancella meglio la memoria.
Bruno,

@Bruno: un'idea interessante, un ulteriore livello di indirizzamento che si occupa di cancellare la memoria risolverebbe questo in modo trasparente. Scrivere un wrapper PKCS # 11 per l'archivio chiavi del software potrebbe già fare il trucco?
Rilievo

51

Come afferma Jon Skeet, non c'è altro modo che usare la riflessione.

Tuttavia, se la riflessione è un'opzione per te, puoi farlo.

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

quando eseguito

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

Nota: se il carattere String [] è stato copiato come parte di un ciclo GC, è possibile che la copia precedente sia da qualche parte nella memoria.

Questa vecchia copia non verrebbe visualizzata in un dump dell'heap, ma se si avesse accesso diretto alla memoria grezza del processo, la si potrebbe vedere. In generale dovresti evitare che chiunque abbia tale accesso.


2
Meglio fare anche qualcosa per impedire la stampa della lunghezza della password da cui otteniamo '***********'.
chux - Ripristina Monica il

@chux puoi usare un carattere di larghezza zero, anche se questo potrebbe essere più confuso che utile. Non è possibile modificare la lunghezza di un array di caratteri senza utilizzare Unsafe. ;)
Peter Lawrey,

5
A causa della deduplicazione delle stringhe di Java 8, penso che fare qualcosa del genere possa essere piuttosto distruttivo ... potresti finire per cancellare altre stringhe nel tuo programma che per caso avevano lo stesso valore della password String. Improbabile, ma possibile ...
jamp

1
@PeterLawrey Deve essere abilitato con un argomento JVM, ma è lì. Puoi leggerlo qui: blog.codecentric.de/en/2014/08/…
jamp

1
È molto probabile che la password sia ancora nel Scannerbuffer interno e, poiché non è stata utilizzata System.console().readPassword(), in forma leggibile nella finestra della console. Ma per la maggior parte dei casi di utilizzo pratico, la durata usePassworddell'esecuzione è il vero problema. Ad esempio, quando si effettua una connessione a un altro computer, ci vuole molto tempo e dice a un utente malintenzionato che è il momento giusto per cercare la password nell'heap. L'unica soluzione è impedire agli aggressori di leggere la memoria dell'heap ...
Holger,

42

Questi sono tutti i motivi, si dovrebbe scegliere un array char [] anziché String per una password.

1. Poiché le stringhe sono immutabili in Java, se si memorizza la password come testo normale, sarà disponibile in memoria fino a quando il Garbage Collector non la cancella e poiché String viene utilizzato nel pool di stringhe per la riusabilità, è molto probabile che rimanere in memoria per un lungo periodo, il che rappresenta una minaccia per la sicurezza.

Dal momento che chiunque abbia accesso al dump della memoria può trovare la password in chiaro, questo è un altro motivo per cui dovresti sempre usare una password crittografata piuttosto che un semplice testo. Poiché le stringhe sono immutabili, non è possibile modificare il contenuto delle stringhe poiché qualsiasi modifica produrrà una nuova stringa, mentre se si utilizza un carattere [] è comunque possibile impostare tutti gli elementi come vuoti o zero. Pertanto, la memorizzazione di una password in una matrice di caratteri riduce chiaramente il rischio per la sicurezza di rubare una password.

2. Java stesso consiglia di utilizzare il metodo getPassword () di JPasswordField che restituisce un carattere [], anziché il metodo deprTated getText () che restituisce le password in testo chiaro indicando motivi di sicurezza. È bene seguire i consigli del team Java e aderire agli standard piuttosto che andare contro di essi.

3. Con String esiste sempre il rischio di stampare testo normale in un file di registro o in una console, ma se si utilizza un array non si stampa il contenuto di un array, ma viene stampata la posizione di memoria. Sebbene non sia una vera ragione, ha ancora senso.

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

Riferito da questo blog . Spero che questo possa essere d'aiuto.


10
Questo è ridondante. Questa risposta è una versione esatta della risposta scritta da @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Si prega di non copiare incolla o duplicare due volte la stessa risposta.
Lucky

Qual è la differenza tra 1.) System.out.println ("Password caratteri:" + charPassword); e 2.) System.out.println (charPassword); Perché sta dando lo stesso "sconosciuto" dell'output.
Vaibhav_Sharma,

3
@Lucky Purtroppo la risposta più vecchia a cui ti sei collegato è stata plagiata dallo stesso blog di questa risposta e ora è stata eliminata. Vedi meta.stackoverflow.com/questions/389144/… . Questa risposta è stata semplicemente tagliata e incollata dallo stesso blog e non ha aggiunto nulla, quindi avrebbe dovuto essere semplicemente un commento collegato alla fonte originale.
skomisa,

41

Modifica: tornando a questa risposta dopo un anno di ricerche sulla sicurezza, mi rendo conto che ha la sfortunata implicazione che tu possa mai effettivamente confrontare le password in chiaro. Per favore, no. Usa un hash unidirezionale sicuro con un sale e un numero ragionevole di iterazioni . Prendi in considerazione l'uso di una libreria: è difficile ottenere queste cose!

Risposta originale: Che dire del fatto che String.equals () utilizza la valutazione del corto circuito ed è quindi vulnerabile a un attacco di temporizzazione? Potrebbe essere improbabile, ma teoricamente potresti calcolare il confronto delle password per determinare la sequenza corretta di caratteri.

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

Altre risorse sugli attacchi di cronometraggio:


Ma questo potrebbe essere presente anche nel confronto char [], da qualche parte faremmo lo stesso anche nella convalida della password. quindi come è meglio char [] della stringa?
Mohit Kanwar,

3
Hai assolutamente ragione, l'errore può essere fatto in entrambi i modi. Conoscere il problema è la cosa più importante qui, considerando che non esiste un metodo esplicito di confronto delle password in Java per password basate su String o basate su char []. Direi che la tentazione di usare compare () per le stringhe è un buon motivo per andare con char []. In questo modo hai almeno il controllo su come viene fatto il confronto (senza estendere String, che è un dolore imo).
Graph Theory,

Oltre a confronto le password in chiaro non è la cosa giusta in ogni caso, la tentazione di utilizzare Arrays.equalsper char[]è alto come per String.equals. Se a qualcuno importava, c'era una classe chiave dedicata che incapsulava la password effettiva e si occupava dei problemi — oh aspetta, i pacchetti di sicurezza reali hanno classi chiave dedicate, queste domande e risposte riguardano solo un'abitudine al di fuori di esse, ad esempio JPasswordField, per usare char[]anziché String(dove gli algoritmi effettivi utilizzano byte[]comunque).
Holger

Il software rilevante per la sicurezza dovrebbe fare qualcosa come sleep(secureRandom.nextInt())prima di rifiutare un tentativo di accesso, non solo rimuovendo la possibilità di attacchi temporali, ma anche contrastando i tentativi di forza bruta.
Holger

36

Non c'è nulla che char array ti offra vs String se non lo ripulisci manualmente dopo l'uso e non ho visto nessuno farlo. Quindi per me la preferenza di char [] vs String è un po 'esagerata.

Dai un'occhiata alla libreria di Spring Security ampiamente usata qui e chiediti: le password incompetenti o char [] dei ragazzi di Spring Security non hanno molto senso. Quando un brutto hacker afferra i dump della memoria della tua RAM, assicurati che ottenga tutte le password anche se usi metodi sofisticati per nasconderle.

Tuttavia, Java cambia continuamente e alcune funzionalità spaventose come la funzionalità di deduplicazione delle stringhe di Java 8 potrebbero internare oggetti String a tua insaputa. Ma questa è una conversazione diversa.


Perché la deduplicazione delle stringhe fa paura? Si applica solo quando ci sono almeno due stringhe con lo stesso contenuto, quindi quale pericolo potrebbe derivare dal lasciare che queste due stringhe già identiche condividano lo stesso array? Oppure chiediamolo al contrario: se non c'è deduplicazione delle stringhe, quale vantaggio deriva dal fatto che entrambe le stringhe hanno una matrice distinta (dello stesso contenuto)? In entrambi i casi, ci sarà una serie di quei contenuti che saranno vivi almeno fintanto che la stringa più lunga vivente di quei contenuti sarà viva ...
Holger

@Holger tutto ciò che è fuori dal tuo controllo è un potenziale rischio ... per esempio se due utenti hanno la stessa password questa meravigliosa funzione li memorizzerà entrambi in un singolo carattere [] rendendo evidente che sono uguali, non sono sicuro che sia un rischio enorme ma comunque
Oleg Mikheev

Se si ha accesso alla memoria dell'heap e ad entrambe le istanze di stringa, non importa se le stringhe puntano allo stesso array o a due array dello stesso contenuto, ognuno è facile da scoprire. Soprattutto, in quanto è comunque irrilevante. Se sei a questo punto, prendi entrambe le password, identiche o meno. L'errore reale risiede nell'utilizzo di password in chiaro invece di hash salati.
Holger

@Holger per verificare la password deve essere in testo in chiaro per qualche tempo, 10ms, anche se è solo per creare un hash salato da esso. Quindi, se succede che ci sono due password identiche mantenute in memoria anche per 10 ms, potrebbe entrare in gioco la deduplicazione. Se in realtà interna le stringhe, queste vengono conservate in memoria per un tempo molto più lungo. Il sistema che non si riavvia da mesi raccoglierà molti di questi. Teorizzando.
Oleg Mikheev il

Sembra che tu abbia un malinteso fondamentale sulla deduplicazione delle stringhe. Non "stringhe interne", tutto ciò che fa, è lasciare che stringhe con lo stesso contenuto puntino allo stesso array, il che in realtà riduce il numero di istanze di array che contengono la password in chiaro, poiché tutte le istanze di array tranne una possono essere recuperate e sovrascritte da altri oggetti immediatamente. Queste stringhe vengono comunque raccolte come qualsiasi altra stringa. Forse aiuta, se si capisce che la deduplicazione viene effettivamente eseguita dal Garbage Collector, per le stringhe che sono sopravvissute solo a più cicli GC.
Holger,

30

Le stringhe sono immutabili e non possono essere modificate una volta create. La creazione di una password come stringa lascerà riferimenti vaganti alla password nell'heap o nel pool di stringhe. Ora, se qualcuno prende un dump del processo Java e lo scansiona attentamente, potrebbe essere in grado di indovinare le password. Naturalmente, queste stringhe non utilizzate verranno raccolte in modo immondizia, ma ciò dipende da quando il GC entra in funzione.

Dall'altro lato, i caratteri [] sono modificabili non appena l'autenticazione è stata eseguita, puoi sovrascriverli con qualsiasi carattere come tutte le M o le barre rovesciate. Ora, anche se qualcuno prende un dump dell'heap, potrebbe non essere in grado di ottenere le password che non sono attualmente in uso. Questo ti dà un maggiore controllo nel senso di cancellare il contenuto dell'Oggetto da solo, in attesa che il GC lo faccia.


Saranno GCC solo se la JVM in questione è> 1.6. Prima della 1.7, tutte le stringhe erano archiviate in permgen.
avgvstvs,

@avgvstvs: "tutte le stringhe sono state memorizzate in permgen" è semplicemente sbagliato. Solo le stringhe internate venivano memorizzate lì e se non provenivano da valori letterali delle stringhe referenziati dal codice, venivano comunque raccolte. Basta pensarci. Se le stringhe in genere non erano mai state codificate in GC nelle JVM precedenti alla 1.7, come poteva una qualsiasi applicazione Java sopravvivere più di qualche minuto?
Holger

@Holger Questo è falso. Internato stringsE ilString pool (pool di stringhe precedentemente utilizzate) ENTRAMBI erano memorizzati in Permgen prima della 1.7. Inoltre, consultare la sezione 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… La JVM ha sempre verificato Stringsse avessero lo stesso valore di riferimento e chiamerebbe String.intern()PER TE. Il risultato era che ogni volta che JVM rilevava stringhe identiche constant_poolnell'heap o le spostava in permgen. E ho lavorato su diverse applicazioni con "permgen strisciante" fino all'1.7. È stato un vero problema.
avgvstvs

Quindi, per ricapitolare: fino all'1.7, le stringhe iniziarono nell'heap, quando furono usate furono inserite nella constant_poolquale si trovava IN permgen, e quindi se una stringa fosse usata più di una volta, sarebbe stata internata.
avgvstvs

@avgvstvs: non esiste un "pool di stringhe precedentemente utilizzate". Stai mettendo insieme cose completamente diverse. Esiste un pool di stringhe di runtime contenente valori letterali di stringa e stringa esplicitamente internata, ma nessun altro. E ogni classe ha il suo pool costante contenente costanti di compilazione-tempo . Queste stringhe vengono automaticamente aggiunte al pool di runtime, ma solo queste , non tutte le stringhe.
Holger

21

String è immutabile e va al pool di stringhe. Una volta scritto, non può essere sovrascritto.

char[] è un array che dovresti sovrascrivere dopo aver usato la password ed è così che dovrebbe essere fatto:

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

Uno scenario in cui l'attaccante potrebbe utilizzarlo è un crashdump - quando la JVM si arresta in modo anomalo e genera un dump di memoria - sarai in grado di vedere la password.

Questo non è necessariamente un aggressore esterno dannoso. Potrebbe trattarsi di un utente dell'assistenza che ha accesso al server a fini di monitoraggio. Poteva sbirciare in una discarica e trovare le password.


2
ch = null; non puoi farlo
Yugerten,

Ma non request.getPassword()crea già la stringa e non la aggiungi al pool?
Tvde1

1
ch = '0'cambia la variabile locale ch; non ha alcun effetto sull'array. E il tuo esempio è inutile comunque, inizi con un'istanza di stringa a cui fai riferimento toCharArray(), creando un nuovo array e anche quando sovrascrivi correttamente il nuovo array, non cambia l'istanza di stringa, quindi non ha alcun vantaggio rispetto al solo utilizzo della stringa esempio.
Holger

@Holger grazie. Corretto il codice di pulizia dell'array char.
ACV

3
Puoi semplicemente usareArrays.fill(pass, '0');
Holger l'

18

La risposta breve e diretta sarebbe perché char[]è mutevole mentreString oggetti non lo sono.

Stringsin Java sono oggetti immutabili. Questo è il motivo per cui non possono essere modificati una volta creati, e quindi l'unico modo per rimuovere i loro contenuti dalla memoria è farli raccogliere. Sarà solo allora che la memoria liberata dall'oggetto potrà essere sovrascritta e i dati saranno spariti.

Ora la garbage collection in Java non avviene ad alcun intervallo garantito. IlString può quindi persistere nella memoria per molto tempo e se un processo si arresta in modo anomalo durante questo periodo, il contenuto della stringa potrebbe finire in un dump della memoria o in qualche registro.

Con un array di caratteri , puoi leggere la password, terminare di lavorarci il prima possibile e quindi modificare immediatamente il contenuto.


@fallenidol Niente affatto. Leggi attentamente e troverai le differenze.
Pritam Banerjee,

12

La stringa in Java è immutabile. Pertanto, ogni volta che viene creata una stringa, rimarrà nella memoria fino a quando non verrà raccolta in modo errato. Quindi chiunque abbia accesso alla memoria può leggere il valore della stringa.
Se il valore della stringa viene modificato, finirà per creare una nuova stringa. Pertanto, sia il valore originale che il valore modificato rimangono in memoria fino a quando non vengono raccolti.

Con l'array di caratteri, il contenuto dell'array può essere modificato o cancellato una volta offerto lo scopo della password. Il contenuto originale dell'array non verrà trovato in memoria dopo che è stato modificato e anche prima che entri in azione la garbage collection.

Per motivi di sicurezza, è meglio archiviare la password come array di caratteri.


2

È discutibile se si debba usare String o usare Char [] a questo scopo perché entrambi hanno i loro vantaggi e svantaggi. Dipende da ciò di cui l'utente ha bisogno.

Poiché le stringhe in Java sono immutabili, ogni volta che alcuni tentano di manipolare la stringa, crea un nuovo oggetto e la stringa esistente rimane inalterata. Questo potrebbe essere visto come un vantaggio per l'archiviazione di una password come stringa, ma l'oggetto rimane in memoria anche dopo l'uso. Quindi, se qualcuno in qualche modo ha ottenuto la posizione di memoria dell'oggetto, quella persona può facilmente rintracciare la password memorizzata in quella posizione.

Char [] è mutabile, ma ha il vantaggio che dopo il suo utilizzo il programmatore può pulire esplicitamente l'array o sovrascrivere i valori. Quindi, una volta terminato l'uso, viene pulito e nessuno potrà mai sapere delle informazioni memorizzate.

Sulla base delle circostanze di cui sopra, si può avere un'idea se andare con String o andare con Char [] per i loro requisiti.


0

Stringa di caso:

    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

Caso 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.