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 à ce sujet -
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 ne les devine ou les force brutalement. Le jeton est un "long" donc il est long de 64 bits.
Le code utilise actuellement la java.util.Random
classe pour générer ces jetons. La documentation de java.util.Random
indique clairement ce qui suit:
Les instances de java.util.Random ne sont pas cryptographiquement sécurisées. Envisagez plutôt d'utiliser SecureRandom pour obtenir un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé à utiliser par les applications sensibles à la sécurité.
Cependant, la façon dont le code utilise actuellement java.util.Random
est la suivante: il instancie la java.security.SecureRandom
classe, puis utilise la SecureRandom.nextLong()
méthode pour obtenir la valeur de départ utilisée pour instancier la java.util.Random
classe. Ensuite, il utilise une java.util.Random.nextLong()
méthode pour générer le jeton.
Donc, ma question maintenant - est-il encore peu sûr étant donné que le java.util.Random
semis est utilisé java.security.SecureRandom
? Dois-je modifier le code afin qu'il utilise java.security.SecureRandom
exclusivement pour générer les jetons?
Actuellement, la graine de code est Random
une fois au démarrage
la source
Random
une fois au démarrage, ou en amène-t-il un nouveau pour chaque jeton? J'espère que c'est une question stupide, mais j'ai pensé vérifier.long
ou lesdouble
valeurs.Réponses:
L'implémentation standard d'Oracle JDK 7 utilise ce qu'on appelle un générateur linéaire congruentiel pour produire des valeurs aléatoires dans
java.util.Random
.Tiré du
java.util.Random
code source (JDK 7u2), d'un commentaire sur la méthodeprotected int next(int bits)
, qui est celle qui génère les valeurs aléatoires:Prévisibilité des générateurs linéaires congruentiels
Hugo Krawczyk a écrit un très bon article sur la façon dont ces LCG peuvent être prédits ("Comment prédire les générateurs congruentiels"). Si vous êtes chanceux et intéressé, vous pouvez toujours en trouver une version gratuite et téléchargeable sur le Web. Et il y a beaucoup plus de recherches qui montrent clairement que vous ne devriez jamais utiliser un LCG à des fins critiques pour la sécurité. Cela signifie également que vos nombres aléatoires sont prévisibles en ce moment, ce que vous ne voulez pas pour les ID de session et autres.
Comment casser 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 n'est qu'un tas d'équations modulaires qui doivent être résolues, 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 semiez avec une valeur aléatoire générée
SecureRandom
ou même produisiez la valeur en lançant un dé plusieurs fois.Un attaquant calculera simplement la graine à 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 expérience , où il est montré que vous pouvez prédire lesRandom
sorties futures en n'observant que 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 dès maintenant.Conclusion
Remplacez votre code actuel. Utilisez
SecureRandom
exclusivement. Ensuite, au moins, vous aurez une petite garantie que le résultat sera difficile à prévoir. Si vous voulez les propriétés d'un PRNG cryptographiquement sécurisé (dans votre cas, c'est ce que vous voulez), alors vous devez y allerSecureRandom
uniquement. Être intelligent pour changer la façon dont il était censé être utilisé entraînera presque toujours quelque chose de moins sûr ...la source
Random
est cassé - il devrait simplement être utilisé dans différents scénarios. Bien sûr, vous pouvez toujours utiliser SecureRandom. Mais en général,SecureRandom
est nettement plus lent que purRandom
. Et il y a des cas où vous n'êtes intéressé que par de bonnes propriétés statistiques et d'excellentes performances, mais vous ne vous souciez pas vraiment de la sécurité: les simulations Monte-Carlo en sont un bon exemple. J'ai fait des commentaires à ce sujet dans une réponse similaire , vous le trouverez peut-être utile.Un aléatoire n'a que 48 bits alors que SecureRandom peut avoir jusqu'à 128 bits. Ainsi, les chances de répéter dans securerandom sont très faibles.
Random utilise le
system clock
comme 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 prendRandom Data
de votreos
(ils peuvent être un intervalle entre les frappes, etc. - la plupart des OS collectent ces données les stockent dans des fichiers -/dev/random and /dev/urandom in case of linux/solaris
) et les utilise comme graine.Donc, si la petite taille du jeton est correcte (dans le cas de Random), vous pouvez continuer à utiliser votre code sans aucune modification, car vous utilisez SecureRandom pour générer la graine. Mais si vous voulez des jetons plus gros (auxquels vous ne pouvez pas faire face
brute force attacks
), optez pour SecureRandom -Dans le cas où des
2^48
tentatives aléatoires sont nécessaires, avec les processeurs avancés d'aujourd'hui, il est possible de les casser en temps pratique. Mais pour plus de sécurité, des2^128
tentatives seront nécessaires, ce qui prendra des années et des années pour atteindre l'équilibre avec les machines avancées d'aujourd'hui.Voir ce lien pour plus de détails.
EDIT
Après avoir lu les liens fournis par @emboss, il est clair que la graine, aussi aléatoire soit-elle, ne doit pas être utilisée avec java.util.Random. Il est très facile de calculer la graine en observant la sortie.
Optez pour SecureRandom - Utilisez Native PRNG (comme indiqué dans le lien ci-dessus) car il prend des valeurs aléatoires du
/dev/random
fichier pour chaque appel ànextBytes()
. De cette façon, un attaquant observant la sortie ne pourra rien distinguer à moins qu'il ne contrôle le contenu du/dev/random
fichier (ce qui est très peu probable).L' algorithme sha1 prng calcule la graine une seule fois et si votre machine virtuelle fonctionne pendant des mois en utilisant la même chose graine, il pourrait être craqué par un attaquant qui observe passivement la sortie.
REMARQUE - Si vous appelez le
nextBytes()
plus rapidement possible, votre système d'exploitation peut écrire des octets aléatoires (entropie) dans le/dev/random
, vous pourriez avoir des problèmes lors de l'utilisation de NATIVE PRNG . Dans ce cas, utilisez une instance SHA1 PRNG de SecureRandom et toutes les quelques minutes (ou un certain intervalle), amorcez cette instance avec la valeur denextBytes()
d'une instance PRNG NATIVE de SecureRandom. L'exécution de ces deux en parallèle garantira que vous semez régulièrement avec de vraies valeurs aléatoires, tout en n'épuisant pas l'entropie obtenue par le système d'exploitation.la source
Random
, l'OP ne devrait pas être utiliséRandom
du tout./proc/sys/kernel/random/entropy_avail
et vérifiez avec quelques vidages de fils qu'il n'y a pas trop de temps à lire/dev/random
Si vous exécutez deux fois
java.util.Random.nextLong()
avec la même graine, elle produira le même nombre. Pour des raisons de sécurité, vous souhaitez vous en tenirjava.security.SecureRandom
car c'est beaucoup moins prévisible.Les 2 classes sont similaires, je pense que vous avez juste besoin de changer
Random
pourSecureRandom
un outil de refactoring et la plupart de votre code existant fonctionnera.la source
Si la modification de 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 considérer comme acquis que:
Il est donc préférable de suivre la suggestion de documentation et d'aller directement avec SecureRandom.
la source
java.util.Random
implémentation était utilisée enSecureRandom
interne, elle disait que leur code utilisaitSecureRandom
pour amorcer leRandom
. Pourtant, je suis d'accord avec les deux réponses jusqu'à présent; il est préférable d'utiliserSecureRandom
pour éviter une solution explicitement déterministe.L'implémentation de référence actuelle de
java.util.Random.nextLong()
effectue deux appels à la méthodenext(int)
qui expose directement 32 bits de la graine actuelle:Les 32 bits supérieurs du résultat de
nextLong()
sont les bits de la graine à ce moment. Étant donné que 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 essais) pour déterminer la graine qui a produit le second 32 bits.Une fois la graine connue, tous les jetons suivants peuvent être facilement calculés.
En utilisant la sortie de
nextLong()
directement, en partie le secret du PNG à un degré tel que le secret entier peut être calculé avec très peu d'effort. Dangereux!* Un effort est nécessaire si les deuxièmes 32 bits sont négatifs, mais on peut le découvrir.
la source
La graine n'a pas de sens. Un bon générateur aléatoire diffère par le nombre 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 après un certain temps, vous atteignez à nouveau le début et recommencez. Vous exécutez donc des cycles. (la valeur de retour d'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 nombres de cet anneau sont choisis, avant de terminer un cycle complet à travers 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 à nouveau au premier élément. Ainsi, le générateur aléatoire sécurisé a juste un cycle plus long, avant d'atteindre à nouveau le début, c'est pourquoi il est plus sûr. Vous ne pouvez pas prédire la génération de nombres aussi facilement qu'avec des cycles plus courts.
En d'autres termes: vous devez tout remplacer.
la source
J'essaierai d'utiliser des mots très basiques 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 unique) est généré? Pour générer un OTP, nous utilisons également la classe Random et SecureRandom. Maintenant, pour rendre votre OTP fort, SecureRandom est meilleur car il a fallu 2 ^ 128 essais pour casser l'OTP, ce qui est presque impossible par la machine actuelle, mais s'il est utilisé Random Class, votre OTP peut être piraté par quelqu'un qui peut nuire à vos données parce qu'il a pris juste 2 ^ 48 essayer de craquer.
la source