Sécurité Java: comment effacer/effacer la mémoire associée à un objet? (Et/ou s'assurer de qui est la seule instance/copie d'une variable particulière)

je suis dans une discussion au travail sur la façon de sécuriser les informations sensibles (par exemple les mots de passe) stockées dans un programme Java. Selon les exigences de sécurité, la mémoire contenant des informations sensibles est effacée, par exemple en définissant les valeurs des octets à tous les zéros. La préoccupation est qu'un attaquant peut observer la mémoire associée au processus d'application, et donc nous voulons limiter autant que possible la fenêtre de temps de telles informations sensibles traîne autour. Auparavant, les projets impliquaient C++, donc un memset () a suffi.

(Incidemment, l'utilisation de memset() a été appelée en cause par certains compilateurs sont connus pour optimiser l'utilisation de l'exécutable résultant basée sur l'hypothèse que, puisque la mémoire n'est pas utilisée plus tard, il n'est pas nécessaire à zéro dans la première place. Ce flou est un avertissement pour ceux qui Google pour "memset" et "mémoire claire", etc).

maintenant nous avons sur nos mains un projet Java étant pressé contre ceci exigence.

pour les objets Java, ma compréhension est que:

  • un entaché de nullité de référence ne change la valeur de la référence; la mémoire sur le tas pour l'objet contient encore des données
  • un objet immuable comme une chaîne de caractères ne pourrait pas avoir ses données modifiées (ou du moins pas facilement, dans les limites d'une VM avec un gestionnaire de sécurité correctement activé)
  • la génération des ordures les collectionneurs peuvent faire des copies d'objets partout (comme noté ici )

et pour les primitifs, mon interprétation est que:

  • une variable de type primitif dans une méthode locale serait affectée sur la pile, et:
  • quand vous changez sa valeur, vous la modifiez directement en mémoire (au lieu d'utiliser une référence pour traiter un objet sur le tas).
  • des copies peuvent/seraient faites "dans les coulisses" dans certaines situations, comme le passage comme un argument dans des méthodes ou la boxe (auto - ou non) créant des instances des wrappers qui contiennent une autre variable primitive ayant la même valeur.

mon collègue prétend que les primitives Java sont immuables et qu'il y a de la documentation de la NSA et D'Oracle concernant le manque de support en Java pour cette exigence.

ma position est que les primitifs peuvent (au moins dans certaines situations) être mis à zéro en mettant la valeur à zéro (ou booléen à false), et la mémoire est nettoyée de cette façon.

j'essaie de vérifier s'il y a un langage dans le JLS ou autre documentation" officielle " sur le comportement requis des JVM quand il s'agit de la gestion de la mémoire en ce qui concerne les primitifs. Le plus proche que j'ai pu trouver était un "Secure Coding Guidelines for the Java Programming Language" sur le site D'Oracle qui mentionne le nettoyage des tableaux de caractères après utilisation.

je me disputais les définitions quand mon collègue appelait les primitifs immuables, mais je suis presque sûr qu'il voulait dire "la mémoire ne peut pas être correctement mise à zéro" - ne nous inquiétons pas à ce sujet. Nous n'avons pas discuté s'il voulait dire variables finales - du contexte nous parlions en général.

y a-t-il des réponses définitives ou des références à ce sujet? J'apprécierais tout ce qui pourrait me montrer où j'ai tort ou confirmer que je suis de droite.

Edit : après plus ample discussion, j'ai pu clarifier que mon collègue pensait aux enveloppements primitifs, pas aux primitifs eux-mêmes. Il nous reste donc le problème original de savoir comment effacer la mémoire en toute sécurité, de préférence des objets. Aussi, pour clarifier, l'information sensible n'est pas seulement des mots de passe, mais aussi des choses comme des adresses IP ou des clés de cryptage.

Existe-t-il des JVM commerciales? qui offre une fonctionnalité comme la manipulation prioritaire de certains objets? (J'imagine que cela violerait la spécification Java, mais j'ai pensé que je demanderais juste au cas où je me trompe.)

36
demandé sur Community 2011-06-25 00:22:25

5 réponses

Edit: en fait, je viens d'avoir trois idées qui peuvent effectivement fonctionner - pour différentes valeurs de "travail" au moins.

le premier plus ou moins documenté serait ByteBuffer.allocateDirect! Comme je le comprends allocateDirect allocates le tampon en dehors du tas de java habituel donc ne sera pas copié autour. Je ne peux pas trouver de garanties dures sur le fait de ne pas se faire Copier dans toutes les situations, mais pour le Hotspot VM actuel qui est en fait le cas (c'est-à-dire qu'il est alloué en un tas supplémentaire) et je suppose que cela restera ainsi.

le second utilise le soleil.misc.paquet dangereux - qui, comme son nom l'indique, a des problèmes assez évidents, mais au moins ce serait assez indépendant de la VM utilisée - soit il est supporté (et il fonctionne), soit il ne l'est pas (et vous obtenez des erreurs de liens). Le problème est, que le code pour utiliser ce truc va devenir horriblement compliqué assez rapidement (seul obtenir une variable dangereuse est non trivial).

La troisième serait d'allouer beaucoup, beaucoup, beaucoup plus grande taille que ce qui est réellement nécessaire, de sorte que l'objet est attribué à l'ancienne génération tas de commencer:

l-XX: Pretentiuresizethreshold= that can be set to limit the importance des allocations pour les jeunes génération. L'affectation de plus de cela ne sera pas tenté dans le la jeune génération et ainsi seront d'attribution de l'ancienne génération.

Eh bien l'inconvénient de cette solution est évident je pense (taille par défaut semble être d'environ 64kb).

. .

de toute façon ici l'ancienne réponse:

Yep comme je vois qu'il vous est impossible de garantir que les données stockées sur le tas est 100% enlevée sans laisser une copie (c'est vrai même si vous ne voulez pas une solution générale, mais celui qui va travailler avec les dire de l'actuel Hotspot VM et son défaut ramasseurs d'ordures).

