Stan Drapkin. Pièges de cryptographie de haut niveau dans .NET

Stan Drapkin est un expert en sécurité et conformité avec plus de 16 ans d'expérience avec le .NET Framework (à partir de .NET 1.0-beta en 2001). Malheureusement, lui-même n'écrit pas d'articles en russe, nous avons donc convenu avec lui de publier une traduction de son rapport avec DotNext Piter . Ce rapport a remporté la première place à la conférence!

Cryptographie symétrique, asymétrique, hybride, haut niveau, bas niveau, flux et cryptographie elliptique moderne. Cinquante-six minutes de vidéo sur la cryptographie, et beaucoup plus rapidement - sous forme de texte.



Sous la coupe - vidéos, diapositives et traduction. Bonne lecture!


Diapositives

Je m'appelle Stan Drapkin, je suis le directeur technique d'une entreprise spécialisée dans la sécurité de l'information et la conformité réglementaire. De plus, je suis l'auteur de plusieurs bibliothèques open source, très bien accueillies par la communauté. Combien ont entendu parler d' Inferno ? Cette bibliothèque illustre l'approche correcte de la cryptographie dans .NET, et TinyORM implémente micro-ORM pour .NET. De plus, j'ai écrit plusieurs livres qui peuvent être pertinents pour le sujet de l'article d'aujourd'hui. L'un d'eux, l'édition 2014, est «Security Driven .NET», l'autre de 2017 est «Application Security in .NET, succinctement».

Tout d'abord, nous parlerons de ce que j'appelle les quatre étapes de la crypto-illumination. Ensuite, deux sujets principaux suivront, dans le premier, nous parlerons de la cryptographie symétrique, dans le second - asymétrique et hybride. Dans la première partie, nous comparons la cryptographie de haut niveau et de bas niveau et examinons un exemple de cryptographie en streaming. Dans la deuxième partie, nous aurons de nombreuses «aventures» avec RSA, après quoi nous nous familiariserons avec la cryptographie elliptique moderne.

Alors, à quoi ressemblent ces étapes de l'illumination cryptographique? La première étape - "XOR est tellement cool, regarde, maman, comment puis-je!" Beaucoup d'entre vous connaissent sûrement cette étape et connaissent les merveilles de la fonction XOR. Mais j'espère que la majeure partie de cette étape a progressé et est passée à la suivante, à savoir apprendre à effectuer le chiffrement et le déchiffrement à l'aide d'AES (Advanced Encryption Standard), un algorithme bien connu et très apprécié. La plupart des développeurs qui ne visitent pas DotNext en sont à ce stade. Mais, puisque vous suivez DotNext et que vous connaissez les rapports sur les dangers des API de bas niveau, vous êtes très probablement à l'étape suivante - «J'ai tout fait (a) mal, je dois passer à des API de haut niveau». Eh bien, pour compléter l'image, je mentionnerai également la dernière étape - la compréhension qu'avec la meilleure solution au problème, la cryptographie peut ne pas être nécessaire du tout. Cette étape est la plus difficile à atteindre, et il y a peu de monde dessus. Un exemple est Peter G. Neumann, qui a dit ce qui suit: «Si vous pensez que la solution à votre problème réside dans la cryptographie, alors vous ne comprenez pas exactement quel est votre problème.

Le fait que la cryptographie de bas niveau soit dangereuse a été discuté dans de nombreux rapports sur .NET. Vous pouvez vous référer au rapport de Vladimir Kochetkov en 2015, "Pièges de System.Security.Cryptography" . Son idée principale est qu'à chaque étape du travail avec des API cryptographiques de bas niveau, sans le savoir, nous prenons de nombreuses décisions, pour lesquelles nous n'avons tout simplement pas les connaissances appropriées. La principale conclusion est que, idéalement, la cryptographie de haut niveau devrait être utilisée à la place de la cryptographie de bas niveau. C'est une merveilleuse conclusion, mais elle nous amène à un autre problème - savons-nous exactement à quoi devrait ressembler la cryptographie de haut niveau? Parlons-en un peu.

Définissez les attributs d'une API cryptographique non de haut niveau. Pour commencer, une telle API ne donnera pas l'impression d'être native de .NET; elle ressemblera plutôt à un shell de bas niveau. De plus, une telle API sera facile à utiliser de manière incorrecte, c'est-à-dire pas comme il se doit. De plus, cela vous obligera à générer de nombreuses choses étranges de bas niveau - nonce, vecteurs d'initialisation, etc. Une telle API vous obligera à prendre des décisions désagréables auxquelles vous ne serez peut-être pas préparé - choisissez des algorithmes, des modes de remplissage, des tailles de clé, des nonce, etc. Il ne disposera pas non plus de l'API appropriée pour la diffusion en continu (API de diffusion en continu) - nous parlerons de l'aspect de cette dernière.

