Les exemples donnés sont en C#, mais sur les dépôts Github, vous trouverez des exemples d'implémentations en Java et PHP. Bien évidemment, les différents exemples sont interopérables, c'est-à-dire qu'il est possible de chiffrer en C# et de déchiffrer en PHP ou Java et inversement.
Contexte
Pour préciser un peu le contexte, la méthode décrite ci-dessous est employée dans le cadre d'interopérabilité entre deux systèmes d'informations. La sécurisation des échanges recouvre plusieurs aspects :
- la confidentialité : il s'agit de s'assurer que les échanges ne seront compréhensibles que par l'émetteur et le destinataire du message ;
- l'intégrité : il s'agit de s'assurer que les échanges ne sont pas altérés ;
- l'authenticité : il s'agit de s'assurer, pour le récepteur, que le message émane bien de l'émetteur.
La confidentialité est assurée par l'usage d'un algorithme cryptographique sûr. L'usage d'un algorithme AES CBC répond à ce critère. La taille de la clé choisie est de 256 bits.
L'authenticité est assurée par la gestion des clés. Une clé est partagée uniquement entre les deux systèmes d'informations pour lesquelles elle sécurise les échanges. Ainsi, si un système d'informations A échange avec les systèmes d'informations B et C, alors les clés utilisées pour communiquer avec B et pour communiquer avec C sont différentes.
Reste l'intégrité. C'est une erreur courante de penser que l'intégrité est assurée par le chiffrement. Un attaquant peut altérer un message. Celui-ci sera décrypté, et les données déchiffrées seront alors invalides. Au mieux, elles seront rejetées. Au pire, cela peut provoquer un plantage de l'application, voire provoquer un deni de service ! Pour tester l'intégrité, il faudra donc ajouter un mécanisme de vérification, car les algorithmes de chiffrement n'en intègrent pas en général.
Vérification de l'intégrité
La vérification de l'intégrité est assurée en utilisant un algorithme de hashage. Ici, l'algorithme SHA-256.
Avant de chiffrer les données, on va calculer une signature, et on ajoutera ce hash aux données à chiffrer. Pour vérifier l'intégrité, il suffira alors, lors du déchiffrement, d'extraire le hash des données, puis de recalculer le hash des données déchiffrées et de comparer les deux. S'ils sont égaux, alors les données sont correctes.
Génération de la clé
Pour générer la clé, la méthode choisie est de dériver la clé à partir d'un mot de passe. Ce choix peut sembler étonnant, mais pour qu'une clé soit sûre, il faut être en mesure de la transmettre par un moyen de communication sûr. L'email n'est pas un moyen de communication sûr. Cela ne veut pas dire qu'il ne peut pas l'être, mais de base, il ne l'est pas. La transmission par support physique n'est pas toujours possible (politique de sécurité).
Reste donc le bon vieux coup de téléphone où on donne l'information de chiffrement. Et personnellement, je me vois mal donner une clé de 256 bits au téléphone ! Par contre, un mot de passe, c'est beaucoup plus simple.
L'idée est donc de dériver la clé à partir d'un mot de passe (gardé secret, bien entendu) et d'un sel (le salage est une notion couramment employée en cryptographie). En pratique, le sel n'a pas besoin d'être gardé secret. Mais cela peut toujours être mieux qu'il ne soit pas trop répandu, et stocké sur un support différent du mot de passe.
Par exemple, dans l'application en question, le sel est codé en dur, tandis que le mot de passe est stocké de manière sécurisée dans une base de données. Ainsi, pour pouvoir retrouver la clé, il faudrait qu'un attaquant dispose à la fois d'un accès complet à la base de données et au logiciel. Donc, en gros, qu'il ait un accès au serveur.
La méthode de dérivation de la clé doit être choisie précautionneusement. Il est recommandé d'utiliser un algorithme robuste comme PBKDF2.
Code C# : | Sélectionner tout |
1 2 3 4 5 | public static byte[] GetKeyFromPassword(string password, byte[] salt) { Rfc2898DeriveBytes derivator = new Rfc2898DeriveBytes(password, salt, 100); return derivator.GetBytes(32); } |
Ne JAMAIS stocker un mot de passe en clair |
Ne JAMAIS stocker un mot de passe en dur dans le code |
- de manière chiffrée dans une base de données ;
- en utilisant System.Security.ProtectedData.
Pas d'exemple de stockage ici, puisque cela dépendra entièrement de votre application.
Vecteur d'initialisation
Pour fonctionner correctement, l'algorithme de chiffrement AES-CBC nécessite un vecteur d'initialisation. Contrairement aux idées reçues, ce vecteur n'a pas vocation à être secret. Il peut même être diffusé (et il le sera !) en clair avec le message chiffré.
L'objectif du vecteur d'initialisation est d'augmenter l'entropie du chiffrement et faire en sorte que la même donnée, chiffrée deux fois, donne un résultat chiffré différent. Pour cela, le vecteur d'initialisation doit être à usage unique, et donc, ne pas être réutilisé. Il est aussi important d'utiliser un générateur de nombres aléatoires robuste. En .NET, il est recommandé d'utiliser System.Security.Cryptography.RandomNumberGenerator
Code C#
Code C# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | public static byte[] EncryptWithAes(byte[] plainContent, byte[] key) { if (plainContent == null || plainContent.Length == 0) { throw new ArgumentNullException("plainText"); } if (key == null || key.Length == 0) { throw new ArgumentNullException("key"); } byte[] encrypted; using (Aes aes = Aes.Create()) using(SHA256 sha256 = SHA256.Create()) { ICryptoTransform encryptor; byte[] signature = sha256.ComputeHash(plainContent); aes.GenerateIV(); aes.Mode = CipherMode.CBC; aes.Key = key; if (aes.IV == null || aes.IV.Length != 16) { throw new Exception("Invalid initialization vector"); } encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream()) { memoryStream.Write(aes.IV, 0, aes.IV.Length); using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { cryptoStream.WriteByte(1); cryptoStream.Write(signature, 0, signature.Length); cryptoStream.Write(plainContent, 0, plainContent.Length); } encrypted = memoryStream.ToArray(); } } return encrypted; } public static byte[] DecryptWithAes(byte[] cipherText, byte[] key) { if (cipherText == null || cipherText.Length == 0) { throw new ArgumentNullException("cipherText"); } if (key == null || key.Length == 0) { throw new ArgumentNullException("Key"); } byte[] plainContent = null; using (SHA256 sha256 = SHA256.Create()) using (Aes aes = Aes.Create()) using (MemoryStream msDecrypt = new MemoryStream(cipherText)) { byte[] initializationVector = new byte[16]; ICryptoTransform decryptor; msDecrypt.Read(initializationVector, 0, initializationVector.Length); aes.Mode = CipherMode.CBC; aes.Key = key; aes.IV = initializationVector; decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream outputDecrypt = new MemoryStream()) { using (CryptoStream cryptoStream = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { int hashAlgorithm = cryptoStream.ReadByte(); if (hashAlgorithm == 1) { byte[] signature = new byte[32]; byte[] computedSignature; cryptoStream.Read(signature, 0, 32); cryptoStream.CopyTo(outputDecrypt); plainContent = outputDecrypt.ToArray(); computedSignature = sha256.ComputeHash(plainContent); if (!CompareByteArray(computedSignature, signature)) { throw new Exception("Corrupted data"); } } } } } return plainContent; } |
Dépôts Github
C# : https://github.com/custom-dev/sample...ography-csharp
Java : https://github.com/custom-dev/sample...ptography-java
PHP : https://github.com/custom-dev/sample...yptography-php