web-dev-qa-db-fra.com

Différence entre Java.util.Random et Java.security.SecureRandom

Mon équipe a reçu un code côté serveur (en Java) qui génère des jetons aléatoires et j'ai une question à propos de la même chose -

Le but de ces jetons est assez sensible - utilisé pour l'identifiant de session, les liens de réinitialisation de mot de passe, etc. Ils doivent donc être cryptographiquement aléatoires pour éviter que quelqu'un les devine ou les forçent brutalement. Le jeton est un "long", donc il est long de 64 bits.

Le code utilise actuellement la classe Java.util.Random pour générer ces jetons. La documentation ([ http://docs.Oracle.com/javase/7/docs/api/Java/util/Random.htmlunset1] ) pour Java.util.Random indique clairement ce qui suit: 

Les instances de Java.util.Random ne sont pas sécurisées par cryptographie. Pensez plutôt à utiliser SecureRandom pour obtenir un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé à utiliser par des applications sensibles à la sécurité.

Cependant, la manière dont le code utilise actuellement Java.util.Random est la suivante: il instancie la classe Java.security.SecureRandom, puis utilise la méthode SecureRandom.nextLong() pour obtenir le germe utilisé pour instancier la classe Java.util.Random. Ensuite, il utilise la méthode Java.util.Random.nextLong() pour générer le jeton.

Donc, ma question maintenant: est-il toujours dangereux de savoir que le Java.util.Random est utilisé avec Java.security.SecureRandom? Dois-je modifier le code pour qu'il utilise exclusivement Java.security.SecureRandom pour générer les jetons?

Actuellement, la graine de code est la Random une fois au démarrage

192
user967973

L'implémentation Oracle JDK 7 standard utilise ce qu'on appelle un générateur de congruences linéaires pour générer des valeurs aléatoires dans Java.util.Random

Tiré du code source Java.util.Random (JDK 7u2), à partir d'un commentaire sur la méthode protected int next(int bits), qui est celui qui génère les valeurs aléatoires:

Ceci est un générateur de nombres pseudo-aléatoires congruentiels linéaires, comme défini par D. H. Lehmer et décrit par Donald E. Knuth dans L'art de la programmation informatique, Volume 3: Algorithmes Seminairessection 3.2.1.

Prévisibilité des générateurs linéaires congruentiels

Hugo Krawczyk a écrit un très bon article sur la manière de prédire ces LCG ("Comment prédire les générateurs congruentiels"). Si vous êtes chanceux et intéressé, vous pouvez toujours trouver une version gratuite et téléchargeable sur le Web. Et de nombreuses autres recherches montrent clairement que vous devez jamais utiliser un groupe de contrôle d'accès à des fins critiques pour la sécurité. Cela signifie également que vos nombres aléatoires sont prévisibles pour le moment, ce que vous ne voulez pas pour les identifiants de session, etc.

Comment briser un générateur linéaire congruentiel

L'hypothèse selon laquelle un attaquant devrait attendre que le LCG se répète après un cycle complet est fausse. Même avec un cycle optimal (le module m dans sa relation de récurrence), il est très facile de prédire les valeurs futures en beaucoup moins de temps qu’un cycle complet. Après tout, ce ne sont que de nombreuses équations modulaires qu'il faut résoudre, ce qui devient facile dès que vous avez observé suffisamment de valeurs de sortie du LCG. 

La sécurité ne s'améliore pas avec une "meilleure" graine. Peu importe que vous ayez une valeur aléatoire générée par SecureRandom ou même que vous produisiez cette valeur en lançant plusieurs fois un dé. 

Un attaquant calculera simplement la valeur de départ à partir des valeurs de sortie observées. Cela prend beaucoup moins de temps que 2 ^ 48 dans le cas de Java.util.Random. Les mécréants peuvent essayer cette experience , où il est montré que vous pouvez prédire les futurs sorties Random en observant seulement deux (!) Valeurs de sortie dans le temps, environ 2 ^ 16. Il ne faut même pas une seconde sur un ordinateur moderne pour prédire la sortie de vos nombres aléatoires pour le moment.

Conclusion

Remplacez votre code actuel. Utilisez SecureRandom exclusivement. Alors au moins, vous aurez un peu de garantie que le résultat sera difficile à prédire. Si vous voulez les propriétés d'un PRNG cryptographiquement sécurisé (dans votre cas, c'est ce que vous voulez), vous devez utiliser uniquement SecureRandom. Être intelligent pour changer la façon dont il était censé être utilisé aura presque toujours pour résultat quelque chose de moins sécurisé ...

222
emboss

Un aléatoire n'a que 48 bits, alors que SecureRandom peut avoir jusqu'à 128 bits. Donc, les chances de répéter dans sécurerandom est très faible.

Random utilise le system clock en tant que graine/ou pour générer la graine. Ils peuvent donc être reproduits facilement si l’attaquant connaît l’heure à laquelle la graine a été générée. Mais SecureRandom prend Random Data de votre os (il peut s’agir d’un intervalle entre les touches, etc. - la plupart des collectent ces données dans des fichiers - /dev/random and /dev/urandom in case of linux/solaris) et l’utilise comme source. 
Ainsi, si la taille du jeton est correcte (en cas d’Aléatoire), vous pouvez continuer à utiliser votre code sans aucune modification, puisque vous utilisez SecureRandom pour générer le germe. Mais si vous voulez des jetons plus gros (qui ne peuvent pas être soumis à brute force attacks), allez avec SecureRandom - 
En cas de hasard, il suffit de 2^48 tentatives; avec les processeurs avancés d'aujourd'hui, il est possible de le décomposer en un temps pratique. Mais, pour la sécurité, 2^128 sera nécessaire, ce qui prendra des années et des années pour atteindre le seuil de rentabilité avec les machines modernes.

Voir this link pour plus de détails .
MODIFIER
Après avoir lu les liens fournis par @emboss, il est clair que le germe, même aléatoire, ne devrait pas être utilisé avec Java.util.Random. Il est très facile de calculer la graine en observant la sortie.

Go for SecureRandom - Utilisez Native PRNG (comme indiqué dans le lien ci-dessus) car il prend des valeurs aléatoires dans le fichier /dev/random pour chaque appel de nextBytes(). De cette manière, un attaquant observant la sortie ne pourra rien distinguer à moins de contrôler le contenu du fichier /dev/random (ce qui est très peu probable). 
L’algorithme sha1 prng calcule la graine une seule fois et si votre VM fonctionne pendant plusieurs mois avec la même graine, elle pourrait être déchiffrée par un attaquant qui observe passivement la sortie.

NOTE - Si vous appelez la nextBytes() plus rapidement que votre système d'exploitation ne peut écrire des octets aléatoires (entropie) dans le /dev/random, vous pourriez avoir des problèmes lorsque vous utilisez NATIVE PRNG. Dans ce cas, utilisez une instance SHA1 PRNG de SecureRandom et, toutes les quelques minutes (ou certains intervalles), insérez cette instance avec la valeur provenant de nextBytes() d'une instance NATIVE PRNG de SecureRandom. L'exécution parallèle de ces deux opérations garantira que vous ensemencez régulièrement avec de vraies valeurs aléatoires, tout en évitant d'épuiser l'entropie obtenue par le système d'exploitation.

68
Ashwin

Si vous exécutez deux fois Java.util.Random.nextLong() avec la même graine, le même nombre sera généré. Pour des raisons de sécurité, vous souhaitez vous en tenir à Java.security.SecureRandom car il est beaucoup moins prévisible.

Les 2 classes sont similaires, je pense que vous devez simplement changer Random en SecureRandom avec un outil de refactoring et la plupart de votre code existant fonctionnera.

8
Mualig

Si changer votre code existant est une tâche abordable, je vous suggère d'utiliser la classe SecureRandom comme suggéré dans Javadoc. 

Même si vous trouvez que l'implémentation de la classe Random utilise la classe SecureRandom en interne. vous ne devez pas prendre pour acquis que: 

  1. D'autres implémentations VM font la même chose. 
  2. L'implémentation de la classe Random dans les futures versions du JDK utilise toujours la classe SecureRandom 

Il est donc préférable de suivre la suggestion de documentation et d’utiliser directement SecureRandom.

3
Andrea Parodi

La graine n'a pas de sens. Un bon générateur aléatoire diffère par le nombre de primen choisi. Chaque générateur aléatoire commence à partir d'un nombre et itère à travers un «anneau». Ce qui signifie que vous venez d'un nombre à l'autre, avec l'ancienne valeur interne. Mais au bout d'un moment, vous reprenez le début et recommencez. Donc, vous exécutez des cycles. (la valeur renvoyée par un générateur aléatoire n'est pas la valeur interne)

Si vous utilisez un nombre premier pour créer un anneau, tous les numéros de cet anneau sont choisis avant de terminer un cycle complet parmi tous les nombres possibles. Si vous prenez des nombres non premiers, tous les nombres ne sont pas choisis et vous obtenez des cycles plus courts.

Des nombres premiers plus élevés signifient des cycles plus longs, avant de revenir au premier élément. Ainsi, le générateur aléatoire sécurisé a simplement un cycle plus long, avant de revenir au début, c’est pourquoi il est plus sûr. Vous ne pouvez pas prédire la génération de nombre aussi facilement qu'avec des cycles plus courts.

Avec d’autres mots: vous devez tout remplacer.

2
Nicolas

L'implémentation de référence actuelle de Java.util.Random.nextLong() effectue deux appels à la méthode next(int) qui directement expose 32 bits de la valeur de départ actuelle:

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom Word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

Les 32 bits supérieurs du résultat de nextLong() sont les bits de la graine à la fois. Puisque la largeur de la graine est de 48 bits (dit le javadoc), il suffit * d’itérer sur les 16 bits restants (soit seulement 65.536 tentatives) pour déterminer la graine qui a produit le second 32 bits.

Une fois que la graine est connue, tous les jetons suivants peuvent facilement être calculés.

En utilisant la sortie de nextLong() directement, en partie le secret du PNG, à un degré tel que le secret entier puisse être calculé avec très peu d'efford. Dangereux!

* Si le deuxième bit 32 est négatif, des efforts sont nécessaires, mais on peut le savoir.

1
Matt

Je vais essayer d'utiliser des mots très simples pour que vous puissiez facilement comprendre la différence entre Random et secureRandom et l'importance de SecureRandom Class.

Vous êtes-vous déjà demandé comment OTP (mot de passe à usage unique) est généré? Pour générer un OTP, nous utilisons aussi les classes Random et SecureRandom. Maintenant, pour que votre OTP soit fort, SecureRandom est préférable, car il a fallu 2 ^ 128 tentatives pour casser le OTP, ce qui est presque impossible avec la machine actuelle, mais si vous utilisez Random Class, votre OTP peut être craqué par quelqu'un qui peut endommager vos données, car il a juste 2 ^ 48 essayer, à craquer. 

0
sachin pathak