En revanche, à quoi devrait ressembler une API cryptographique de haut niveau? Je pense qu'il doit tout d'abord être intuitif et concis tant pour le lecteur du code que pour l'auteur. De plus, une telle API devrait être facile à apprendre et à utiliser, et elle devrait être extrêmement difficile à appliquer de la mauvaise façon. Il doit également être puissant, c'est-à-dire qu'il doit nous permettre d'atteindre notre objectif avec un petit effort, une petite quantité de code. Enfin, une telle API ne devrait pas avoir une longue liste de restrictions, mises en garde, cas particuliers, en général - il devrait y avoir un minimum de choses à retenir lors de l'utilisation avec elle, en d'autres termes - elle devrait être caractérisée par un faible niveau d'interférence (faible friction), devrait il suffit de travailler sans aucune réserve.

Après avoir traité les exigences d'une API cryptographique de haut niveau pour .NET, comment pouvons-nous la trouver maintenant? Vous pouvez essayer simplement google, mais ce serait trop primitif - nous sommes des développeurs professionnels, et ce n'est pas notre méthode. Par conséquent, nous étudions ce problème et testons diverses alternatives. Mais pour cela, nous devons d'abord nous créer une idée correcte de ce qu'est le chiffrement authentifié, et pour cela, nous devons comprendre les concepts de base. Ils sont les suivants: le texte en clair P (texte en clair), que nous convertissons en texte chiffré C (texte chiffré) de même longueur en utilisant une clé secrète K (clé). Comme vous pouvez le voir, jusqu'à présent, nous travaillons avec un schéma très simple. De plus, nous avons également une balise d'authentification T et nonce N. Un paramètre important est N̅, c'est-à-dire la réutilisation de nonce avec une clé. Comme beaucoup d'entre vous le savent probablement, cela conduit à une violation de la confidentialité du texte, ce qui n'est évidemment pas souhaitable. Un autre concept important est l'AD (données associées), c'est-à-dire les données associées. Il s'agit de données facultatives authentifiées mais ne participant pas au chiffrement et au déchiffrement.



Après avoir compris les concepts de base, examinons les différentes options des bibliothèques cryptographiques pour .NET. Commençons par l'analyse de Libsodium.NET. Combien d'entre vous la connaissent? Comme je le vois, certains sont familiers.

nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad); 

Voici le code C # avec lequel le chiffrement est effectué avec Libsodium.NET . À première vue, il est assez simple et concis: dans la première ligne, un nonce est généré, qui est ensuite utilisé dans la deuxième ligne, où le chiffrement lui-même a lieu, et dans la troisième, où le texte est déchiffré. Il semblait - quelles difficultés pouvait-il y avoir? Pour commencer, Libsodium.NET propose non pas une, mais trois méthodes de chiffrement symétriques différentes:

Fois

 nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad); 

Deux

 nonce = SecretAead.GenerateNonce(); c = SecretAead.Encrypt(p, nonce, key, ad); d = SecretAead.Decrypt(c, nonce, key. ad); 

Trois

 nonce = SecretBox.GenerateNonce(); c = SecretBox.Create(p, nonce, key); d = SecretBox.Open(c, nonce, key); 

De toute évidence, la question se pose - laquelle est la meilleure dans votre situation spécifique? Pour y répondre, vous devez entrer dans ces méthodes, ce que nous allons faire maintenant.

La première méthode, SecretAeadAes , utilise AES-GCM avec un nonce 96 bits. Il est important qu'il ait une liste assez longue de restrictions. Par exemple, lorsque vous l'utilisez, vous ne devez pas chiffrer plus de 550 gigaoctets avec une seule clé, et il ne doit pas y avoir plus de 64 gigaoctets dans un message avec un maximum de 2 32 messages. De plus, la bibliothèque ne vous avertit pas de l'approche de ces restrictions, vous devez les suivre vous-même, ce qui crée une charge supplémentaire pour vous en tant que développeur.

Seconde méthode, SecretAead utilise une autre suite de chiffrement, ChaCha20/Poly1305 avec un nonce 64 bits significativement plus petit. Un si petit nonce rend les collisions extrêmement probables, et pour cette seule raison, vous ne devez pas utiliser cette méthode - sauf dans des cas assez rares et à condition que vous soyez très bien versé dans le sujet.

Enfin, la troisième méthode, SecretBox . Il convient de noter immédiatement qu'il n'y a pas de données associées dans les arguments de cette API. Si vous avez besoin d'un chiffrement authentifié avec AD, cette méthode ne vous convient pas. L'algorithme de chiffrement utilisé ici est appelé xSalsa20/Poly1305 , nonce est assez grand - 192 bits. Cependant, le manque de DA est une limitation importante.

