java - Perché char[]è preferito a String per le password?




security passwords (15)

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

Perché String pone una minaccia alla sicurezza quando si tratta di password? È scomodo usare char[] .


Answers

Questi sono tutti i motivi, si dovrebbe scegliere char [] array invece di String per password.

1. Poiché le stringhe sono immutabili in Java se si memorizza la password come testo normale, sarà disponibile in memoria fino a quando Garbage Collector non lo cancella e poiché String viene utilizzato nel pool di stringhe per riusabilità, è molto probabile che rimanga in memoria a lungo durata, che rappresentano una minaccia per la sicurezza. Dal momento che chiunque abbia accesso al dump della memoria può trovare la password in chiaro e questa è un'altra ragione per cui si dovrebbe sempre usare una password crittografata rispetto al testo normale. Poiché le stringhe non sono modificabili, non è possibile modificare il contenuto delle stringhe perché qualsiasi modifica produrrà una nuova stringa, mentre se si char [] è ancora possibile impostare tutti i suoi elementi come vuoti o nulli. Pertanto, la memorizzazione della password nell'array di caratteri attenua chiaramente il rischio di sicurezza di furto della password.

2. Java stesso consiglia di utilizzare il metodo getPassword () di JPasswordField che restituisce un metodo char [] e getText () che restituisce la password in chiaro, indicando il motivo di sicurezza. È buono seguire i consigli del team di Java e aderire allo standard piuttosto che andare contro di esso.

