web-dev-qa-db-fra.com

Pourquoi Math.random () n'est pas conçu pour être cryptographiquement sécurisé?

La fonction JavaScript Math.random() est conçue pour renvoyer une seule valeur à virgule flottante IEEE n telle que 0 ≤ n = <1. Il est (ou du moins devrait être) largement connu que la sortie est pas cryptographiquement sécurisée. La plupart des implémentations modernes utilisent l'algorithme XorShift128 + qui peut être facilement cassé . Comme il n'est pas rare du tout que les gens l'utilisent par erreur lorsqu'ils ont besoin d'un meilleur caractère aléatoire, pourquoi les navigateurs ne le remplacent-ils pas par un CSPRNG? Je sais que Opera le fait *, au moins. Le seul raisonnement auquel je pourrais penser serait que XorShift128 + est plus rapide qu'un CSPRNG, mais sur des ordinateurs modernes (et même pas si modernes), il serait trivial de produire des centaines de mégaoctets par seconde en utilisant ChaCha8 ou AES-CTR. Celles-ci sont souvent suffisamment rapides pour qu'une implémentation bien optimisée ne soit goulot d'étranglement que par la vitesse de la mémoire du système. Même une implémentation non optimisée de ChaCha20 est extrêmement rapide sur toutes les architectures, et ChaCha8 est plus de deux fois plus rapide.

Je comprends qu'il ne pourrait pas être redéfini comme un CSPRNG car la norme ne donne explicitement aucune garantie d'adéquation à une utilisation cryptographique, mais il ne semble pas y avoir d'inconvénient pour les fournisseurs de navigateurs à le faire volontairement. Cela réduirait l'impact des bogues dans un grand nombre d'applications Web sans violer la norme (cela nécessite uniquement que la sortie soit arrondie au plus proche IEEE 754 nombres), diminuant les performances ou cassant la compatibilité avec des applications Web.


EDIT: Quelques personnes ont souligné que cela pourrait potentiellement amener les gens à abuser de cette fonction même si la norme dit que vous ne pouvez pas vous y fier pour la sécurité cryptographique. À mon avis, il existe deux facteurs opposés qui déterminent si l'utilisation d'un CSPRNG constituerait un avantage net en matière de sécurité:

  1. Faux sentiment de sécurité - Le nombre de personnes qui autrement utiliseraient une fonction conçue à cet effet, comme window.crypto , décidez à la place d'utiliser Math.random() car il se trouve qu'il est cryptographiquement sécurisé sur la plate-forme cible prévue.

  2. Sécurité opportuniste - Le nombre de personnes qui ne connaissent pas mieux et utilisent de toute façon Math.random() pour les applications sensibles qui seraient protégées contre leur propre erreur. Évidemment, il serait préférable de les éduquer à la place, mais ce n'est pas toujours possible.

Il semble sûr de supposer que le nombre de personnes qui seraient protégées de leurs propres erreurs dépasserait largement le nombre de personnes endormies dans un faux sentiment de sécurité.

* Comme le souligne CodesInChaos, ce n'est plus vrai maintenant que Opera est basé sur Chromium.


Plusieurs navigateurs majeurs ont reçu des rapports de bogues suggérant de remplacer cette fonction par une alternative sécurisée sur le plan cryptographique, mais aucune des modifications sécurisées suggérées n'a abouti:

Les arguments for le changement correspondent essentiellement au mien. Les arguments contre cela varient d'une réduction des performances sur les microbenchmarks (avec peu d'impact dans le monde réel) à des malentendus et des mythes, tels que l'idée incorrecte qu'un CSPRNG s'affaiblit avec le temps à mesure que plus d'aléatoire est généré. À la fin, Chromium a créé un tout nouvel objet cryptographique et Firefox a remplacé son RNG par l'algorithme XorShift128 +. La fonction Math.random() reste entièrement prévisible.

228
forest

J'ai été l'un des implémenteurs de JScript et membre du comité ECMA du milieu à la fin des années 1990, je peux donc fournir ici une perspective historique.