Lors de l'utilisation de Libsodium.NET , certaines questions se posent. Par exemple, que devons-nous faire exactement avec le nonce généré par la première ligne de code dans les exemples ci-dessus? La bibliothèque ne nous dit rien à ce sujet, nous devons le découvrir par nous-mêmes. Très probablement, nous ajouterons manuellement ce nonce au début ou à la fin du texte chiffré. De plus, nous pourrions avoir l'impression que la DA dans les deux premières méthodes peut être de n'importe quelle longueur. Mais en fait, la bibliothèque prend en charge AD pas plus de 16 octets de long - après tout, 16 octets suffiront à tout le monde, non? Continuons. Que se passe-t-il avec les erreurs de décryptage? Dans cette bibliothèque, il a été décidé dans ces cas de lever des exceptions. Si, dans votre environnement, l'intégrité des données de déchiffrement peut être violée, vous aurez de nombreuses exceptions qui devront être gérées. Que faire si la taille de votre clé n'est pas exactement de 32 octets? La bibliothèque ne nous en dit rien, ce sont vos problèmes qui ne l'intéressent pas. Un autre sujet important est la réutilisation des tableaux d'octets afin de réduire la charge sur le garbage collector dans les scénarios intensifs. Par exemple, dans le code, nous avons vu un tableau que le générateur de nonce nous a retourné. Je ne voudrais pas créer un nouveau tampon à chaque fois, mais réutiliser celui existant. Ce n'est pas possible dans cette bibliothèque, un tableau d'octets sera régénéré à chaque fois.

En utilisant le schéma que nous avons déjà vu, nous allons essayer de comparer différents algorithmes Libsodium.NET .



