Pourquoi char [] est-il préféré à String pour les mots de passe?


3423

Dans Swing, le champ de mot de passe a une méthode getPassword()(retours char[]) au lieu de la méthode habituelle getText()(retours String). De même, je suis tombé sur une suggestion de ne pas utiliser Stringpour gérer les mots de passe.

Pourquoi Stringconstitue- t-il une menace pour la sécurité en ce qui concerne les mots de passe? Cela semble gênant à utiliser char[].

Réponses:


4290

Les cordes sont immuables . Cela signifie qu'une fois que vous avez créé le String, si un autre processus peut vider la mémoire, il n'y a aucun moyen (à part la réflexion ) que vous puissiez vous débarrasser des données avant que la collecte des ordures n'intervienne.

Avec un tableau, vous pouvez effacer explicitement les données une fois que vous en avez terminé. Vous pouvez écraser le tableau avec tout ce que vous voulez et le mot de passe ne sera présent nulle part dans le système, même avant le ramassage des ordures.

Alors oui, c'est un problème de sécurité - mais même l'utilisation char[]ne fait que réduire la fenêtre d'opportunité pour un attaquant, et ce n'est que pour ce type d'attaque spécifique.

Comme indiqué dans les commentaires, il est possible que les tableaux déplacés par le garbage collector laissent des copies parasites des données en mémoire. Je crois que cela est spécifique à l'implémentation - le garbage collector peut effacer toute la mémoire au fur et à mesure, pour éviter ce genre de chose. Même si c'est le cas, il reste le temps pendant lequel le char[]contient les personnages réels comme fenêtre d'attaque.


3
Si un processus a accès à la mémoire de votre application, c'est déjà une faille de sécurité, non?
Yeti

5
@Yeti: Oui, mais ce n'est pas comme si c'était en noir et blanc. S'ils ne peuvent obtenir qu'un instantané de la mémoire, vous souhaitez réduire les dommages que cet instantané peut causer, ou réduire la fenêtre pendant laquelle un instantané vraiment sérieux peut être pris.
Jon Skeet

11
Une méthode d'attaque courante consiste à exécuter un processus qui alloue beaucoup de mémoire, puis à le rechercher des données utiles restantes, comme des mots de passe. Le processus n'a besoin d'aucun accès magique à l'espace mémoire d'un autre processus; il repose simplement sur d'autres processus qui meurent sans d'abord effacer les données sensibles et le système d'exploitation ne vide pas non plus la mémoire (ou les tampons de page) avant de la mettre à la disposition d'un nouveau processus. La suppression des mots de passe stockés dans des char[]emplacements coupe cette ligne d'attaque, ce qui n'est pas possible lors de l'utilisation String.
Ted Hopp

Si l'OS n'efface pas la mémoire avant de la donner à un autre processus, l'OS a des problèmes de sécurité majeurs! Cependant, techniquement, l'effacement se fait souvent avec des astuces en mode protégé et si le CPU est cassé (par exemple Intel Meldown), il est toujours possible de lire l'ancien contenu de la mémoire.
Mikko Rantalainen

1222

Alors que d'autres suggestions semblent valables ici, il y a une autre bonne raison. Avec plain, Stringvous avez beaucoup plus de chances d' imprimer accidentellement le mot de passe dans les journaux , les moniteurs ou tout autre endroit non sécurisé. char[]est moins vulnérable.

Considère ceci:

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

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

Tirages:

String: Password
Array: [C@5829428e

40
@voo, mais je doute que vous vous connectiez via l'écriture directe pour diffuser et concaténer. le cadre de journalisation transformerait le char [] en une bonne sortie
bestsss

41
@ Thr4wn L'implémentation par défaut de toStringis classname@hashcode. [Creprésente char[], le reste est du code de hachage hexadécimal.
Konrad Garus

15
Idée intéressante. Je voudrais souligner que cela ne se transpose pas à Scala qui a un toString significatif pour les tableaux.
mauhiz

37
J'écrirais un Passwordtype de classe pour cela. C'est moins obscur et plus difficile à passer accidentellement quelque part.
droite

8
Pourquoi est-ce que quelqu'un supposerait que le tableau de caractères allait être converti en objet? Je ne suis pas sûr de comprendre pourquoi tout le monde aime cette réponse. Supposons que vous ayez fait cela: System.out.println ("Password" .toCharArray ());
GC_

680

Pour citer un document officiel, le guide de Java Cryptography architecture dit ceci au sujet par char[]rapport aux Stringmots de passe (sur le cryptage par mot de passe, mais cela est plus généralement sur les mots de passe bien sûr):

Il semblerait logique de collecter et de stocker le mot de passe dans un objet de type java.lang.String. Cependant, voici la mise en garde: Objectles types de type Stringsont immuables, c'est-à-dire qu'aucune méthode définie ne vous permet de modifier (écraser) ou de mettre à zéro le contenu d'une String utilisation ultérieure. Cette fonctionnalité rend les Stringobjets inadaptés au stockage d'informations sensibles pour la sécurité telles que les mots de passe des utilisateurs. Vous devez toujours collecter et stocker des informations sensibles à la sécurité dans un chartableau à la place.

La directive 2-2 des directives de codage sécurisé pour le langage de programmation Java, version 4.0 dit également quelque chose de similaire (bien que ce soit à l'origine dans le contexte de la journalisation):

Ligne directrice 2-2: ne pas enregistrer d'informations très sensibles

Certaines informations, telles que les numéros de sécurité sociale (SSN) et les mots de passe, sont très sensibles. Ces informations ne doivent pas être conservées plus longtemps que nécessaire ni là où elles peuvent être vues, même par les administrateurs. Par exemple, il ne doit pas être envoyé dans des fichiers journaux et sa présence ne doit pas être détectable lors de recherches. Certaines données transitoires peuvent être conservées dans des structures de données modifiables, telles que des tableaux de caractères, et effacées immédiatement après leur utilisation. La suppression des structures de données a réduit l'efficacité sur les systèmes d'exécution Java typiques car les objets sont déplacés en mémoire de manière transparente vers le programmeur.

Cette directive a également des implications pour la mise en œuvre et l'utilisation de bibliothèques de niveau inférieur qui n'ont pas de connaissances sémantiques sur les données qu'elles traitent. Par exemple, une bibliothèque d'analyse de chaîne de bas niveau peut enregistrer le texte sur lequel elle fonctionne. Une application peut analyser un SSN avec la bibliothèque. Cela crée une situation où les SSN sont disponibles pour les administrateurs ayant accès aux fichiers journaux.


4
c'est exactement la référence imparfaite / bidon dont je parle ci-dessous la réponse de Jon, c'est une source bien connue avec beaucoup de critiques.
bestsss

37
@bestass pouvez-vous également citer une référence?
user961954

10
@bestass désolé, mais il Stringest assez bien compris et comment il se comporte dans la JVM ... il y a de bonnes raisons d'utiliser char[]à la place Stringlorsque vous traitez les mots de passe de manière sécurisée.
SnakeDoc

3
encore une fois bien que le mot de passe soit passé sous forme de chaîne du navigateur à la demande en tant que «chaîne» et non char? donc peu importe ce que vous faites, c'est une chaîne, à quel moment elle doit être traitée et supprimée, jamais stockée en mémoire?
Dawesi

3
@Dawesi - At which point- c'est spécifique à l'application, mais la règle générale est de le faire dès que vous obtenez quelque chose qui est censé être un mot de passe (texte en clair ou autre). Vous l'obtenez depuis le navigateur dans le cadre d'une requête HTTP, par exemple. Vous ne pouvez pas contrôler la livraison, mais vous pouvez contrôler votre propre stockage, donc dès que vous l'obtenez, mettez-le dans un caractère [], faites ce que vous devez en faire, puis mettez tout à '0' et laissez le gc récupérer.
luis.espinal

354

Les tableaux de caractères ( char[]) peuvent être effacés après utilisation en mettant chaque caractère à zéro et non aux chaînes. Si quelqu'un peut en quelque sorte voir l'image de la mémoire, il peut voir un mot de passe en texte brut si des chaînes sont utilisées, mais s'il char[]est utilisé, après avoir purgé les données avec des 0, le mot de passe est sécurisé.


13
Pas sécurisé par défaut. Si nous parlons d'une application Web, la plupart des conteneurs Web transmettront le mot de passe à l' HttpServletRequestobjet en texte clair. Si la version JVM est 1.6 ou inférieure, elle sera dans l'espace permgen. S'il est en 1.7, il sera toujours lisible jusqu'à ce qu'il soit collecté. (Quand c'est le cas.)
avgvstvs

4
@avgvstvs: les chaînes ne sont pas automatiquement déplacées vers l'espace permgen, qui ne s'applique qu'aux chaînes internes. En plus de cela, l'espace permgen est également soumis à la collecte des ordures, juste à un taux inférieur. Le vrai problème avec l'espace permgen est sa taille fixe, ce qui est exactement la raison pour laquelle personne ne devrait invoquer sans réfléchir intern()des chaînes arbitraires. Mais vous avez raison en ce que les Stringinstances existent en premier lieu (jusqu'à ce qu'elles soient collectées) et les transformer ensuite en char[]tableaux ne change rien.
Holger

3
@Holger voir docs.oracle.com/javase/specs/jvms/se6/html/… "Sinon, une nouvelle instance de la classe String est créée contenant la séquence de caractères Unicode donnée par la structure CONSTANT_String_info; cette instance de classe est le résultat de dérivation littérale de chaîne. Enfin, la méthode interne de la nouvelle instance de chaîne est invoquée. " En 1.6, la JVM appelait un stagiaire pour vous lorsqu'elle détectait des séquences identiques.
avgvstvs

3
@ Holger, vous avez raison, j'ai confondu le pool constant et le pool de chaînes, mais il est également faux que l'espace permgen ne s'applique qu'aux chaînes internes. Avant la version 1.7, le pool constant_pool et le string_pool résidaient dans l'espace permgen. Cela signifie que la seule classe de chaînes qui a été allouée au tas était comme vous l'avez dit, new String()ou StringBuilder.toString() j'ai géré des applications avec beaucoup de constantes de chaîne, et nous avons eu beaucoup de fluage de permgen en conséquence. Jusqu'au 1.7.
avgvstvs

5
@avgvstvs: eh bien, les constantes de chaîne sont, comme les mandats JLS, toujours internes, d'où la déclaration que les chaînes internes se sont retrouvées dans l'espace permgen, appliquées implicitement aux constantes de chaîne. La seule différence est que les constantes de chaîne ont été créées dans l'espace permgen en premier lieu, alors que l'appel intern()à une chaîne arbitraire pourrait provoquer l'allocation d'une chaîne équivalente dans l'espace permgen. Ce dernier pourrait se faire GC'er, s'il n'y avait pas de chaîne littérale du même contenu partageant cet objet…
Holger

220

Certaines personnes pensent que vous devez remplacer la mémoire utilisée pour stocker le mot de passe une fois que vous n'en avez plus besoin. Cela réduit la fenêtre de temps dont dispose un attaquant pour lire le mot de passe de votre système et ignore complètement le fait que l'attaquant a déjà besoin d'un accès suffisant pour détourner la mémoire JVM pour ce faire. Un attaquant avec autant d'accès peut attraper vos événements clés, ce qui le rend complètement inutile (AFAIK, alors corrigez-moi si je me trompe).

Mise à jour

Grâce aux commentaires, je dois mettre à jour ma réponse. Apparemment, il y a deux cas où cela peut ajouter une amélioration (très) mineure de la sécurité car cela réduit le temps qu'un mot de passe peut atterrir sur le disque dur. Pourtant, je pense que c'est exagéré pour la plupart des cas d'utilisation.

  • Votre système cible peut être mal configuré ou vous devez le supposer et vous devez être paranoïaque à propos des vidages de mémoire (peut être valide si les systèmes ne sont pas gérés par un administrateur).
  • Votre logiciel doit être trop paranoïaque pour éviter les fuites de données avec l'attaquant ayant accès au matériel - en utilisant des choses comme TrueCrypt (interrompu), VeraCrypt ou CipherShed .

Si possible, la désactivation des vidages mémoire et du fichier d'échange résoudrait les deux problèmes. Cependant, ils nécessiteraient des droits d'administrateur et pourraient réduire les fonctionnalités (moins de mémoire à utiliser) et extraire la RAM d'un système en cours d'exécution serait toujours une préoccupation valable.


33
Je remplacerais «complètement inutile» par «juste une légère amélioration de la sécurité». Par exemple, vous pouvez avoir accès à un vidage de mémoire si vous avez un accès en lecture à un répertoire tmp, à une machine mal configurée et à un plantage de votre application. Dans ce cas, vous ne pourriez pas installer un enregistreur de frappe, mais vous pourriez analyser le vidage de mémoire.
Joachim Sauer

44
L'effacement des données non chiffrées de la mémoire dès que vous en avez terminé est considéré comme une meilleure pratique, non pas parce qu'il est infaillible (ce n'est pas le cas); mais parce qu'il réduit votre niveau d'exposition aux menaces. Les attaques en temps réel ne sont pas empêchées en faisant cela; mais parce qu'il sert un outil d'atténuation des dommages en réduisant considérablement la quantité de données qui sont exposées dans une attaque rétroactive sur un instantané de mémoire (par exemple, une copie de la mémoire de vos applications qui a été écrite dans un fichier d'échange, ou qui a été lue de la mémoire retirée) à partir d'un serveur en cours d'exécution et déplacé vers un autre avant que son état n'échoue).
Dan est en train de jouer par Firelight le

9
J'ai tendance à être d'accord avec l'attitude de cette réponse. Je me risquerais à proposer que la plupart des violations de sécurité des conséquences se produisent à un niveau d'abstraction beaucoup plus élevé que les bits en mémoire. Bien sûr, il existe probablement des scénarios dans les systèmes de défense hyper-sécurisés où cela peut être très préoccupant, mais penser sérieusement à ce niveau est excessif pour 99% des applications où .NET ou Java sont exploités (en ce qui concerne la collecte des ordures).
kingdango

10
Après la pénétration de Heartbleed dans la mémoire du serveur, révélant les mots de passe, je remplacerais la chaîne "juste une amélioration de sécurité mineure" par "absolument essentiel pour ne pas utiliser String pour les mots de passe, mais utiliser un char [] à la place."
Peter vdL

9
@PetervdL heartbleed autorise uniquement la lecture d'une collection de tampons réutilisée spécifique (utilisée à la fois pour les données critiques pour la sécurité et les E / S réseau sans effacer entre les deux - pour des raisons de performances), vous ne pouvez pas combiner cela avec une chaîne Java car elles ne sont pas réutilisables par conception . Vous ne pouvez pas non plus lire dans la mémoire aléatoire en utilisant Java pour obtenir le contenu d'une chaîne. Les problèmes de langage et de conception qui conduisent à des déboires ne sont tout simplement pas possibles avec Java Strings.
josefx

86

Je ne pense pas que ce soit une suggestion valable, mais je peux au moins deviner la raison.

Je pense que la motivation est de vouloir vous assurer que vous pouvez effacer toute trace du mot de passe en mémoire rapidement et avec certitude après son utilisation. Avec un, char[]vous pouvez remplacer chaque élément du tableau par un blanc ou quelque chose à coup sûr. Vous ne pouvez pas modifier la valeur interne de Stringcette façon.

Mais cela seul n'est pas une bonne réponse; pourquoi ne pas simplement vous assurer qu'une référence au char[]ou Stringne s'échappe pas? Ensuite, il n'y a pas de problème de sécurité. Mais le fait est que les Stringobjets peuvent être intern()édités en théorie et maintenus en vie à l'intérieur du bassin constant. Je suppose que l'utilisation char[]interdit cette possibilité.


4
Je ne dirais pas que le problème est que vos références "s'échapperont" ou non. C'est juste que les chaînes resteront non modifiées dans la mémoire pendant un certain temps supplémentaire, alors char[]qu'elles peuvent être modifiées, et ensuite cela n'a pas d'importance qu'elles soient collectées ou non. Et puisque l'internement de chaînes doit être fait explicitement pour les non-littéraux, c'est la même chose que de dire qu'un char[]peut être référencé par un champ statique.
Groo

1
le mot de passe n'est-il pas en mémoire en tant que chaîne du post du formulaire?
Dawesi

66

La réponse a déjà été donnée, mais je voudrais partager un problème que j'ai découvert récemment avec les bibliothèques standard Java. Bien qu'ils prennent grand soin maintenant de remplacer les chaînes de mot de passe par char[]partout (ce qui est bien sûr une bonne chose), d'autres données critiques pour la sécurité semblent être ignorées lorsqu'il s'agit de les effacer de la mémoire.

Je pense par exemple à la classe PrivateKey . Considérez un scénario dans lequel vous chargeriez une clé RSA privée à partir d'un fichier PKCS # 12, en l'utilisant pour effectuer une opération. Maintenant, dans ce cas, renifler le mot de passe seul ne vous aiderait pas tant que l'accès physique au fichier de clé est correctement restreint. En tant qu'attaquant, vous feriez beaucoup mieux si vous obteniez la clé directement au lieu du mot de passe. Les informations souhaitées peuvent être des fuites multiples, des vidages de mémoire, une session de débogage ou des fichiers d'échange ne sont que quelques exemples.

Et il s'avère que rien ne vous permet d'effacer les informations privées d'un PrivateKeyde la mémoire, car il n'y a pas d'API qui vous permet d'effacer les octets qui forment les informations correspondantes.

C'est une mauvaise situation, car cet article décrit comment cette circonstance pourrait être potentiellement exploitée.

La bibliothèque OpenSSL, par exemple, écrase les sections de mémoire critiques avant de libérer les clés privées. Étant donné que Java est récupéré, nous aurions besoin de méthodes explicites pour effacer et invalider les informations privées des clés Java, qui doivent être appliquées immédiatement après l'utilisation de la clé.


Une solution consiste à utiliser une implémentation PrivateKeyqui ne charge pas réellement son contenu privé en mémoire: via un jeton matériel PKCS # 11 par exemple. Peut-être qu'une implémentation logicielle de PKCS # 11 pourrait prendre en charge le nettoyage manuel de la mémoire. Il est peut-être PKCS11préférable d' utiliser quelque chose comme le magasin NSS (qui partage la majeure partie de son implémentation avec le type de magasin en Java). Le KeychainStore(magasin de clés OSX) charge le contenu complet de la clé privée dans ses PrivateKeyinstances, mais il ne devrait pas en avoir besoin. (Je ne sais pas ce que fait le WINDOWS-MYKeyStore sous Windows.)
Bruno

@Bruno Bien sûr, les jetons matériels ne souffrent pas de cela, mais qu'en est-il des situations où vous êtes plus ou moins obligé d'utiliser une clé logicielle? Tous les déploiements n'ont pas le budget pour s'offrir un HSM. À un moment donné, les magasins de clés logicielles devront charger la clé en mémoire, de sorte que l'OMI devrait au moins avoir la possibilité d'effacer à nouveau la mémoire à volonté.
graver le

Absolument, je me demandais simplement si certaines implémentations logicielles équivalentes à un HSM pourraient mieux se comporter en termes de nettoyage de la mémoire. Par exemple, lors de l'utilisation de l'authentification client avec Safari / OSX, le processus Safari ne voit jamais réellement la clé privée, la bibliothèque SSL sous-jacente fournie par le système d'exploitation parle directement au démon de sécurité qui invite l'utilisateur à utiliser la clé du trousseau. Bien que tout cela soit fait dans un logiciel, une séparation similaire comme celle-ci peut aider si la signature est déléguée à une entité différente (même basée sur un logiciel) qui déchargerait ou effacerait mieux la mémoire.
Bruno

@Bruno: Idée intéressante, une couche d'indirection supplémentaire qui prend soin de vider la mémoire résoudrait en effet cela de manière transparente. Écrire un wrapper PKCS # 11 pour le magasin de clés logicielles pourrait déjà faire l'affaire?
graver le

51

Comme le dit Jon Skeet, il n'y a pas d'autre moyen que d'utiliser la réflexion.

Cependant, si la réflexion est une option pour vous, vous pouvez le faire.

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

lors de l'exécution

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

Remarque: si le caractère de la chaîne [] a été copié dans le cadre d'un cycle GC, il est possible que la copie précédente se trouve quelque part en mémoire.

Cette ancienne copie n'apparaîtrait pas dans un vidage de tas, mais si vous avez un accès direct à la mémoire brute du processus, vous pouvez la voir. En général, vous devez éviter que quiconque ait un tel accès.


2
Mieux vaut également faire quelque chose pour éviter d'imprimer la longueur du mot de passe dont nous obtenons '***********'.
chux

@chux vous pouvez utiliser un caractère de largeur nulle, bien que cela puisse être plus déroutant qu'utile. Il n'est pas possible de modifier la longueur d'un tableau de caractères sans utiliser Unsafe. ;)
Peter Lawrey

5
En raison de la déduplication des chaînes de Java 8, je pense que faire quelque chose comme ça peut être assez destructeur ... vous pouvez finir par effacer d'autres chaînes de votre programme qui, par hasard, avaient la même valeur que la chaîne de mot de passe. Peu probable, mais possible ...
Jamp

1
@PeterLawrey Il doit être activé avec un argument JVM, mais il est là. Vous pouvez en lire plus ici: blog.codecentric.de/en/2014/08/…
jamp

1
Il y a de fortes chances que le mot de passe soit toujours dans Scannerle tampon interne de et, comme vous ne l'avez pas utilisé System.console().readPassword(), sous une forme lisible dans la fenêtre de la console. Mais pour la plupart des cas d'utilisation pratiques, la durée de usePasswordl'exécution est le problème réel. Par exemple, lors d'une connexion à une autre machine, cela prend beaucoup de temps et indique à un attaquant que c'est le bon moment pour rechercher le mot de passe dans le tas. La seule solution est d'empêcher les attaquants de lire la mémoire du tas…
Holger

42

Ce sont toutes les raisons, il faut choisir un tableau char [] au lieu de String pour un mot de passe.

1. Étant donné que les chaînes sont immuables en Java, si vous stockez le mot de passe en texte brut, il sera disponible en mémoire jusqu'à ce que le garbage collector le supprime, et puisque String est utilisé dans le pool de chaînes pour être réutilisable, il y a de fortes chances qu'il le soit. rester en mémoire pendant une longue durée, ce qui constitue une menace pour la sécurité.

Étant donné que toute personne ayant accès au vidage de la mémoire peut trouver le mot de passe en texte clair, c'est une autre raison pour laquelle vous devez toujours utiliser un mot de passe crypté plutôt que du texte brut. Étant donné que les chaînes sont immuables, il est impossible de modifier le contenu des chaînes car toute modification produira une nouvelle chaîne, tandis que si vous utilisez un char [], vous pouvez toujours définir tous les éléments comme vides ou nuls. Ainsi, le stockage d'un mot de passe dans un tableau de caractères atténue clairement le risque de sécurité de voler un mot de passe.

2. Java lui-même recommande d'utiliser la méthode getPassword () de JPasswordField qui retourne un char [], au lieu de la méthode obsolète getText () qui retourne les mots de passe en texte clair en indiquant des raisons de sécurité. Il est bon de suivre les conseils de l'équipe Java et de se conformer aux normes plutôt que de s'y opposer.

3. Avec String, il y a toujours un risque d'imprimer du texte brut dans un fichier journal ou une console, mais si vous utilisez un tableau, vous n'imprimerez pas le contenu d'un tableau, mais son emplacement de mémoire sera imprimé. Bien que ce ne soit pas une vraie raison, cela reste logique.

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

Référencé depuis ce blog . J'espère que ça aide.


10
C'est redondant. Cette réponse est une version exacte de la réponse écrite par @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Veuillez ne pas copier-coller ou dupliquer deux fois la même réponse.
Lucky

Quelle est la différence entre 1.) System.out.println ("Mot de passe du caractère:" + charPassword); et 2.) System.out.println (charPassword); Parce qu'il donne le même "inconnu" en sortie.
Vaibhav_Sharma

3
@Lucky Malheureusement, l'ancienne réponse à laquelle vous avez lié a été plagiée à partir du même blog que cette réponse, et a maintenant été supprimée. Voir meta.stackoverflow.com/questions/389144/… . Cette réponse a simplement été copiée et collée à partir de ce même blog et n'a rien ajouté, elle aurait donc dû simplement être un commentaire renvoyant à la source d'origine.
skomisa

41

Edit: Pour en revenir à cette réponse après un an de recherche sur la sécurité, je me rends compte que cela implique plutôt malheureusement que vous compareriez réellement des mots de passe en texte brut. S'il vous plait, ne le faites pas. Utilisez un hachage unidirectionnel sécurisé avec un sel et un nombre raisonnable d'itérations . Pensez à utiliser une bibliothèque: ce truc est difficile à réaliser!

Réponse originale: Qu'en est-il du fait que String.equals () utilise une évaluation de court-circuit et est donc vulnérable à une attaque de synchronisation? Cela peut être peu probable, mais vous pourriez théoriquement chronométrer la comparaison des mots de passe afin de déterminer la séquence correcte de caractères.

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

Quelques ressources supplémentaires sur le timing des attaques:


Mais cela pourrait aussi être là dans la comparaison de char [], quelque part nous ferions la même chose pour la validation de mot de passe. alors comment est char [] mieux que string?
Mohit Kanwar

3
Vous avez absolument raison, l'erreur peut être commise de toute façon. Connaître le problème est la chose la plus importante ici, étant donné qu'il n'y a pas de méthode de comparaison de mot de passe explicite en Java pour les mots de passe basés sur String ou basés sur char []. Je dirais que la tentation d'utiliser compare () pour Strings est une bonne raison d'aller avec char []. De cette façon, vous avez au moins le contrôle sur la façon dont la comparaison est effectuée (sans étendre String, ce qui est une douleur imo).
Graph Theory

En plus de comparer les mots de passe en texte brut n'est pas la bonne chose de toute façon, la tentation d'utiliser Arrays.equalspour char[]est aussi élevée que pour String.equals. Si quelqu'un s'en souciait, il y avait une classe de clé dédiée encapsulant le mot de passe réel et s'occupant des problèmes - oh attendez, les vrais packages de sécurité ont des classes de clé dédiées, cette Q&R ne concerne qu'une habitude en dehors d'eux, par exemple JPasswordField, à utiliser char[]au lieu de String(où les algorithmes réels utilisent de byte[]toute façon).
Holger

Les logiciels liés à la sécurité devraient faire quelque chose comme sleep(secureRandom.nextInt())avant de rejeter une tentative de connexion de toute façon, ce qui élimine non seulement la possibilité de chronométrer les attaques, mais aussi contre les tentatives de force brute.
Holger

36

Il n'y a rien que le tableau de caractères vous donne contre String à moins que vous ne le nettoyiez manuellement après utilisation, et je n'ai vu personne le faire. Donc, pour moi, la préférence de char [] vs String est un peu exagérée.

Jetez un œil à la bibliothèque Spring Security largement utilisée ici et posez-vous la question - les gars de Spring Security sont-ils incompétents ou char [] n'ont tout simplement pas beaucoup de sens. Quand un pirate méchant saisit les vidages de mémoire de votre RAM, assurez-vous qu'il obtiendra tous les mots de passe même si vous utilisez des moyens sophistiqués pour les cacher.

Cependant, Java change tout le temps, et certaines fonctionnalités effrayantes comme la fonction de déduplication des chaînes de Java 8 peuvent interner des objets String à votre insu. Mais c'est une conversation différente.


Pourquoi la déduplication de chaînes fait-elle peur? Cela ne s'applique que lorsqu'il y a au moins deux chaînes ayant le même contenu, donc quel danger résulterait de laisser ces deux chaînes déjà identiques partager le même tableau? Ou demandons l'inverse: s'il n'y a pas de déduplication de chaîne, quel avantage découle du fait que les deux chaînes ont un tableau distinct (du même contenu)? Dans les deux cas, il y aura un tableau de ce contenu vivant au moins aussi longtemps que la plus longue chaîne vivante de ce contenu sera vivante…
Holger

@Holger tout ce qui est hors de votre contrôle est un risque potentiel ... par exemple, si deux utilisateurs ont le même mot de passe, cette merveilleuse fonctionnalité les stockera tous les deux dans un seul caractère [], ce qui rendra évident qu'ils sont les mêmes, je ne sais pas si c'est un énorme risque mais toujours
Oleg Mikheev

Si vous avez accès à la mémoire du tas et aux deux instances de chaîne, peu importe que les chaînes pointent vers le même tableau ou vers deux tableaux du même contenu, chacun est facile à découvrir. Surtout, car cela n'est pas pertinent de toute façon. Si vous en êtes à ce stade, vous saisissez les deux mots de passe, qu'ils soient identiques ou non. L'erreur réelle réside dans l'utilisation de mots de passe en texte brut au lieu de hachages salés.
Holger

@Holger pour vérifier le mot de passe, il doit être en texte clair en mémoire pendant un certain temps, 10 ms, même si c'est juste pour en créer un hachage salé. Ensuite, s'il arrive que deux mots de passe identiques soient conservés en mémoire même pendant 10 ms, la déduplication peut entrer en jeu. Si elle interne vraiment les chaînes, elles sont conservées en mémoire beaucoup plus longtemps. Un système qui ne redémarre pas pendant des mois en collectera beaucoup. Juste théoriser.
Oleg Mikheev

Il semble que vous ayez un malentendu fondamental sur la déduplication des chaînes. Il ne fait pas de "chaînes internes", tout ce qu'il fait, c'est de laisser des chaînes avec le même contenu pointer vers le même tableau, ce qui réduit en fait le nombre d'instances de tableau contenant le mot de passe en clair, car toutes les instances de tableau sauf une peuvent être récupérées et écrasées par d'autres objets immédiatement. Ces chaînes sont toujours collectées comme n'importe quelle autre chaîne. Peut-être que cela aide, si vous comprenez que la déduplication est réellement effectuée par le garbage collector, pour les chaînes qui ont survécu à plusieurs cycles de GC uniquement.
Holger

30

Les chaînes sont immuables et ne peuvent pas être modifiées une fois qu'elles ont été créées. La création d'un mot de passe sous forme de chaîne laissera des références erronées au mot de passe sur le tas ou sur le pool de chaînes. Maintenant, si quelqu'un prend un vidage de tas du processus Java et analyse attentivement, il pourrait être en mesure de deviner les mots de passe. Bien sûr, ces chaînes non utilisées seront récupérées, mais cela dépend du moment où le GC entre en jeu.

D'un autre côté, les char [] sont mutables dès que l'authentification est terminée, vous pouvez les écraser avec n'importe quel caractère comme tous les M ou les barres obliques inverses. Maintenant, même si quelqu'un prend un vidage de tas, il pourrait ne pas être en mesure d'obtenir les mots de passe qui ne sont pas actuellement utilisés. Cela vous donne plus de contrôle dans le sens comme l'effacement du contenu de l'objet vous-même par rapport à l'attente du GC pour le faire.


Ils ne seront GC'd que si la JVM en question est> 1.6. Avant la 1.7, toutes les chaînes étaient stockées dans permgen.
avgvstvs

@avgvstvs: "toutes les chaînes ont été stockées dans permgen" est tout simplement faux. Seules les chaînes internées y étaient stockées et si elles ne provenaient pas de littéraux de chaîne référencés par le code, elles étaient toujours récupérées. Pensez-y. Si les chaînes n'étaient généralement jamais GCées dans les JVM avant la 1.7, comment une application Java pourrait-elle survivre plus de quelques minutes?
Holger

@Holger C'est faux. Internés stringsET le Stringpool (pool de chaînes précédemment utilisées) étaient LES DEUX stockés dans Permgen avant la 1.7. Voir également la section 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… La machine virtuelle Java a toujours vérifié Stringssi elles étaient la même valeur de référence et appelait String.intern()POUR VOUS. Le résultat était que chaque fois que la JVM détectait des chaînes identiques dans le constant_pooltas, elle les déplaçait dans permgen. Et j'ai travaillé sur plusieurs applications avec "creeping permgen" jusqu'à la 1.7. C'était un vrai problème.
avgvstvs

Donc, pour récapituler: jusqu'à la version 1.7, les chaînes commençaient dans le tas, lorsqu'elles étaient utilisées, elles étaient placées dans le constant_poolqui était situé dans permgen, puis si une chaîne était utilisée plusieurs fois, elle serait internée.
avgvstvs

@avgvstvs: Il n'y a pas de "pool de chaînes précédemment utilisées". Vous jetez des choses entièrement différentes ensemble. Il existe un pool de chaînes d'exécution contenant des littéraux de chaîne et une chaîne internée explicitement, mais aucun autre. Et chaque classe a son pool constant contenant des constantes au moment de la compilation . Ces chaînes sont automatiquement ajoutées au pool d'exécution, mais uniquement celles - ci , pas toutes les chaînes.
Holger

21

La chaîne est immuable et va au pool de chaînes. Une fois écrit, il ne peut pas être écrasé.

char[] est un tableau que vous devez remplacer une fois que vous avez utilisé le mot de passe et voici comment procéder:

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

Un scénario où l'attaquant pourrait l'utiliser est un crashdump - lorsque la JVM se bloque et génère un vidage de la mémoire - vous pourrez voir le mot de passe.

Ce n'est pas nécessairement un attaquant externe malveillant. Il peut s'agir d'un utilisateur du support qui a accès au serveur à des fins de surveillance. Il pouvait jeter un œil dans un crashdump et trouver les mots de passe.


2
ch = null; vous ne pouvez pas faire cela
Yugerten

Mais ne crée pas request.getPassword()déjà la chaîne et ne l'ajoute pas au pool?
Tvde1

1
ch = '0'change la variable locale ch; cela n'a aucun effet sur le tableau. Et votre exemple est inutile de toute façon, vous commencez avec une instance de chaîne que vous appelez toCharArray(), en créant un nouveau tableau et même lorsque vous écrasez le nouveau tableau correctement, cela ne change pas l'instance de chaîne, donc il n'a aucun avantage sur l'utilisation de la chaîne exemple.
Holger

@ Merci Holger. Correction du code de nettoyage du tableau de caractères.
ACV

3
Vous pouvez simplement utiliserArrays.fill(pass, '0');
Holger

18

La réponse courte et directe serait parce qu'il char[]est mutable alors que les Stringobjets ne le sont pas.

Stringsen Java sont des objets immuables. C'est pourquoi ils ne peuvent pas être modifiés une fois créés, et donc la seule façon de supprimer leur contenu de la mémoire est de les faire ramasser les ordures. Ce ne sera qu'à ce moment que la mémoire libérée par l'objet pourra être écrasée et que les données auront disparu.

Désormais, le ramasse-miettes en Java ne se produit à aucun intervalle garanti. Le Stringpeut donc persister en mémoire pendant une longue période et si un processus se bloque pendant ce temps, le contenu de la chaîne peut se retrouver dans un vidage de la mémoire ou un journal.

Avec un tableau de caractères , vous pouvez lire le mot de passe, terminer de travailler avec lui dès que vous le pouvez, puis modifier immédiatement le contenu.


@fallenidol Pas du tout. Lisez attentivement et vous trouverez les différences.
Pritam Banerjee

12

La chaîne en java est immuable. Ainsi, chaque fois qu'une chaîne est créée, elle restera en mémoire jusqu'à ce qu'elle soit récupérée. Ainsi, toute personne ayant accès à la mémoire peut lire la valeur de la chaîne.
Si la valeur de la chaîne est modifiée, elle finira par créer une nouvelle chaîne. Ainsi, la valeur d'origine et la valeur modifiée restent en mémoire jusqu'à ce qu'elles soient récupérées.

Avec le tableau de caractères, le contenu du tableau peut être modifié ou effacé une fois l'objectif du mot de passe atteint. Le contenu d'origine du tableau ne sera pas trouvé en mémoire après sa modification et avant même que le garbage collection ne démarre.

En raison de problèmes de sécurité, il est préférable de stocker le mot de passe sous forme de tableau de caractères.


2

Il est discutable de savoir si vous devez utiliser String ou Char [] à cet effet, car les deux ont leurs avantages et leurs inconvénients. Cela dépend de ce dont l'utilisateur a besoin.

Étant donné que les chaînes en Java sont immuables, chaque fois que certains essaient de manipuler votre chaîne, il crée un nouvel objet et la chaîne existante reste inchangée. Cela pourrait être considéré comme un avantage pour le stockage d'un mot de passe sous forme de chaîne, mais l'objet reste en mémoire même après utilisation. Donc, si quelqu'un a en quelque sorte obtenu l'emplacement de mémoire de l'objet, cette personne peut facilement retrouver votre mot de passe stocké à cet emplacement.

Char [] est modifiable, mais il a l'avantage qu'après son utilisation, le programmeur peut nettoyer explicitement le tableau ou remplacer les valeurs. Ainsi, une fois qu'il est utilisé, il est nettoyé et personne ne peut jamais connaître les informations que vous avez stockées.

Sur la base des circonstances ci-dessus, on peut se faire une idée si aller avec String ou avec Char [] pour leurs besoins.


0

Chaîne de cas:

    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

Affaire 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.