web-dev-qa-db-fra.com

«Toujours utiliser / dev / urandom» est-il toujours un bon conseil à l'ère des conteneurs et de l'isolement?

En bref: au lieu d'une autre question demandant quand utiliser /dev/random au lieu de /dev/urandom, Je présente le scénario suivant, dans lequel je me retrouve dans une application que je construis:

  • A VM ou environnement de conteneur (c'est-à-dire une nouvelle installation, probablement vieille de quelques secondes seulement lorsque l'application est exécutée pour la première fois)
  • Un besoin d'octets aléatoires cryptographiquement sécurisés à utiliser comme matériel de clé pour le reste de la vie de l'installation (mois ou plus)
  • Une histoire et une interface utilisateur dans lesquelles le blocage (même pendant des minutes, si nécessaire) est acceptable

Je me demande: est-ce le cas d'utilisation rare mais approprié pour une source aléatoire de blocage (c'est-à-dire, en utilisant getrandom avec l'indicateur de mode de blocage)?

Forme plus longue:

Évidemment /dev/urandom contre /dev/random est un sujet qui a donné lieu à une discussion controversée. Pour ma part, je pense que /dev/urandom est préférable dans presque tous les cas d'utilisation typiques - en fait, je n'ai jamais utilisé de source aléatoire bloquante auparavant.

Dans cette réponse populaire et merveilleuse , Thomas Pornin fait valoir que la page de manuel urandom est quelque peu trompeuse (convenu) et que, une fois correctement définie, le pool urandom ne "s'épuisera" pas dans l'entropie dans aucun scénario pratique - et cela me convient également.

Cependant, je pense qu'il sur-vend légèrement urandom en disant que "le seul instant où /dev/urandom pourrait impliquer un problème de sécurité en raison d'une faible entropie pendant les premiers instants d'une nouvelle installation automatisée du système d'exploitation. "

Ma compréhension est que le "trou d'entropie au démarrage" pour un démarrage de serveur Ubuntu typique dure plus d'une minute! Ceci est basé sur recherche à l'Université du Michigan par J. Alex Halderman .

Halderman semble également dire que le pool d'entropie se remplit à chaque démarrage , et non, comme Pornin le dit dans sa réponse, lors de la toute première installation du système d'exploitation. Bien que ce ne soit pas très important pour mon application, je me demande: qu'est-ce que c'est?

J'ai lu le "Mythes sur Urandom" post par Thomas Hühn , mais je trouve cela peu convaincant pour plusieurs raisons, le plus pertinent pour mon application est que le post se résume essentiellement à "les gens n'aiment pas Ils vont imaginer des solutions de contournement, concocter des machinations bizarres pour le faire fonctionner. " Bien que cela soit sans aucun doute vrai (et c'est la raison pour laquelle j'ai toujours utilisé /dev/urandom partout ailleurs, en particulier pour les contenus Web), il existe certaines applications dans lesquelles les utilisateurs tolèrent d'avoir à attendre, surtout s'ils l'installent pour la première fois.

Je construis une application destinée à être exécutée localement dans un paramètre de terminal et j'ai déjà des raisons de créer une attente que le processus d'installation initial sera un peu impliqué. Je n'ai aucun scrupule à demander à l'utilisateur d'attendre un peu s'il peut ajouter même une petite quantité de robustesse contre une paire de clés répétée.

En fait, Halderman dit qu'il a été capable de calculer des clés privées pour 105 728 hôtes SSH - plus de 1% de ceux qu'il a analysés - en raison des faibles pools d'entropie utilisés pour générer la paire de clés. Dans ce cas, il s'agissait en grande partie de dispositifs intégrés qui ont vraisemblablement des sources d'entropie abyssales et donc une difficulté à remplir leur bassin.

Mais - et c'est peut-être le cœur de ma question - à une époque où les applications sont livrées dans des conteneurs entièrement naïfs, destinés à être exécutés comme si sur une installation de système d'exploitation brillante et fraîche datant de seulement quelques secondes, ne sommes-nous pas raisonnablement préoccupés par cette même phénomène? N'avons-nous pas besoin d'une interface de blocage pratique? Et c'est ce que getrandom est censé devenir?

Bien sûr, il est possible dans de nombreuses situations de partager l'entropie de l'hôte à l'invité. Mais pour les besoins de cette question, supposons que l'auteur a décidé de ne pas le faire, soit parce qu'elle n'aura pas un contrôle suffisant sur les détails du déploiement ou parce qu'il n'y a pas de pool d'entropie disponible sur l'hôte.