Le premier algorithme, AES-GCM, utilise nonce 96 bits de long (colonne jaune dans l'image). Il est inférieur à 128 bits, ce qui crée un certain inconfort, mais pas trop important. La colonne suivante est bleue, c'est la place occupée par la balise d'authentification, avec AES-GCM c'est 16 octets ou 128 bits. Le deuxième chiffre bleu, entre parenthèses, signifie la quantité d'entropie, ou aléatoire, contenue dans cette balise - moins de 128 bits. Combien moins - dans cet algorithme dépend de la quantité de données cryptées. Plus il est crypté, plus le tag est faible. Cela seul devrait donner lieu à des doutes sur cet algorithme, qui ne fera qu'augmenter si nous regardons la colonne blanche. Il indique que les répétitions (collisions) de nonce entraîneront la falsification de tous les textes chiffrés créés par la même clé. Si, par exemple, 100 de vos textes chiffrés créés par une clé commune sur deux, il y a une collision nonce, ce nonce entraînera une fuite interne de la clé d'authentification et permettra à un attaquant de truquer tout autre texte chiffré créé par cette clé. Il s'agit d'une limitation très importante.

Passons à la deuxième méthode Libsodium.NET . Comme je l'ai dit, ici pour nonce, trop peu d'espace est utilisé, seulement 64 bits. La balise occupe 128 bits, mais ne contient que 106 bits d'entropie ou moins, en d'autres termes, nettement inférieur au niveau de sécurité de 128 bits, ce qu'ils tentent dans la plupart des cas d'atteindre. Quant à la contrefaçon, la situation ici est légèrement meilleure que dans le cas de l'AES-GCM. La collision de nonce conduit à la falsification de textes chiffrés, mais uniquement pour les blocs dans lesquels des collisions se sont produites. Dans l'exemple précédent, nous aurions forgé 2 textes chiffrés, pas 100.

Enfin, dans le cas de l'algorithme xSalsa / Poly, nous avons un très grand nonce de 192 bits, ce qui rend les collisions extrêmement improbables. La méthode d'authentification est la même que dans la méthode précédente, donc l'étiquette prend à nouveau 128 bits et a 106 bits d'entropie ou moins.

Comparez tous ces chiffres avec les indicateurs correspondants de la bibliothèque Inferno . Dans ce document, le nonce occupe un espace colossal, 320 bits, ce qui rend les collisions presque impossibles. Quant à la balise, tout est simple avec elle: elle occupe exactement 128 bits et possède exactement 128 bits d'entropie, rien de moins. Ceci est un exemple d'une approche fiable et sûre.

Avant de découvrir Libsodium.NET plus en détail, nous devons comprendre son objectif - malheureusement, tous ceux qui utilisent cette bibliothèque ne le savent pas. Pour ce faire, reportez-vous à sa documentation, qui indique que Libsodium.NET est un wrapper C # pour libsodium . Il s'agit d'un autre projet open source, dont la documentation indique qu'il s'agit d'un fork de NaCl avec une API compatible. Eh bien, tournons-nous vers la documentation de NaCl , un autre projet open source. Dans ce document, comme un objectif, NaCl est postulé pour fournir toutes les opérations nécessaires à la création d'outils cryptographiques de haut niveau. C'est ici que le chien est enterré: la tâche de NaCl et de tous ses obus est de fournir des éléments de bas niveau, à partir desquels quelqu'un d'autre peut déjà assembler des API cryptographiques de haut niveau. Ces shells eux-mêmes en tant que bibliothèques de haut niveau n'ont pas été conçus. D'où la morale: si vous avez besoin d'une API cryptographique de haut niveau, vous devez trouver une bibliothèque de haut niveau, ne pas utiliser un wrapper de bas niveau et prétendre que vous travaillez avec une de haut niveau.

Voyons comment fonctionne le cryptage dans Inferno .



Voici un exemple de code dans lequel, comme dans le cas de Libsodium , chaque chiffrement et déchiffrement ne prend qu'une seule ligne. Les arguments sont la clé, le texte et les données associées facultatives. Il convient de noter qu'il n'y a pas de nonce, il n'est pas nécessaire de prendre de décision, en cas d'erreur de décryptage, il renvoie simplement null, sans lever d'exceptions. Étant donné que la création d'exceptions augmente considérablement la charge du garbage collector, leur absence est très importante pour les scripts qui traitent des flux de données volumineux. J'espère avoir réussi à vous convaincre que cette approche est optimale.

Par intérêt, essayons de crypter une chaîne. Cela devrait être le scénario le plus simple que tout le monde puisse implémenter. Supposons que nous ayons seulement deux valeurs de chaîne différentes possibles: "LEFT" et "RIGHT".



Dans l'image, vous voyez le cryptage de ces lignes à l'aide d' Inferno (bien que pour cet exemple, peu importe la bibliothèque utilisée). Nous chiffrons deux lignes avec une clé et obtenons deux textes chiffrés, c1 et c2 . Tout dans ce code est-il correct? Est-il prêt pour la production? Quelqu'un peut dire que le problème est possible de manière courte, mais il est loin d'être le principal, nous supposerons donc que la clé est utilisée de la même manière et qu'elle est de longueur suffisante. Je veux dire autre chose: avec les approches cryptographiques conventionnelles, c1 dans notre exemple sera plus court que c2 . C'est ce qu'on appelle la fuite de longueur - c2 dans de nombreux cas sera un octet de plus que c1 . Cela peut permettre à un attaquant de comprendre quelle chaîne est représentée par ce texte chiffré, "LEFT" ou "RIGHT". La façon la plus simple de résoudre ce problème est de faire en sorte que les deux lignes aient la même longueur - par exemple, ajoutez un caractère à la fin de la ligne «GAUCHE».

À première vue, la fuite de longueur est perçue comme un problème quelque peu farfelu qui ne peut pas être rencontré dans des applications réelles. Mais en janvier 2018, un article a été publié dans le magazine Wired avec une étude menée par la société israélienne Checkmarx, sous le titre «Le manque de cryptage dans Tinder permet aux étrangers de suivre quand vous faites glisser l'écran». Je vais brièvement raconter le contenu, mais d'abord une description approximative de la fonctionnalité Tinder. Tinder est une application qui reçoit un flux de photos, puis l'utilisateur fait glisser l'écran vers la droite ou la gauche, selon qu'il aime ou non la photo. Les chercheurs ont découvert que bien que les commandes elles-mêmes soient correctement cryptées à l'aide de TLS et HTTPS, les données de la commande de droite prenaient un nombre d'octets différent de celui des données de gauche. Il s'agit, bien sûr, d'une vulnérabilité, mais en soi, elle n'est pas trop importante. Plus important pour Tinder était le fait qu'ils ont envoyé les flux avec des photos via HTTP standard, sans aucun cryptage. Ainsi, l'attaquant pourrait accéder non seulement aux réactions des utilisateurs aux photos, mais également aux photos elles-mêmes. Donc, comme vous pouvez le voir, la fuite de longueur est un problème très réel.

Essayons maintenant de crypter le fichier. Je dois immédiatement dire que dans le cryptage de fichiers Libsodium.NET ou, plus généralement, le cryptage de flux n'est pas implémenté par défaut, il doit y être fait manuellement - ce qui, croyez-moi, est très difficile à faire correctement. Dans Inferno , les choses vont beaucoup mieux avec ça.



Ci-dessus, vous voyez un exemple pris avec pratiquement aucun changement de MSDN. C'est très simple, nous voyons ici un flux pour le fichier source et un autre pour le fichier de destination, ainsi qu'un flux cryptographique qui convertit le premier en second. Dans ce code, Inferno n'est utilisé que sur une seule ligne - dans celle où la conversion a lieu. Ainsi, nous avons devant nous une solution simple et en même temps entièrement fonctionnelle et testée pour le chiffrement de flux.

Il faut se rappeler que lors du cryptage avec la même clé, nous avons une limite sur le nombre de messages. Ils existent dans Inferno , et dans cette bibliothèque, ils sont clairement écrits à l'écran. Mais en même temps, ils sont si grands dans Inferno qu'en pratique, vous ne les atteindrez jamais. Dans Libsodium.NET, les restrictions sont différentes pour différents algorithmes, mais dans tous les cas, elles sont suffisamment faibles pour être dépassées. Vous devez donc vérifier s'ils seront atteints dans chaque scénario individuel.

Nous devrions également parler de l'authentification des données associées, car c'est un sujet qui n'est pas souvent traité. Les AD peuvent être «faibles»: cela signifie qu'ils sont authentifiés, mais ils ne sont pas impliqués dans le processus de cryptage et de décryptage. En revanche, les DA «forts» modifient ce processus lui-même. La plupart des bibliothèques AD que je connais sont faibles, tandis que Inferno utilise la deuxième approche, où les AD sont utilisées dans le processus de cryptage / décryptage lui-même ...

Il devrait également s'attarder sur le niveau de sécurité à atteindre pour la cryptographie de haut niveau. En bref, ma réponse est: un cryptage 256 bits avec une balise d'authentification 128 bits. Pourquoi une clé est-elle si grosse? Il y a plusieurs raisons à cela, chacune étant importante en soi, mais maintenant je voudrais que vous vous souveniez d'une chose: nous devons nous protéger contre les biais possibles lors de la génération de clés cryptographiques. Permettez-moi d'expliquer ce que l'on entend par biais. Pour un générateur de bits aléatoires sans biais, pour chaque bit, les probabilités d'accepter la valeur 0 ou 1 sont égales. Mais supposons que dans notre générateur, le bit prendra la valeur 1 avec une probabilité de 56%, et non 50%. À première vue, ce biais est faible, mais en fait il est significatif: 25%. Essayons maintenant de calculer la quantité d'entropie que nous obtenons lors de la génération d'un certain nombre de bits avec notre générateur.



Dans l'image, vous voyez la formule par laquelle ce calcul sera effectué. Il est important qu'il ne contienne que deux variables: le biais dont nous avons déjà parlé (biais) et le nombre de bits créés par le générateur. Nous supposons que le biais est de 25% - c'est un cas assez extrême, dans la pratique, vous ne travaillerez probablement pas dans des systèmes avec un tel générateur de nombres aléatoires déformé. Quoi qu'il en soit, avec un biais de 25% et une clé de 128 bits, nous n'obtenons que 53 bits d'entropie. Premièrement, il est nettement inférieur à 128 bits, ce qui est généralement attendu d'un générateur de nombres aléatoires, et deuxièmement, avec les technologies modernes, une telle clé peut simplement être une force brute. Mais si au lieu de la clé 128 bits, nous utilisons 256 bits, alors nous obtenons 106 bits d'entropie. C'est déjà assez bon, bien que inférieur aux 256. Les technologies modernes rendent presque impossible de casser une telle clé.

À la fin de la première partie du rapport, je résumerai les résultats intermédiaires. Je recommande à tout le monde d'utiliser des API cryptographiques bien écrites. Trouvez celle qui vous convient ou envoyez une pétition à Microsoft pour vous écrire. De plus, lorsque vous choisissez une API, vous devez faire attention à la disponibilité de la prise en charge pour travailler avec les threads. Pour les raisons déjà expliquées, la longueur de clé minimale doit être de 256 bits. Enfin, il convient de garder à l'esprit que la cryptographie de haut niveau, comme toute autre, n'est pas idéale. Des fuites peuvent se produire et, dans la plupart des scénarios, leurs capacités doivent être gardées à l'esprit.

Parlons de la cryptographie asymétrique ou hybride. Je vais poser une question piège: pouvez-vous utiliser RSA dans .NET? Ne vous précipitez pas pour répondre par l'affirmative, comme beaucoup le font - testons d'abord vos connaissances dans ce domaine. Les diapositives suivantes seront spécialement conçues pour les personnes qui connaissent déjà ce sujet. Mais d'abord, regardons Wikipédia et rappelons-nous ce qu'est exactement RSA au cas où quelqu'un aurait oublié ou n'aurait pas utilisé cet algorithme depuis longtemps.



Supposons qu'il y ait une Alice qui, à l'aide d'un générateur de nombres aléatoires, crée une paire de clés qui comprend un privé et un public. Ensuite, il y a un Bob qui veut crypter un message pour Alice: "Bonjour Alice!" À l'aide de sa clé publique, il génère un texte chiffré, qu'il lui envoie ensuite. Elle décrypte ce texte chiffré à l'aide de la partie privée de sa clé.

Essayons de reproduire ce scénario dans la pratique.



Comme vous pouvez le voir ci-dessus, nous créons une instance de RSA et chiffrons du texte. Faites immédiatement attention à ce que .NET nous oblige à choisir le mode de remplissage. Il y en a cinq, tous avec des noms obscurs. Si nous les essayons tous à tour de rôle, nous découvrirons que les trois derniers lèvent simplement une exception et ne fonctionnent pas. Nous utiliserons l'un des deux autres - OaepSHA1 . Ici, la clé sera de 1 kilobit, ce qui est trop petit pour RSA, c'est pratiquement une clé piratée. Par conséquent, nous devons définir la taille de la clé manuellement. La documentation nous apprend qu'il existe une propriété spéciale .KeySize , qui reçoit ou définit la taille de la clé.



À première vue, c'est exactement ce dont nous avons besoin, nous écrivons donc: rsa.KeySize = 3072 . Mais si, guidé par de vagues soupçons, après cela, nous vérifions à quel point la taille de la clé est maintenant égale, alors nous découvrirons qu'il faut toujours 1 kilobit. Peu importe, nous vérifierons ce paramètre à l'aide de la WriteLine(rsa.KeySize) ou rsa.ExportParameters(false).Modulus.Length * 8 - dans ce dernier cas, le composant public de la clé RSA est exporté, pour cela nous avons besoin de l'argument "false". Le module de cette clé est un tableau, que nous multiplions par 8 et obtenons la taille en bits - qui sera encore 1 kilobit. Comme vous pouvez le voir, cet algorithme est encore trop tôt pour être envoyé en production.

Nous ne perdrons pas de temps à comprendre pourquoi cette API ne fonctionne pas; essayez plutôt une autre implémentation RSA fournie par Microsoft dans .NET 4.6, c'est-à-dire complètement nouvelle. Il s'appelle RSACng et Cng signifie Cryptography next generation. Génial, qui ne veut pas travailler avec des outils de nouvelle génération? Nous trouverons sûrement ici une solution magique à tous nos problèmes.



Nous demandons une instance de RSACng, définissons à nouveau la taille de la clé à 3 kilobits, vérifions à nouveau la taille de la clé via WriteLine(rsa.KeySize) - et découvrons à nouveau que la taille de la clé est toujours égale à un kilobit. De plus, si nous demandons le type de l'objet qui a généré la clé - comme nous nous en souvenons, nous avons demandé une instance de RSACng - nous découvrons qu'il s'agit de RSACryptoServiceProvider. Je veux juste partager mon sentiment personnel de désespoir ici et crier: "Pourquoi, Microsoft?!"

Après des tourments et des tourments prolongés, nous découvrons qu'en fait, vous devez utiliser le concepteur, pas l'usine.



Ici, la valeur de taille de clé par défaut est de 2048 bits, ce qui est déjà beaucoup mieux. Quoi de mieux - ici, nous arrivons enfin à définir la taille de la clé à 3 kilobits. Comme on dit, le succès est débloqué.

Permettez-moi de vous rappeler que jusqu'à présent, tous nos efforts ont été réduits à la création de RSA, nous n'avons même pas encore commencé le chiffrement. Il y a encore des questions auxquelles nous devons d'abord répondre. Pour commencer, dans quelle mesure pouvez-vous vous fier aux tailles de clés par défaut? L'implémentation de la fabrique RSA peut être remplacée machine.config, par conséquent, elle peut changer à votre insu (par exemple, un administrateur système peut la modifier). Et cela signifie que la taille de clé par défaut peut également changer. Ainsi, vous ne devez jamais faire confiance aux valeurs fournies par défaut, la taille de la clé doit toujours être définie indépendamment. Ensuite, quelle est la taille des clés RSA par défaut? Il existe deux implémentations RSA dans .NET, l'une basée RSACryptoServiceProvider, l'autre baséeRSACng. Dans le premier, la taille par défaut est de 1 kilobits, dans les deux autres. Pour le plaisir, comparons ces valeurs avec celles du réseau Bitcoin (BCN). Je m'excuse à l'avance d'avoir soulevé un sujet sensible, mais nous ne discuterons pas du Bitcoin ou de la crypto-monnaie, nous ne parlerons que du réseau lui-même. Elle a un hashrate publié, qui augmente chaque mois et équivaut aujourd'hui à 2 64 hachages par seconde. Cela équivaut à 2 90 hachages par an. Pour simplifier, supposons qu'un hachage équivaut à une opération de base - bien que ce ne soit pas entièrement vrai, il est plus complexe. Si vous lisez des livres sur la cryptographie écrits par de vrais professionnels, et pas des gens comme moi, alors vous savez que 2 70 opérations (c'est-à-dire une minute de BCN) suffisent pour casser une clé RSA de 1 kilobit, et 2 90(un an BCN) - pour casser une clé de 2 kilobits. Ces deux valeurs devraient nous inquiéter - c'est ce qui peut être réalisé avec les technologies existantes. C'est pourquoi je vous recommande fortement de toujours définir la taille de la clé vous-même, et de lui faire au moins 3 kilobits, et si les performances vous le permettent, alors 4.