La fonction JavaScript Math.random () est conçue pour renvoyer une valeur à virgule flottante entre 0 et 1. Il est largement connu (ou du moins devrait l'être) que la sortie n'est pas sécurisée cryptographiquement.

Tout d'abord: la conception de nombreuses API RNG est horrible . Le fait que la classe aléatoire .NET puisse être utilisée de manière triviale de plusieurs manières pour produire de longues séquences du même nombre est affreux. Une API où la façon naturelle de l'utiliser est également la mauvaise façon est une API "fosse d'échec"; nous voulons que nos API soient des gages de succès, où la voie naturelle et la bonne voie sont les mêmes.

Je pense qu'il est juste de dire que si nous savions alors ce que nous savons maintenant, l'API aléatoire JS serait différente. Même des choses simples comme changer le nom en "pseudo-aléatoire" aideraient, car comme vous le constatez, dans certains cas, les détails d'implémentation sont importants. Au niveau architectural, il y a de bonnes raisons pour que vous souhaitiez que random() soit une fabrique qui renvoie un objet représentant une séquence aléatoire ou pseudo-aléatoire, plutôt que de simplement renvoyer des nombres. Etc. Leçons apprises.

Deuxièmement, rappelons-nous quel était l'objectif fondamental de la conception de JS dans les années 1990. Faites danser le singe lorsque vous déplacez la souris . Nous pensions que les scripts d'expression en ligne étaient normaux, nous pensions que les blocs de script de deux à dix lignes étaient courants, et l'idée que quelqu'un pourrait écrire un cent lignes de script sur une page était vraiment très inhabituel. Je me souviens de la première fois que j'ai vu un programme JS de dix mille lignes et ma première question aux personnes qui me demandaient de l'aide parce que c'était si lent par rapport à leur version C++ était une version de "êtes-vous fou?! 10KLOC JS?! "

L'idée que n'importe qui aurait besoin de crypto-aléatoire dans JS était tout aussi folle. Vous avez besoin de vos mouvements de singe pour être imprévisible? Peu probable.

N'oubliez pas non plus que c'était au milieu des années 1990. Si vous n'y étiez pas, je peux vous dire que c'était un monde très différent qu'aujourd'hui en ce qui concerne la crypto ... Voir exportation de la cryptographie .

Je n'aurais même pas envisagé mettre le caractère aléatoire de la force de crypto dans tout ce qui est livré avec le navigateur sans obtenir une énorme quantité de conseils juridiques de l'équipe MSLegal. Je ne voulais pas toucher à la crypto avec un poteau de dix pieds dans un monde où le code d'expédition était considéré pour exporter des munitions vers les ennemis de l'État . Cela semble fou du point de vue d'aujourd'hui, mais c'était le monde qui était .

pourquoi les navigateurs ne le remplacent-ils pas par un CSPRNG?

Les auteurs de navigateurs n'ont pas à fournir de raison pour NE PAS effectuer de modification. Les changements coûtent de l'argent, et ils enlèvent l'effort de meilleurs changements; chaque changement a un énorme coût d'opportunité.

Au lieu de cela, vous devez fournir un argument non seulement pourquoi faire le changement est une bonne idée, mais pourquoi c'est la meilleure utilisation possible de leur temps. Il s'agit d'un petit changement pour le prix.

Je comprends qu'il ne pourrait pas être redéfini en tant que CSPRNG car la norme ne donne explicitement aucune garantie d'adéquation à une utilisation cryptographique, mais il ne semble pas y avoir d'inconvénient à le faire de toute façon

L'inconvénient est que les développeurs sont encore dans une situation où ils ne peuvent pas savoir de manière fiable si leur caractère aléatoire est une force cryptographique ou non, et peuvent encore plus facilement tomber dans le piège de s'appuyer sur une propriété qui n'est pas garanti par la norme. La modification proposée ne résout pas réellement le problème, qui est un problème de conception.

443
Eric Lippert

Parce qu'il existe en fait une alternative cryptographiquement sécurisée à Math.random():

window.crypto.getRandomValues(typedArray)

Cela permet au développeur d'utiliser le bon outil pour le travail. Si vous souhaitez générer de jolies images ou des butins pour votre jeu, utilisez la fonction rapide Math.random(). Lorsque vous avez besoin de nombres aléatoires sécurisés cryptographiquement, utilisez le window.crypto.

116
Philipp

JavaScript (JS) a été inventé en 1995.

  1. Potentiellement illégal: la cryptographie était encore sous contrôle strict des exportations en 1995, donc un bon CSPRNG n'aurait peut-être même pas été légal de le distribuer dans un navigateur.
  2. Performances: historiquement, les CSPRNG (générateurs de nombres pseudo-aléatoires cryptographiquement sécurisés) sont beaucoup plus lents que les PRNG, alors pourquoi utiliser un CSPRNG par défaut?
  3. Aucun état d'esprit de sécurité: en 1995, SSL venait d'être inventé. Pratiquement aucun serveur ne le supportait encore, Internet était composé de lignes téléphoniques et il était utilisé pour les forums publics ( BBSes ) et MUDs . Le chiffrement était quelque chose pour les agences de renseignement.
  4. Pas besoin: les applications web n'existaient pas encore car JavaScript venait d'être inventé. Il a été conçu comme un langage interprété (donc lent) pour aider les pages à être plus dynamiques. Personne n'aurait pensé à utiliser un CSPRNG lent (et à peine existant) par défaut pour une fonction aléatoire.
  5. Si peu de besoin, en fait, qu'il n'y avait pas d'alternative: JavaScript n'avait même pas d'API généralement prise en charge pour un CSPRNG jusqu'en décembre 2013, donc une cryptographie appropriée dans les applications Web était à peine possible jusqu'à il y a quelques années.
  6. Cohérence: plutôt que de changer une fonction existante pour avoir une signification différente, ils ont créé une nouvelle fonction avec un nom différent. Vous pouvez accéder au CSPRNG maintenant via crypto.getRandomValues.

En résumé: héritage, mais aussi rapidité et cohérence . Les PRNG non sécurisés sont encore beaucoup plus rapides car vous ne pouvez pas supposer que tout le matériel prend en charge AES, ni dépendre de la disponibilité ou de la sécurité de RDRAND.

Personnellement, je pense qu'il est temps de remplacer toutes les fonctions aléatoires par des CSPRNG et de renommer les fonctions plus rapides et non sécurisées en quelque chose comme fast_insecure_random(). Ils ne devraient être nécessaires qu'aux scientifiques ou à d'autres personnes qui effectuent des simulations qui nécessitent de nombreux nombres aléatoires, mais où la prévisibilité du RNG n'est pas un problème. Mais pour une fonction avec deux décennies d'histoire, où une alternative n'existe que depuis quatre ans maintenant (en 2018), je peux comprendre pourquoi nous n'en sommes pas encore là.

69
Luc

C'est trop long pour un commentaire.

Je crois qu'il y a une prémisse défectueuse dans votre question:

sur des ordinateurs modernes (et même pas si modernes), il serait trivial de produire des centaines de mégaoctets par seconde en utilisant ChaCha8 ou AES-CTR

Vous pensez à un navigateur de bureau sur une machine connectée à CA ou à un ordinateur portable avec une grosse batterie honkin 10Ah.

Nous vivons dans un monde de plus en plus mobile, et bien que les appareils mobiles de nos jours soient assez puissants, ils ont deux contraintes importantes: la chaleur et la durée de vie de la batterie. Contrairement à un processeur de bureau qui peut facilement atteindre 100 ° C, vous ne pouvez pas brûler la main de l'utilisateur du smartphone. Et les batteries de téléphone ne tiennent généralement que peut-être 1/3 autant qu'un ordinateur portable (si vous êtes chanceux). Il n'y a tout simplement aucune bonne raison d'ajouter la génération de chaleur/consommation d'énergie supplémentaire si vous n'en avez pas besoin.

20
Jared Smith

La raison principale est qu'il existe une alternative à Math.random(): voir la réponse de Philipp . Ainsi, quiconque a besoin d'une cryptographie forte peut l'avoir, et ceux qui n'en ont pas peuvent économiser du temps et de l'énergie (batterie).

Mais en supposant que vous ayez demandé, "pourquoi, même s'il existe une alternative plus forte, les développeurs n'ont-ils pas mis à jour Math.random () de la même façon - c'est-à-dire, rendu aléatoire () un dérivé de getRandomValues ​​() - afin de renforcer automatiquement beaucoup d'applications là-bas? " - alors je ne pense pas que cela soit vraiment responsable avec confiance, sauf par les développeurs qui ont pris la décision (mise à jour: et comme le destin l'aurait voulu, nous avons une telle réponse ).

En principe - comme vous l'avez déjà dit - il n'y a pas de raison forte .

De plus, la plupart des équipes de développement ont un arriéré important de choses qui sont plus urgentes à faire; et même un petit changement en apparence comme celui-ci nécessite des tests, une régression et aller à l'encontre de la règle d'or " Si ce n'est pas cassé, ne le réparez pas", une forme plus forte du critère YAGNI .

16
LSerni

Les nombres aléatoires et les bits cryptorandom sont des animaux complètement différents. Ils ne sont même pas utilisés dans le même but. Si vous voulez un nombre aléatoire uniformément réparti entre 0 et 42, alors vous voulez une distribution uniforme sans schéma évident. Notez que si vous modifiez un nombre plus grand avec un plus petit, ce n'est pas exactement une distribution paire. Cet exemple est facile à voir pour un nombre aléatoire de 0 à 31 pris le mod 27. 0 à 4 apparaissent deux fois plus souvent que 5 à 31.

Jusqu'à ce que vous parliez de cryptorandom, la notion d'entropie n'est même pas discutée. Un peu d'entropie double l'espace de recherche pour deviner le nombre (pour l'utilisateur prévu des nombres).

Lorsque vous demandez des bits cryptorandom, vous demandez N bits d'entropie. Il n'est pas suffisant d'avoir un modèle non évident, car si une fonction qui le génère est découverte (peu importe la façon dont elle est compliquée), il y a en fait 0 bit d'entropie du point de vue de celui qui connaît cette fonction.

Un bon exemple de ceci est un générateur de nombres pseudo-aléatoires de type Fortuna. Vous chiffrez un numéro 1 avec une clé pour le premier nombre aléatoire (où le bloc de chiffrement est un grand nombre), puis chiffrez le numéro 2 avec une clé pour le deuxième nombre aléatoire, et ainsi de suite. En ce qui concerne l'utilisateur qui ne connaît pas la clé (de K bits) du chiffrement, un bloc de chiffrement parfait à N bits aura N bits d'entropie pour ce bloc.

Si vous en développez un million de bits de données pseudo-aléatoires, vous n'avez toujours que K bits d'entropie si vous continuez avec la même clé K. En d'autres termes: si vous aviez un livre de 1 million de bits que vous savoir ont été générés avec un seul chiffrement sous K, alors n'essayez pas de deviner tous les bits de flux de chiffrement. Il suffit de deviner la clé et de générer le flux de chiffrement à partir de celle-ci.

Ainsi, un générateur de nombres aléatoires est souvent un chiffre qui continue d'être re-semé avec plus d'aléatoire, car il peut être atteint. Par comparaison, un simple générateur de nombres aléatoires [0,1] ne peut pas avoir plus d'entropie que le nombre de bits du nombre; et aura généralement une distribution étrange qui n'est pas exactement ce que vous voulez aussi. Crypto a besoin de centaines de bits, lorsque les nombres à virgule flottante ne sont que de 32 ou 64 bits, et l'algorithme lui-même enlève une grande partie de l'entropie .... en supposant que vous voulez quelque chose de réparti uniformément à partir de [0..1], plutôt que de dire un représentation en virgule flottante composée de bits aléatoires. Je ne sais même pas quelle distribution cela aurait.

14
Rob

Vous avez en quelque sorte répondu à la question vous-même:

la norme ne donne explicitement aucune garantie d'aptitude à une utilisation cryptographique

Ainsi, plutôt que de modifier la mise en œuvre, l'accent devrait être mis sur l'éducation des développeurs à choisir le "bon outil pour le travail" ™.

Compte tenu de cela, et de la surcharge technique liée à la modification de la mise en œuvre d'une fonction couramment utilisée, ainsi que du fait qu'il existe déjà des solutions spécifiques à ce problème (voir la réponse de @Philipps), il n'y a aucune raison impérieuse d'effectuer le changement.

8
richzilla

La conception d'un langage de programmation doit prendre en compte de nombreuses choses. Les navigateurs sont très puissants et optimisent beaucoup le javascript aujourd'hui. Mais lorsque vous envisagez des systèmes embarqués, vous ne disposez peut-être pas d'une bonne source de hasard. Par exemple, il existe des microcontrôleurs exécutant un environnement nodeJS (similaire).

Un tel microcontrôleur n'a pas de sources aléatoires qui garantissent des nombres aléatoires sécurisés cryptographiquement. Il vous faudrait donc exiger la connexion d'un périphérique qui peut fournir une entrée aléatoire à une broche pour pouvoir implémenter un langage de programmation qui offre de fortes garanties sur les nombres aléatoires. Et vous auriez besoin de quelques connaissances pour construire un appareil qui offre suffisamment d'aléatoire et pour traiter également l'entrée de l'appareil de manière appropriée.

5
allo

Comme d'autres, je voudrais souligner que Math.random() n'est pas cryptographiquement sécurisé car il n'est généralement pas nécessaire. Mais j'irais plus loin et dirais qu'il n'est pas sage d'écrire un algorithme cryptographiquement sécurisé dans une spécification, sauf si vous avez une très bonne raison.

Que signifie être cryptographiquement sécurisé? Eh bien, il y a toujours la définition ennuyeuse de "personne ne sait encore comment le briser". Mais que se passe-t-il quand quelqu'un le casse? Si vous avez spécifié un CSPRNG, vous devez également inclure un moyen de rechercher quel algorithme est utilisé, ou de le faire autrement afin que l'utilisateur final puisse être certain de ce qu'il obtient.

Cela conduit également à la nécessité de pouvoir prendre en charge plusieurs générateurs, afin que l'utilisateur puisse sélectionner celui auquel il fait confiance. Cela ajoute une énorme complexité. Soudain, une fonction 1 ligne dans une API est devenue une suite.

De plus, lorsque vous commencez à parler de crypto, vous commencez à parler d'essayer d'être sécurisé dans le générateur. Vous mentionnez l'utilisation d'AES pour générer des nombres aléatoires: votre implémentation AES doit-elle être à l'abri des attaques des canaux secondaires? Lorsque vous écrivez une bibliothèque dans le but spécifique de fournir des garanties cryptographiques, il n'est pas si déraisonnable d'avoir à poser cette question. Pour une spécification, cela pourrait être terriblement déraisonnable. L'immunité contre les attaques par canal latéral est une chose très difficile à exprimer dans le langage des spécifications.

Et qu'avez-vous accompli en le mettant dans une spécification? La plupart des utilisateurs de PRNG n'ont pas du tout besoin de garanties cryptographiques, donc vous venez de gaspiller des cycles CPU pour eux. Ceux qui veulent des garanties cryptographiques vont probablement chercher une bibliothèque qui prend en charge la suite complète des fonctionnalités nécessaires pour être à l'aise avec une telle cryptogaphie, donc ils ne feront pas confiance à Math.random() de toute façon. Il ne reste que la démographie que vous mentionnez: les personnes qui ont fait une erreur et utilisé un outil alors qu'elles ne devraient pas. Eh bien, je peux vous dire par expérience, que les principaux langages de programmation sont pas un endroit où chercher une API que vous ne pouvez pas utiliser incorrectement par erreur. Ils sont pleins de phrases "si vous faites cela, c'est de votre faute".

Considérez également ceci: si vous utilisez Math.random() et que vous assumez des garanties cryptographiques, quelles sont les chances que vous fassiez une autre erreur cryptographique fatale quelque part dans votre algorithme? Un CSPRNG Math.random() peut fournir un faux sentiment de sécurité, et nous pouvons trouver encore plus d'erreurs!

5
Cort Ammon

Tout le monde semble avoir manqué un peu d'une nuance ici: les algorithmes cryptographiques nécessitent qu'un nombre soit mathématiquement et statistiquement aléatoire sur toutes les exécutions de l'algorithme. Cela signifie par exemple pendant un jeu ou une animation, que vous pouvez utiliser une séquence de nombres aléatoire et ce serait parfaitement bien pour un "type de nombre aléatoire".

Cependant, si ce nombre peut être manipulé ou prédit, par exemple un nombre aléatoire prédéfini (qui est le comportement par défaut des fonctions aléatoires de Windows), cette valeur initiale est en fait prévisible. Si je peux manipuler votre application pour redémarrer puis utiliser un nombre aléatoire prédéfini, je peux prédire le nombre "aléatoire" que vous choisirez. Si cela est possible, la cryptographie peut être vaincue. Une préoccupation secondaire peut également être que certains algorithmes nécessitent une distribution garantie des nombres à travers le spectre, ce que certains générateurs aléatoires ne peuvent pas garantir.

Les générateurs de nombres aléatoires cryptographiquement ont un grand nombre d'entrées pour créer l'entropie, par exemple la mesure du bruit de tir de l'entrée du microphone, les ticks de l'heure, la somme de contrôle des registres RAM, les numéros de série, etc. Autant d'entrées que possible pour la rendre sinon impossible, alors incroyablement difficile à manipuler et à prévoir. Dans un sens cryptographique, la performance n'est pas l'objectif, mais le "vrai" caractère aléatoire.

Donc, selon votre cas d'utilisation, vous voudrez peut-être une implémentation raisonnablement aléatoire et performante d'un nombre aléatoire, mais si vous effectuez un échange de clés diffie-hellman, vous avez besoin d'un algorithme cryptographiquement sécurisé.

3
Spence