3. Con String c'è sempre il rischio di stampare testo normale in file di log o console, ma se si usa Array non si stamperà il contenuto dell'array, ma si stamperà la sua posizione di memoria. anche se non è una vera ragione, ma 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: [[email protected]

Riferimento da: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html Spero che questo aiuti.


  1. Le stringhe sono immutabili in Java se si memorizza la password come testo normale sarà disponibile in memoria fino a quando Garbage Collector non lo cancella e poiché le stringhe vengono utilizzate nel pool di stringhe per la riutilizzabilità ci sono possibilità piuttosto elevate che rimanga in memoria per un lungo durata, che rappresenta una minaccia per la sicurezza. Dal momento che chiunque abbia accesso al dump della memoria può trovare la password in chiaro
  2. Raccomandazione Java che utilizza il metodo getPassword() di JPasswordField che restituisce un metodo char [] e deprecato getText() che restituisce la password in chiaro, indicando il motivo di sicurezza.
  3. toString () c'è sempre il rischio di stampare testo normale in file di log o console, ma se si usa Array non si stamperà il contenuto dell'array, ma si stamperà la sua posizione di memoria.

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    Parola d'ordine stringa: passwd

    Password caratteri: [C @ 110b2345

Considerazioni finali: sebbene l'uso di char [] non sia sufficiente, è necessario cancellare il contenuto per renderlo più sicuro. Suggerisco anche di lavorare con hash o password cifrata invece di testo normale e di cancellarlo dalla memoria non appena l'autenticazione è completata.


Modifica: Tornando a questa risposta dopo un anno di ricerche sulla sicurezza, mi rendo conto che ciò comporta l'implicazione piuttosto sfortunata di poter effettivamente confrontare le password in chiaro. Per favore non farlo. Utilizzare un hash unidirezionale sicuro con un sale e un numero ragionevole di iterazioni . Prendi in considerazione l'utilizzo di una libreria: questa roba è difficile da ottenere!

Risposta originale: per quanto riguarda il fatto che String.equals () utilizza la valutazione di cortocircuito ed è quindi vulnerabile a un attacco di temporizzazione? Potrebbe essere improbabile, ma in teoria è possibile calcolare il confronto delle password per determinare la sequenza corretta dei 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 su attacchi di temporizzazione:


Mentre altri suggerimenti qui sembrano validi, c'è un altro buon motivo. Con la String semplice hai molte più possibilità di stampare accidentalmente la password su registri , monitor o qualche altro posto non sicuro. 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: [[email protected]

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

Sembrerebbe logico raccogliere e memorizzare la password in un oggetto di tipo java.lang.String . Tuttavia, ecco l'avvertenza: Gli Object di tipo String sono immutabili, ovvero non ci sono metodi definiti che consentono di modificare (sovrascrivere) o azzerare il contenuto di una String dopo l'uso. Questa funzione rende gli oggetti String inadatti per la memorizzazione di informazioni sensibili alla sicurezza come le password utente. Dovresti sempre raccogliere e archiviare le informazioni sensibili per la sicurezza in un array di char .

Linee guida 2-2 delle Linee guida per la codifica sicura per Java Programming Language, la 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 numeri di sicurezza sociale (SSN) e password, sono estremamente sensibili. Questa informazione non dovrebbe essere conservata più a lungo del necessario né dove possa essere vista, anche 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, ad esempio array di caratteri, e cancellati immediatamente dopo l'uso. La cancellazione delle strutture di dati ha ridotto l'efficacia sui tipici sistemi di runtime Java man mano che gli oggetti vengono trasferiti 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 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.


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

Aggiornare

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

  • Il tuo sistema di destinazione potrebbe essere configurato male o devi supporre che sia e devi essere paranoico sui core dump (può essere valido se i sistemi non sono gestiti da un amministratore).
  • Il tuo software deve essere eccessivamente paranoico per prevenire perdite di dati con l'hacker che ha accesso all'hardware - usando cose come TrueCrypt (fuori produzione), VeraCrypt o CipherShed .

Se possibile, disabilitare i core dump e il file di swap si prenderà cura di entrambi i problemi. Tuttavia, richiederebbero i diritti di amministratore e potrebbero ridurre la funzionalità (meno memoria da utilizzare) e la rimozione della RAM da un sistema in esecuzione rappresenterebbe ancora un problema valido.


Le stringhe sono immutabili e non possono essere modificate una volta che sono state create. La creazione di una password come una stringa lascerà riferimenti vaghi alla password nell'heap o nel lotto di stringhe. Ora se qualcuno prende un dump dell'heap del processo Java e analizza attentamente potrebbe essere in grado di indovinare le password. Ovviamente queste stringhe non usate saranno raccolte da garbage ma dipende da quando il GC entra in gioco.

D'altra parte, char [] sono modificabili non appena l'autenticazione è terminata, è possibile sovrascriverli con qualsiasi carattere come tutte le M o le barre inverse. Ora anche se qualcuno prende una discarica 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, come liberare il contenuto dell'oggetto da te stesso in attesa che il GC lo faccia.


Come afferma Jon Skeet, non c'è modo se non usando 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 corri

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

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

Questa vecchia copia non comparirebbe in un dump dell'heap, ma se si ha accesso diretto alla memoria grezza del processo si potrebbe vederlo. In generale si dovrebbe evitare a chiunque abbia tale accesso.


1) Poiché le stringhe sono immutabili in Java se si memorizza la password come testo normale, sarà disponibile in memoria fino a quando Garbage Collector non lo cancella e poiché String viene utilizzato nel pool di stringhe per riusabilità, è molto probabile che rimanga in memoria per la lunga durata, che rappresenta una minaccia per la sicurezza. Dal momento che chiunque abbia accesso al dump della memoria può trovare la password in chiaro e questa è un'altra ragione per cui si dovrebbe sempre usare una password crittografata rispetto al testo normale. Poiché le stringhe sono immutabili, non è possibile modificare il contenuto delle stringhe perché qualsiasi modifica produrrà una nuova stringa, mentre se si char [] è ancora possibile impostare tutti i suoi elementi come vuoti o nulli. Pertanto, la memorizzazione della password nell'array di caratteri riduce il rischio di sicurezza di furto della password.

2) Java raccomanda di utilizzare il metodo getPassword () di JPasswordField che restituisce un metodo char [] e getText () che restituisce la password in chiaro, indicando il motivo di sicurezza. È bene seguire i consigli del team di Java e aderire allo standard piuttosto che andare contro di esso.


Le stringhe sono immutabili . Ciò significa che una volta creata la String , se un altro processo può eseguire il dump della memoria, non c'è modo (a parte la reflection ) di liberarsi dei dati prima che la raccolta dati venga presa a calci.

Con un array, puoi cancellare i dati in modo esplicito dopo aver finito. Puoi sovrascrivere l'array con qualsiasi cosa tu voglia e la password non sarà presente in nessun punto del sistema, anche prima della garbage collection.