Dans .NET, il n'est pas si facile de comprendre comment exporter les clés publiques et privées.



En haut de la diapositive, vous voyez deux instances de la clé RSA, la première de RSACryptoServiceProvider, la seconde deRSACngchacun 4 kilobits. Le code ci-dessous est utilisé pour extraire les clés publiques et privées des deux instances. Il convient de noter que les deux API sont assez différentes l'une de l'autre - code différent, méthodes différentes, paramètres différents. De plus, si nous comparons les tailles des clés publiques des premier et deuxième exemplaires, nous verrons qu'elles sont comparables, environ un demi-kilo-octet chacune. Mais la clé privée de la nouvelle implémentation RSA est beaucoup plus petite que l'ancienne. Il est nécessaire de garder cela à l'esprit et d'observer l'uniformité, afin de ne pas interférer avec ces deux API.

Tout ce que nous avons fait avec RSA jusqu'à présent se résume à essayer d'obtenir une copie de travail; Essayez maintenant de crypter quelque chose.



Créez un tableau d'octets, qui sera notre texte en clair (data), puis nous allons le crypter en utilisant l'un de ces modes d'addition qui n'a pas généré d'exception. Mais cette fois, nous avons une exception. Il s'agit d'une exception à un paramètre non valide; mais de quel paramètre parlons-nous? Je n'en ai aucune idée - et Microsoft, très probablement aussi. Si nous essayons d'exécuter la même méthode avec d'autres modes de supplément, alors dans chaque cas, nous obtenons la même exception. Le point n'est donc pas en mode supplément. Le problème vient donc du code source lui-même. Il est difficile de dire ce qui ne va pas chez lui, alors essayons de le couper en deux au cas où. Cette fois, le chiffrement est réussi. Nous sommes perplexes.