En réfléchissant un peu plus loin: quelles sont les meilleures pratiques pour des environnements aussi frais et naïfs que je l'ai décrit ci-dessus, mais qui fonctionnent sur des appareils aux perspectives assez abyssales pour la génération d'entropie initiale? Je pense à de petits appareils embarqués avec peu ou pas de HID et qui sont peut-être insuffisant pour le processus d'installation initial.

edit: Update: Il semble donc qu'à partir de PEP524, Python (dans lequel l'application en question est écrite) utilise getrandom lorsque os.urandom est appelé et bloque dans le cas où le pool d'entropie n'a pas rassemblé au moins 128 bits. Donc, sur le plan pratique, je pense avoir ma réponse - utilisez simplement os.urandom et il se comportera comme /dev/random uniquement si nécessaire. Je suis cependant intéressé par la question primordiale ici (c.-à-d., L'ère de la conteneurisation signifie-t-elle une refonte de l'orthodoxie "utilisez toujours l'urandom").

27
jMyles

J'ai écrit un answer qui décrit en détail comment getrandom() bloque l'attente de l'entropie initiale.

Cependant, je pense qu'il a légèrement survendu urandom en disant que "le seul instant où/dev/urandom pourrait impliquer un problème de sécurité en raison d'une faible entropie est pendant les premiers moments d'une nouvelle installation automatisée du système d'exploitation."

Vos inquiétudes sont fondées. J'ai une --- question ouverte sur cette chose et ses implications. Le problème est que la graine aléatoire persistante prend un certain temps pour passer du pool d'entrée au pool de sortie (le pool de blocage et le CRNG). Ce problème signifie que /dev/urandom Produira des valeurs potentiellement prévisibles pendant quelques minutes après le démarrage. La solution est, comme vous le dites, d'utiliser soit le blocage /dev/random, Soit d'utiliser getrandom() set to block.

En fait, il n'est pas rare de voir des lignes comme celle-ci dans le journal du noyau au démarrage précoce:

random: sn: uninitialized urandom read (4 bytes read, 7 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 15 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 16 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 16 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 20 bits of entropy available)

Ce sont tous des cas où le pool non bloquant a été accédé avant même que suffisamment d'entropie ait été collectée. Le problème est que la quantité d'entropie est juste trop faible pour être suffisamment sécurisée cryptographiquement à ce stade. Il devrait y avoir 232 valeurs possibles de 4 octets, mais avec seulement 7 bits d'entropie disponibles, cela signifie qu'il n'y en a que 27, ou 128, différentes possibilités.

Halderman semble également dire que le pool d'entropie se remplit à chaque démarrage, et non, comme Pornin le dit dans sa réponse, lors de la toute première installation du système d'exploitation. Bien que ce ne soit pas très important pour mon application, je me demande: qu'est-ce que c'est?

C'est en fait une question de sémantique. L'entropie réelle pool (la page de mémoire conservée dans le noyau qui contient des valeurs aléatoires) est remplie à chaque démarrage par la graine d'entropie persistante et par le bruit environnemental. Cependant, l'entropie seed lui-même est un fichier créé au moment de l'installation et mis à jour avec de nouvelles valeurs aléatoires à chaque arrêt du système. J'imagine que Pornin considère la graine aléatoire comme faisant partie du pool d'entropie (comme dans une partie du système général de distribution et de collecte d'entropie), alors que Halderman la considère comme distincte (parce que le pool d'entropie est techniquement une page de mémoire, rien de plus). La vérité est que la graine d'entropie est introduite dans le pool d'entropie à chaque démarrage, mais cela peut prendre quelques minutes pour affecter réellement le pool.

Un résumé des trois sources de hasard:

  1. /dev/random - Le périphérique de caractères de blocage décrémente un "nombre d'entropie" chaque fois qu'il est lu (bien que l'entropie ne soit pas réellement épuisée). Cependant, il se bloque également jusqu'à ce qu'une entropie suffisante ait été collectée au démarrage, ce qui le rend sûr à utiliser dès le début.

  2. /dev/urandom - L'appareil à caractère non bloquant produira des données aléatoires chaque fois que quelqu'un en lira. Une fois que l'entropie suffisante a été collectée, elle produira un flux pratiquement illimité impossible à distinguer des données aléatoires. Malheureusement, pour des raisons de compatibilité, il est lisible même au début du démarrage avant que suffisamment d'entropie ponctuelle ait été collectée.

  3. getrandom() - Un syscall qui produira des données aléatoires tant que le pool d'entropie a été correctement initialisé avec le minimum d'entropie requis. Par défaut, il est lu dans le pool non bloquant. Si on lui donne le drapeau GRND_NONBLOCK, Il retournera une erreur s'il n'y a pas assez d'entropie. Si le drapeau GRND_RANDOM Lui est attribué, il se comportera de la même manière que /dev/random, En bloquant simplement jusqu'à ce qu'il y ait une entropie disponible.