Quindi sì, questo è un problema di sicurezza - ma anche l'uso di char[] riduce solo la finestra di opportunità per un utente malintenzionato, 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 le copie vaganti dei dati. Credo che questo sia specifico per l'implementazione: il garbage collector può cancellare tutta la memoria nel suo svolgimento, per evitare questo genere di cose. Anche se lo fa, c'è ancora il tempo durante il quale il char[] contiene i caratteri reali come una finestra di attacco.


Gli array di char[] ( char[] ) possono essere cancellati 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 normale se vengono utilizzate le stringhe, ma se viene utilizzato char[] , dopo aver eliminato i dati con 0, la password è sicura.


Non c'è nulla che l'array di char ti dia rispetto a String a meno che non lo pulisci manualmente dopo l'uso, e non ho visto nessuno farlo davvero. Quindi per me la preferenza di char [] vs String è un po 'esagerata.

Dai un'occhiata alla vasta libreria Spring Security here usata e chiediti: le password incompetenti o char [] di Spring Security non hanno molto senso. Quando un brutto hacker afferra i dump di memoria della tua RAM assicurati che otterrà 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 gli oggetti String a tua insaputa. Ma questa è una conversazione diversa.


La stringa in java è immutabile. Quindi, ogni volta che viene creata una stringa, essa rimarrà nella memoria fino a quando non viene raccolta. Quindi chiunque abbia accesso alla memoria può leggere il valore della stringa.
Se il valore della stringa viene modificato, verrà creata una nuova stringa. Quindi sia il valore originale che il valore modificato rimangono nella memoria fino a quando non viene raccolto.

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

A causa della sicurezza, è meglio memorizzare la password come array di caratteri.


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

Sto pensando ad esempio alla classe PrivateKey . Considera uno scenario in cui devi caricare una chiave RSA privata da un file PKCS # 12, utilizzandola per eseguire alcune operazioni. Ora, in questo caso, il solo sniffing della password non sarebbe di grande aiuto fin tanto che l'accesso fisico al file chiave fosse opportunamente limitato. Come attaccante, sarebbe molto meglio se ottenessi direttamente la chiave invece della password. Le informazioni desiderate possono essere trapelate molteplici, core dump, una sessione di debugger o file di swap sono solo alcuni esempi.

E a quanto pare, non c'è nulla che ti permetta di cancellare le informazioni private di un PrivateKey dalla memoria, perché non c'è alcuna API che ti permetta di cancellare i byte che formano le informazioni corrispondenti.

Questa è una brutta situazione, in quanto questo paper descrive come questa circostanza potrebbe essere potenzialmente sfruttata.

La libreria OpenSSL, ad esempio, sovrascrive le sezioni di memoria critica prima che le chiavi private vengano liberate. Poiché Java è stato sottoposto a raccolta dei dati inutili, occorrerebbero metodi espliciti per cancellare e invalidare le informazioni private per le chiavi Java, che devono essere applicate immediatamente dopo l'uso della chiave.


Riepilogo ArrayList con ArrayDeque sono preferibili in molti più casi d'uso rispetto a LinkedList . Se non sei sicuro, inizia con ArrayList .

LinkedList e ArrayList sono due diverse implementazioni dell'interfaccia List. LinkedList implementa con una lista doppiamente collegata. ArrayList implementa con un array di ridimensionamento dinamico.

Come con le operazioni di elenchi e matrici standard collegate, i vari metodi avranno diversi runtime algoritmici.

Per LinkedList<E>

  • get(int index) è O (n) (con n / 4 passaggi in media)
  • add(E element) è O (1)
  • add(int index, E element) è O (n) (con n / 4 passaggi in media), ma O (1) quando index = 0 <--- vantaggio principale di LinkedList<E>
  • remove(int index) è O (n) (con n / 4 passaggi in media)
  • Iterator.remove() è O (1) . <--- vantaggio principale di LinkedList<E>
  • ListIterator.add(E element) è O (1) Questo è uno dei principali vantaggi di LinkedList<E>

Nota: molte delle operazioni richiedono n / 4 passaggi in media, un numero costante di passaggi nel migliore dei casi (ad es. Indice = 0) e n / 2 passaggi nel caso peggiore (centro della lista)