comme dit dans votre billet relié ( ici ), le ramasseur d'ordures rend à peu près impossible de garantir cela. En fait, contrairement à ce que dit le post, le problème ici n'est pas le GC générationnel, mais le fait que la VM Hotspot (et maintenant nous sommes spécifiques à l'implémentation) utilise une sorte de gc Stop & Copy pour sa jeune génération par défaut.

cela signifie que dès qu'une collecte des ordures se produit entre le stockage du mot de passe dans le tableau de char et la mise à zéro il vous obtiendrez une copie des données qui seront écrasées seulement dès que le prochain GC se produit. Notez que maintenir un objet aura exactement le même effet, mais au lieu de le copier dans l'espace, il est copié dans le tas de l'ancienne génération-nous nous retrouvons avec une copie des données dans l'espace qui n'est pas écrasée.

pour éviter ce problème, nous avons à peu près besoin d'un moyen de garantir qu'il N'y a pas de collecte des ordures entre le stockage le mot de passe et le zéroer ou que le tableau de char est stocké à partir de l'get go dans le tas de l'ancienne génération. Notez également que cela dépend de l'internas de la VM Hotspot qui peut très bien changer (en fait, il y a différents collecteurs de déchets où beaucoup plus de copies peuvent être générées; iirc la VM Hotspot supporte un GC concurrent en utilisant un algorithme train). "heureusement", il est impossible de garantir l'un ou l'autre de ces (afaik chaque méthode appel/retour introduit un point de sécurité!), vous n'avez même pas tentés d'essayer (surtout que je ne vois pas de moyen de vérifier que le JIT n'est pas d'optimiser la réinitialisation de là) ;)

semble comme le seul moyen de garantir que les données sont stockées dans un seul endroit est d'utiliser le JNI pour elle.

PS: notez que bien que ce qui précède ne soit vrai que pour le tas, vous ne pouvez rien garantir de plus pour la pile (le JIT optimisera probablement écrit sans lire à la pile loin, donc quand vous revenez de la fonction les données seront toujours sur la pile)

4
répondu Voo 2017-05-23 12:06:48

Dites à vos collègues que c'est une cause désespérée. Quel est le noyau de la prise tampons, juste pour commencer.

si vous ne pouvez pas empêcher les programmes indésirables d'espionner la mémoire sur votre machine, les mots de passe sont compromis. Période.

3
répondu bmargulies 2011-06-25 19:50:52

bizarre, je n'ai jamais pensé à quelque chose comme ça.

Ma première idée serait de faire un char[100] pour stocker votre mot de passe. Mettre en il y, pour quoi, et puis de faire une boucle pour chaque char à vide.

le problème est que le mot de passe se transformerait à un moment donné en une chaîne à l'intérieur du pilote de la base de données, qui pourrait vivre en mémoire pendant 0 à l'infini des secondes.

ma deuxième idée serait d'avoir tout authentification faite par une sorte d'appel JNI à C, mais ce serait vraiment difficile si vous essayez d'utiliser quelque chose comme JDBC....

2
répondu bwawok 2011-06-24 21:02:18

juste à côté, mais certains environnements les libs de sécurité java core utilisent char[] donc il peut être mis à zéro. J'imagine que vous n'obtenez pas une garantie tho.

0
répondu teknopaul 2018-01-08 13:05:59

j'ai essayé de résoudre certains problèmes similaires avec les qualifications.

Jusqu'à présent, ma seule réponse est de "ne pas utiliser les cordes du tout pour des secrets". Les cordes sont confortables à utiliser et à stocker en termes humains, mais les ordinateurs peuvent bien travailler avec des tableaux d'octets. Même les primitives de cryptage fonctionnent avec byte[].

lorsque vous n'avez plus besoin du mot de passe, remplissez simplement le tableau avec des zéros et ne laissez pas le GC inventer de nouvelles façons de réutiliser votre secret.

dans un autre thread ( pourquoi les chaînes ne peuvent-elles pas être mutables en Java et .NET? ) ils font l'hypothèse qu'il s'agit d'une vue très courte. Que les chaînes sont immuables pour des raisons de sécurité; ce qui n'a pas été conçu est que les problèmes opérationnels ne sont pas toujours les seuls qui existent et que la sécurité a parfois besoin de flexibilité et/ou de support pour être efficace, un support n'existe pas dans le Java natif.

à compléter. Comment pourrions-nous lire un mot de passe sans utiliser de chaînes? Bien. .. soyez créatif et n'utilisez pas des choses comme L'Android EditText avec mot de passe de type input, qui n'est tout simplement pas assez sécurisé et vous oblige à aller aux chaînes.

-1
répondu user2583872 2017-05-23 12:33:18