Peut-être que le fait est que nous avons utilisé le supplément SHA-1? Comme nous le savons, SHA-1 n'est plus une fonction cryptographiquement solide, donc nos auditeurs et le service de la conformité insistent pour que nous nous en débarrassions. Remplacer OaepSHA1par OaepSHA256, au moins cela rassurera les auditeurs.



Mais lorsque nous essayons de chiffrer, nous obtenons à nouveau l'exception du mauvais paramètre. Toute cette situation est due au fait que la restriction de la taille du texte qui peut être transféré à la fonction cryptographique dépend non seulement du mode de supplément, mais également de la taille de la clé.

Essayons de savoir exactement à quoi ressemble cette formule magique, qui détermine la quantité maximale de données chiffrées. Ça doit être dans la méthodeint GetMaxDataSizeForEnc(RSAEncryptionPadding pad), qui calcule ce volume, après avoir reçu le mode supplément à l'entrée. Le principal inconvénient de cette méthode est qu'elle n'existe pas, je l'ai inventée. J'essaie de transmettre l'idée que même les informations les plus élémentaires dont un développeur a besoin pour utiliser correctement RSA ne nous sont pas disponibles. Merci, Microsoft.

Voici les raisons pour lesquelles le RSA doit être évité, même pour la signature. Comme j'espère avoir réussi à le montrer, les API pour RSA dans .NET sont extrêmement insatisfaisantes. Vous êtes obligé de prendre de nombreuses décisions concernant le mode de supplément, la taille des données et autres, ce qui n'est pas souhaitable. De plus, pour un niveau de sécurité de 128 bits, vous aurez besoin d'au moins une clé très volumineuse de 4 kilo-octets. Il vous donnera une clé privée de 1 kilo-octet, une clé publique d'un demi-kilo-octet et une signature d'un demi-kilo-octet. Pour de nombreux scénarios, ces valeurs peuvent ne pas être souhaitables. Et si vous essayez d'atteindre un niveau de sécurité de 256 bits, vous aurez besoin d'une énorme clé - 15360 bits. Dans RSA, l'utilisation d'une telle clé est presque impossible. Sur mon ordinateur portable, une telle clé est générée une minute et demie.En plus de cela, le RSA à un niveau fondamental, en tant qu'algorithme, implémente très lentement une signature, quelle que soit sa mise en œuvre. Pourquoi la vitesse de signature est-elle importante pour nous? Si vous utilisez TLS avec des certificats RSA, la signature est effectuée sur le serveur. Et nous, en tant que développeurs, sommes les plus affectés par ce qui se passe exactement sur le serveur, nous en sommes responsables, son débit est important pour nous. En résumé, je veux recommander encore une fois de ne pas utiliser RSA.Je veux recommander à nouveau de ne pas utiliser RSA.Je veux recommander à nouveau de ne pas utiliser RSA.