Per ArrayList<E>

  • get(int index) è O (1) <--- vantaggio principale di ArrayList<E>
  • add(E element) è O (1) ammortizzato, ma O (n) worst-case poiché l'array deve essere ridimensionato e copiato
  • add(int index, E element) è O (n) (con n / 2 passaggi in media)
  • remove(int index) è O (n) (con n / 2 passaggi in media)
  • Iterator.remove() è O (n) (con n / 2 passaggi in media)
  • ListIterator.add(E element) è O (n) (con n / 2 passaggi in media)

Nota: molte delle operazioni richiedono n / 2 passaggi in media, un numero costante di passaggi nel migliore dei casi (fine dell'elenco), n passaggi nel caso peggiore (inizio dell'elenco)

LinkedList<E> consente inserimenti o rimozioni a tempo costante utilizzando gli iteratori , ma solo l'accesso sequenziale di elementi. In altre parole, puoi scorrere l'elenco avanti o indietro, ma trovare una posizione nell'elenco richiede tempo proporzionale alla dimensione dell'elenco. Javadoc dice "le operazioni con cui l'indice nella lista attraverserà la lista dall'inizio o alla fine, a seconda di quale sia più vicino" , quindi quei metodi sono O (n) ( n / 4 passi) in media, sebbene O (1) per l' index = 0 .

ArrayList<E> , d'altra parte, consente l'accesso veloce alla lettura casuale, in modo da poter afferrare qualsiasi elemento in tempo costante. Ma aggiungere o rimuovere da qualsiasi luogo, ma alla fine richiede di spostare tutti gli ultimi elementi, sia per fare un'apertura o colmare il divario. Inoltre, se si aggiungono più elementi rispetto alla capacità dell'array sottostante, viene allocato un nuovo array (1,5 volte la dimensione) e il vecchio array viene copiato in quello nuovo, quindi l'aggiunta a un ArrayList è O (n) nel caso peggiore ma costante in media.

Quindi, a seconda delle operazioni che intendi fare, dovresti scegliere le implementazioni di conseguenza. L'iterazione su entrambi i tipi di List è praticamente altrettanto economica. (Iterare su ArrayList è tecnicamente più veloce, ma a meno che tu non stia facendo qualcosa di veramente sensibile alle prestazioni, non dovresti preoccuparti di questo: sono entrambe costanti.)

I principali vantaggi dell'utilizzo di LinkedList sorgono quando si riutilizzano gli iteratori esistenti per inserire e rimuovere elementi. Queste operazioni possono quindi essere eseguite in O (1) cambiando la lista solo localmente. In un elenco di array, il resto dell'array deve essere spostato (cioè copiato). Dall'altro lato, cercare in una LinkedList significa seguire i collegamenti in O (n) ( n / 2 passi) per il caso peggiore, mentre in una ArrayList la posizione desiderata può essere calcolata matematicamente e accessibile in O (1) .

Un altro vantaggio dell'utilizzo di LinkedList presenta quando si aggiunge o si rimuove dal capo dell'elenco, poiché tali operazioni sono O (1) , mentre sono O (n) per ArrayList . Nota che ArrayDeque può essere una buona alternativa a LinkedList per aggiungere e rimuovere dalla testa, ma non è una List .

Inoltre, se si dispone di elenchi di grandi dimensioni, tenere presente che anche l'utilizzo della memoria è diverso. Ogni elemento di una LinkedList ha un overhead maggiore poiché vengono memorizzati anche i puntatori agli elementi successivo e precedente. ArrayLists non hanno questo sovraccarico. Tuttavia, le ArrayLists occupano tutta la memoria allocata per la capacità, indipendentemente dal fatto che gli elementi siano stati effettivamente aggiunti.

La capacità iniziale predefinita di ArrayList è piuttosto piccola (10 da Java 1.4 - 1.8). Ma poiché l'implementazione sottostante è un array, l'array deve essere ridimensionato se aggiungi molti elementi. Per evitare l'alto costo del ridimensionamento quando sai che stai per aggiungere molti elementi, costruisci ArrayList con una capacità iniziale più alta.







java string security passwords char