Je vous suggère d'utiliser la troisième option, le getrandom() syscall. Cela permettra à un processus de lire des données aléatoires sécurisées cryptographiquement à des vitesses élevées, et ne se bloquera qu'au début du démarrage lorsque suffisamment d'entropie n'a pas été collectée. Si la fonction os.urandom() de Python agit comme un wrapper pour ce syscall comme vous le dites, alors ça devrait être bien à utiliser. Il semble qu'il y ait effectivement eu beaucoup de discussions sur si oui ou non cela devrait être le cas, se retrouvant avec un blocage jusqu'à ce que suffisamment d'entropie soit disponible.

En réfléchissant un peu plus loin: quelles sont les meilleures pratiques pour des environnements aussi frais et naïfs que je l'ai décrit ci-dessus, mais qui fonctionnent sur des appareils aux perspectives assez abyssales pour la génération d'entropie initiale?

Il s'agit d'une situation courante et il existe plusieurs façons de la gérer:

  • Assurez-vous de bloquer au démarrage précoce, par exemple en utilisant /dev/random Ou getrandom().

  • Conservez une graine aléatoire persistante, si possible (c'est-à-dire si vous pouvez écrire dans le stockage à chaque démarrage).

  • Plus important encore, utilisez un RNG matériel . Il s'agit de la mesure la plus efficace n ° 1.

L'utilisation d'un générateur de nombres aléatoires matériel est très importante. Le noyau Linux initialisera son pool d'entropie avec toute interface HWRNG prise en charge, le cas échéant, éliminant complètement le trou d'entropie de démarrage. De nombreux appareils intégrés ont leurs propres générateurs d'aléatoire.

Ceci est particulièrement important pour de nombreux périphériques intégrés, car ils peuvent ne pas avoir de temporisateur haute résolution requis pour que le noyau génère en toute sécurité une entropie à partir du bruit environnemental. Certaines versions de processeurs MIPS, par exemple, n'ont pas de compteur de cycles.

Comment et pourquoi suggérez-vous d'utiliser urandom pour amorcer un (je suppose userland?) CSPRNG? Comment cela bat-il au hasard?

Le dispositif aléatoire non bloquant n'est pas conçu pour des performances élevées. Jusqu'à récemment, l'appareil était incroyablement lent en raison de l'utilisation de SHA-1 pour l'aléatoire plutôt que d'un chiffrement de flux comme il le fait maintenant. L'utilisation d'une interface de noyau pour le caractère aléatoire peut être moins efficace qu'un CSPRNG local dans l'espace utilisateur, car chaque appel au noyau nécessite un coûteux changement de contexte . Le noyau a été conçu pour prendre en compte les applications qui souhaitent en tirer largement parti, mais les commentaires dans le code source indiquent clairement qu'ils ne voient pas cela comme la bonne chose à faire:

/*
 * Hack to deal with crazy userspace progams when they are all trying
 * to access /dev/urandom in parallel.  The programs are almost
 * certainly doing something terribly wrong, but we'll work around
 * their brain damage.
 */

Bibliothèques de cryptographie populaires telles que OpenSSL prise en charge de la génération de données aléatoires . Ils peuvent être ensemencés une fois ou réensemencés de temps en temps, et peuvent bénéficier davantage de la parallélisation. Il permet en outre d'écrire du code portable qui ne dépend pas du comportement d'un système d'exploitation ou d'une version de système d'exploitation particulier.

Si vous n'avez pas besoin d'énormes quantités de hasard, il est tout à fait correct d'utiliser l'interface du noyau. Si vous développez une application de chiffrement qui nécessitera beaucoup d'aléatoire tout au long de sa vie, vous voudrez peut-être utiliser une bibliothèque comme OpenSSL pour gérer cela pour vous.

22
forest

Le système peut se trouver dans trois états:

  1. N'a pas collecté suffisamment d'entropie pour initialiser en toute sécurité un CPRNG.
  2. A collecté suffisamment d'entropie pour initialiser en toute sécurité un CPRNG, et:

    2a. A donné plus d'entropie qu'il n'en a été collecté.

    2b. A donné moins d'entropie qu'il n'est collecté.

Historiquement, les gens pensaient que la distinction entre (2a) et (2b) était importante. Cela a causé deux problèmes. Tout d'abord, c'est faux - la distinction n'a pas de sens pour un CPRNG correctement conçu. Et deuxièmement, l'accent mis sur la distinction (2a) -vs2b) a fait que les gens ont manqué la distinction entre (1) et (2), ce qui est vraiment très important. Les gens se sont simplement effondrés (1) pour devenir un cas spécial de (2a).