Dans ce cas, que peut remplacer le RSA? Je voudrais vous présenter les primitives cryptographiques elliptiques modernes. Tout d'abord, vous devez garder à l'esprit l'ECDSA (Digital Signature Algorithm), qui peut être utilisé à la place de RSA pour les signatures. Dans cette abréviation et dans les suivantes, EC est un préfixe commun qui signifie Elliptic-Curve («elliptical»). Sur securitydriven.net/inferno/#DSA Signatures, vous pouvez trouver un exemple de code ECDSA, qui, soit dit en passant, est natif de .NET. Un autre algorithme important est ECIES (Integrated Encryption Scheme, «schéma de cryptage intégré elliptique»). Cet algorithme peut effectuer un cryptage hybride au lieu de RSA, c'est-à-dire que vous générez une clé symétrique, cryptez les données avec, puis cryptez la clé elle-même.Un exemple de code est disponible sur l'exemple securitydriven.net/inferno/#ECIES. Enfin, un autre algorithme très important est l'ECDH (échange de clés Diffie-Hellman, «échange de clés Diffie-Hellman»). Il vous permet de créer des clés de chiffrement symétrique entre deux parties avec des clés publiques connues. Dans certaines situations et méthodes d'utilisation, il permet le secret direct (transmettre le secret ). Le lien securitydriven.net/inferno/#DHM clé le code d'échantillon disponible Exchange.

Pour résumer la conversation sur le chiffrement asymétrique. Vous devez toujours utiliser des API de haut niveau qui ne vous obligent pas à prendre des décisions auxquelles vous n'êtes pas prêt. Je recommanderais également d'arrêter d'utiliser RSA. Bien sûr, cela est plus facile à dire qu'à faire, car nous travaillons tous avec de grandes applications déjà créées, qui peuvent ne pas être entièrement refactorisées. Dans ce cas, vous devez au moins apprendre à utiliser correctement RSA. De plus, je vous conseille de vous familiariser avec les algorithmes cryptographiques elliptiques modernes (ECDSA, ECDH, ECIES). Enfin, il est important que la cryptographie de haut niveau ne résout pas comme par magie tous les problèmes, vous devez donc vous rappeler les objectifs que vous poursuivez. Je citerai StackOverflow, avec lequel je suis entièrement d'accord: «La cryptographie seule ne résout pas les problèmes.Le chiffrement symétrique ne fait que de la confidentialité des données un problème de gestion des clés. »

Je vais dire quelques mots sur les ressources qui peuvent vous être utiles. Il existe une bibliothèque SecurityDriven.Inferno de haut niveau relativement acceptable avec une bonne documentation. Il y a un merveilleux livre, Serious Cryptography de Jean-Philippe Aumasson, Serious Cryptography. Il donne un aperçu de l'état actuel de la cryptographie, en tenant compte des dernières innovations. De plus, j'ai écrit le livre déjà mentionné Application Security in .NET, succinctement, qui est dans le domaine public. Il contient encore plus d'informations sur les pièges de sécurité .NET. Enfin, il y a une excellente présentation de Vladimir Kochetkov à Slideshare, qui décrit les bases de la théorie de la sécurité des applications de manière quelque peu simpliste mais très solide et explique diverses sources de dangers.

Pour conclure, voyons quelques exemples supplémentaires que j'ai préparés. Au tout début, j'ai parlé de la quatrième étape de l'illumination cryptographique, au cours de laquelle il est réalisé que la meilleure solution peut ne pas avoir besoin de cryptographie du tout. Regardons un exemple d'une telle solution. Jetons un coup d'œil au mécanisme .NET classique - CSRF (Cross-Site Request Forgery, «cross-site request forgery»), conçu pour protéger contre une classe d'attaques, y compris la contrefaçon de demande intersite. Dans ce modèle, nous avons un agent utilisateur - généralement un navigateur. Il essaie d'établir une connexion avec le serveur en envoyant une demande GET. En réponse, le serveur envoie un jeton CSRF, qui est caché dans le champ HTML "caché". De plus, le même jeton est attaché à la réponse comme un cookie, comme un en-tête.L'utilisateur traite un formulaire et effectue un POST, qui retourne au serveur avec les deux jetons. Le serveur vérifie, premièrement, si les deux jetons ont été envoyés et, deuxièmement, s'ils correspondent. C'est cette comparaison d'identité qui permet au serveur de se protéger contre un attaquant. Il s'agit d'un mécanisme classique intégré à ASP.NET et ASP.NET Core. Mikhail Shcherbakov a fait un excellent rapport dans lequel le travail du CSRF a été étudié en détail.

Le problème avec cette approche est que la génération de jetons CSRF utilise le chiffrement. La difficulté est que le chiffrement est en soi une opération complexe et consommatrice de ressources, il charge le processeur, nécessite de la mémoire et augmente le délai. Tout cela n'est pas souhaitable. De plus, l'injection d'un jeton est un processus lourd, déroutant et peu pratique. Dans de nombreux cas - par exemple, lors de l'utilisation d'AJAX, d'appels asynchrones - son implémentation vous incombe en tant que développeur. Ceux qui l'ont fait savent que cette activité est extrêmement désagréable.



Le Crypto identique ou comparable peut être créé sans l'utilisation du chiffrement, comment - indiqué sur la diapositive. Je comprends que le texte ici est assez compliqué, je suis donc prêt à en discuter plus en détail dans la zone de discussion. C'est tout pour moi, merci beaucoup.

. DotNext. DotNext 2018 Moscow — 22-23 2018 - « ».

. , , . ! .

Source: https://habr.com/ru/post/fr428121/


All Articles