Ce que vous voulez vraiment, c'est quelque chose qui bloque dans l'état (1) et ne bloque pas dans les états (2a) ou (2b).

Malheureusement, dans l'ancien temps, la confusion entre (1) et (2a) signifiait que ce n'était pas une option. Vos deux seules options étaient /dev/random, qui a bloqué dans les cas (1) et (2a), et /dev/urandom, qui n'a jamais bloqué. Mais l'état (1) ne se produit presque jamais - et ne se produit pas du tout dans des systèmes bien configurés, voir ci-dessous - puis /dev/urandom est meilleur pour presque tous les systèmes, presque tout le temps. C'est de là que viennent tous ces articles de blog sur "toujours utiliser l'andandom" - ils essayaient de convaincre les gens de cesser de faire une distinction dénuée de sens et nuisible entre les États (2a) et (2b).

Mais, oui, ni l'un ni l'autre n'est ce que vous voulez réellement. Ainsi, le nouveau syscall getrandom, qui par défaut bloque dans l'état (1), et ne bloque pas dans les états (2a) ou (2b). Donc, sur Linux moderne, l'orthodoxie devrait être mise à jour pour: --- (utilisez toujours getrandom avec les paramètres par défaut.

Rides supplémentaires:

  • getrandom prend également en charge un mode non par défaut où il agit comme /dev/random, qui peut être demandé via le GRND_RANDOM drapeau. AFAIK ce drapeau n'est jamais réellement utile, pour les mêmes raisons que les anciens articles de blog décrits. Ne l'utilisez pas.

  • getrandom offre également des avantages supplémentaires par rapport à /dev/urandom: il fonctionne quelle que soit la disposition de votre système de fichiers et ne nécessite pas l'ouverture d'un descripteur de fichier, tous deux problématiques pour les bibliothèques génériques qui souhaitent faire des hypothèses minimales sur l'environnement dans lequel elles seront utilisées. Cela n'affecte pas la sécurité cryptographique, mais c'est Nice sur le plan opérationnel.

  • Un système bien configuré aura toujours l'entropie disponible, même au démarrage précoce (c'est-à-dire que vous ne devriez vraiment jamais entrer dans l'état (1), jamais). Il existe de nombreuses façons de gérer cela: enregistrez l'entropie du démarrage précédent pour l'utiliser sur le suivant. Installez un RNG matériel. Les conteneurs Docker utilisent le noyau de l'hôte et ont ainsi accès à son pool d'entropie. Les configurations de virtualisation de haute qualité permettent au système invité de récupérer l'entropie du système hôte via des interfaces d'hyperviseur (par exemple, recherchez "virtio rng"). Mais bien sûr, tous les systèmes ne sont pas bien configurés. Si vous avez un système mal configuré, vous devriez voir si vous pouvez le rendre bien configuré à la place. En principe, cela devrait être bon marché et facile, mais en réalité, les gens ne priorisent pas la sécurité, donc ... cela pourrait nécessiter de faire des choses comme changer de fournisseur de cloud ou passer à une autre plate-forme intégrée. Et malheureusement, vous constaterez peut-être que cela coûte plus cher que ce que vous (ou votre patron) êtes prêt à payer, vous êtes donc coincé face à un système mal configuré. Mes sympathies si c'est le cas.

  • Comme le note @forest, si vous avez besoin de beaucoup de valeurs CPRNG, alors si vous faites très attention, vous pouvez accélérer cela en exécutant votre propre CPRNG dans l'espace utilisateur, tout en utilisant getrandom pour le (ré) amorçage. Cependant, c'est une chose "réservée aux experts", comme dans toute situation où vous vous trouvez à implémenter vos propres primitives de cryptographie. Vous ne devriez le faire que si vous avez mesuré et trouvé que l'utilisation directe de getrandom est trop lente pour vos besoins et vous avez une cryptographie importante compétence. Il est très facile de bousiller une implémentation CPRNG de manière à ce que votre sécurité soit totalement rompue, mais la sortie "semble" aléatoire de sorte que vous ne le remarquiez pas.

9