web-dev-qa-db-fra.com

Cryptage et décryptage d'une chaîne en C #

Quel est le moyen le plus moderne (le meilleur) de satisfaire les éléments suivants en C #?

string encryptedString = SomeStaticClass.Encrypt(sourceString);

string decryptedString = SomeStaticClass.Decrypt(encryptedString);

MAIS avec un minimum de problèmes avec les sels, les clés, les octets [], etc.

Vous avez été googlé et confus à ce que je découvre (vous pouvez voir la liste des SO Qs similaires à voir c'est une question trompeuse à poser).

316
Richard

UPDATE 23/Dec/2015: Comme cette réponse semble recevoir de nombreux votes positifs, je l'ai mise à jour afin de corriger les bugs ridicules et d'améliorer globalement le code en fonction des commentaires. Voir la fin de l'article pour une liste d'améliorations spécifiques.

Comme d’autres l’ont dit, la cryptographie n’est pas simple, il est donc préférable d’éviter de "lancer votre propre" algorithme de cryptage.

Vous pouvez toutefois "lancer votre propre" classe wrapper autour de quelque chose comme la classe de cryptographie intégrée RijndaelManaged .

Rijndael est le nom algorithmique de l'actuel Advanced Encryption Standard , vous utilisez donc certainement un algorithme qui pourrait être considéré comme une "meilleure pratique".

La classe RijndaelManaged vous oblige normalement à vous "débrouiller" avec des tableaux d'octets, des sels, des clés, des vecteurs d'initialisation, etc. "classe.

La classe suivante est celle que j'ai écrite il y a quelque temps pour effectuer exactement le genre de chose que vous recherchez, un simple appel à une méthode unique permettant de chiffrer un texte en clair basé sur une chaîne avec un mot de passe basé sur une chaîne, avec également la chaîne résultante. être représenté comme une chaîne. Bien sûr, il existe une méthode équivalente pour déchiffrer la chaîne chiffrée avec le même mot de passe.

Contrairement à la première version de ce code, qui utilisait exactement les mêmes valeurs sel et IV, cette nouvelle version générera des valeurs sel et IV aléatoires à chaque fois. Puisque salt et IV doivent être identiques entre le chiffrement et le déchiffrement d'une chaîne donnée, les mots salt et IV sont ajoutés au texte chiffré lors du chiffrement et extraits à nouveau pour permettre le déchiffrement. Le résultat de cela est que le cryptage du même texte en clair avec le même mot de passe donne un résultat de texte crypté totalement différent à chaque fois.

La "force" de l’utiliser provient de l’utilisation de la classe RijndaelManaged pour effectuer le cryptage, ainsi que de l’utilisation de la fonction Rfc2898DeriveBytes de l’espace de nommage System.Security.Cryptography qui Générez votre clé de chiffrement à l'aide d'un algorithme standard et sécurisé (en particulier, PBKDF2 ) basé sur le mot de passe basé sur une chaîne que vous avez fourni. (Notez qu'il s'agit d'une amélioration de l'utilisation par la première version de l'ancien algorithme PBKDF1).

Enfin, il est important de noter que le cryptage est toujours non authentifié . Le cryptage seul ne fournit que la confidentialité (le message est inconnu des tiers), tandis que le cryptage authentifié vise à assurer à la fois la confidentialité et l'authenticité (le destinataire sait que le message a été envoyé par l'expéditeur).

Sans connaître exactement vos exigences, il est difficile de dire si le code ici est suffisamment sécurisé pour vos besoins. Cependant, il a été produit pour offrir un bon équilibre entre simplicité relative de mise en œuvre et "qualité". Par exemple, si votre "destinataire" d'une chaîne cryptée reçoit la chaîne directement d'un "expéditeur" de confiance, alors l'authentification peut même ne pas être nécessaire .

Si vous avez besoin de quelque chose de plus complexe et offrant un cryptage authentifié, consultez this post ​​pour une implémentation.

Voici le code:

using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Linq;

namespace EncryptStringSample
{
    public static class StringCipher
    {
        // This constant is used to determine the keysize of the encryption algorithm in bits.
        // We divide this by 8 within the code below to get the equivalent number of bytes.
        private const int Keysize = 256;

        // This constant determines the number of iterations for the password bytes generation function.
        private const int DerivationIterations = 1000;

        public static string Encrypt(string plainText, string passPhrase)
        {
            // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
            // so that the same Salt and IV values can be used when decrypting.  
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
                                var cipherTextBytes = saltStringBytes;
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                // Fill the array with cryptographically secure random bytes.
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }
    }
}

La classe ci-dessus peut être utilisée très simplement avec un code similaire à celui-ci:

using System;

namespace EncryptStringSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter a password to use:");
            string password = Console.ReadLine();
            Console.WriteLine("Please enter a string to encrypt:");
            string plaintext = Console.ReadLine();
            Console.WriteLine("");

            Console.WriteLine("Your encrypted string is:");
            string encryptedstring = StringCipher.Encrypt(plaintext, password);
            Console.WriteLine(encryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Your decrypted string is:");
            string decryptedstring = StringCipher.Decrypt(encryptedstring, password);
            Console.WriteLine(decryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }
}

(Vous pouvez télécharger un exemple de solution VS2013 simple (comprenant quelques tests unitaires) ici ).

UPDATE 23/Dec/2015: La liste des améliorations spécifiques apportées au code est la suivante:

  • Correction d'un bug idiot où l'encodage était différent entre le cryptage et le décryptage. Le mécanisme par lequel les valeurs de sel et d'IV sont générées ayant changé, le codage n'est plus nécessaire.
  • En raison de la modification salt/IV, le commentaire de code précédent qui indiquait de manière incorrecte que le codage UTF8 d’une chaîne de 16 caractères produit 32 octets n’est plus applicable (car le codage n’est plus nécessaire).
  • L'utilisation de l'algorithme remplacé PBKDF1 a été remplacée par l'utilisation de l'algorithme plus moderne PBKDF2.
  • La dérivation de mot de passe est maintenant correctement salée alors qu'avant, elle ne l'était pas du tout (une autre bête stupide écrasée).
692
CraigTP
using System.IO;
using System.Text;
using System.Security.Cryptography;

public static class EncryptionHelper
{
    public static string Encrypt(string clearText)
    {
        string EncryptionKey = "abc123";
        byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
        using (Aes encryptor = Aes.Create())
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(clearBytes, 0, clearBytes.Length);
                    cs.Close();
                }
                clearText = Convert.ToBase64String(ms.ToArray());
            }
        }
        return clearText;
    }
    public static string Decrypt(string cipherText)
    {
        string EncryptionKey = "abc123";
        cipherText = cipherText.Replace(" ", "+");
        byte[] cipherBytes = Convert.FromBase64String(cipherText);
        using (Aes encryptor = Aes.Create())
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(cipherBytes, 0, cipherBytes.Length);
                    cs.Close();
                }
                cipherText = Encoding.Unicode.GetString(ms.ToArray());
            }
        }
        return cipherText;
    }
}
77
A Ghazal

Essayez cette classe:

public class DataEncryptor
{
    TripleDESCryptoServiceProvider symm;

    #region Factory
    public DataEncryptor()
    {
        this.symm = new TripleDESCryptoServiceProvider();
        this.symm.Padding = PaddingMode.PKCS7;
    }
    public DataEncryptor(TripleDESCryptoServiceProvider keys)
    {
        this.symm = keys;
    }

    public DataEncryptor(byte[] key, byte[] iv)
    {
        this.symm = new TripleDESCryptoServiceProvider();
        this.symm.Padding = PaddingMode.PKCS7;
        this.symm.Key = key;
        this.symm.IV = iv;
    }

    #endregion

    #region Properties
    public TripleDESCryptoServiceProvider Algorithm
    {
        get { return symm; }
        set { symm = value; }
    }
    public byte[] Key
    {
        get { return symm.Key; }
        set { symm.Key = value; }
    }
    public byte[] IV
    {
        get { return symm.IV; }
        set { symm.IV = value; }
    }

    #endregion

    #region Crypto

    public byte[] Encrypt(byte[] data) { return Encrypt(data, data.Length); }
    public byte[] Encrypt(byte[] data, int length)
    {
        try
        {
            // Create a MemoryStream.
            var ms = new MemoryStream();

            // Create a CryptoStream using the MemoryStream 
            // and the passed key and initialization vector (IV).
            var cs = new CryptoStream(ms,
                symm.CreateEncryptor(symm.Key, symm.IV),
                CryptoStreamMode.Write);

            // Write the byte array to the crypto stream and flush it.
            cs.Write(data, 0, length);
            cs.FlushFinalBlock();

            // Get an array of bytes from the 
            // MemoryStream that holds the 
            // encrypted data.
            byte[] ret = ms.ToArray();

            // Close the streams.
            cs.Close();
            ms.Close();

            // Return the encrypted buffer.
            return ret;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine("A cryptographic error occured: {0}", ex.Message);
        }
        return null;
    }

    public string EncryptString(string text)
    {
        return Convert.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(text)));
    }

    public byte[] Decrypt(byte[] data) { return Decrypt(data, data.Length); }
    public byte[] Decrypt(byte[] data, int length)
    {
        try
        {
            // Create a new MemoryStream using the passed 
            // array of encrypted data.
            MemoryStream ms = new MemoryStream(data);

            // Create a CryptoStream using the MemoryStream 
            // and the passed key and initialization vector (IV).
            CryptoStream cs = new CryptoStream(ms,
                symm.CreateDecryptor(symm.Key, symm.IV),
                CryptoStreamMode.Read);

            // Create buffer to hold the decrypted data.
            byte[] result = new byte[length];

            // Read the decrypted data out of the crypto stream
            // and place it into the temporary buffer.
            cs.Read(result, 0, result.Length);
            return result;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine("A cryptographic error occured: {0}", ex.Message);
        }
        return null;
    }

    public string DecryptString(string data)
    {
        return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(data))).TrimEnd('\0');
    }

    #endregion

}

et l'utiliser comme ça:

string message="A very secret message here.";
DataEncryptor keys=new DataEncryptor();
string encr=keys.EncryptString(message);

// later
string actual=keys.DecryptString(encr);
28
ja72

Si vous avez besoin de stocker un mot de passe en mémoire et souhaitez le chiffrer, vous devez utiliser SecureString :

http://msdn.Microsoft.com/en-us/library/system.security.securestring.aspx

Pour des utilisations plus générales, j'utiliserais un algorithme approuvé par FIPS, tel que Advanced Encryption Standard, anciennement Rijndael. Voir cette page pour un exemple d'implémentation:

http://msdn.Microsoft.com/en-us/library/system.security.cryptography.rijndael.aspx

19
Ulises

Si vous ciblez ASP.NET Core qui ne prend pas encore en charge RijndaelManaged, vous pouvez utiliser IDataProtectionProvider.

Tout d’abord, configurez votre application pour utiliser la protection des données:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection();
    }
    // ...
}

Ensuite, vous pourrez injecter l'instance IDataProtectionProvider et l'utiliser pour chiffrer/déchiffrer des données:

public class MyService : IService
{
    private const string Purpose = "my protection purpose";
    private readonly IDataProtectionProvider _provider;

    public MyService(IDataProtectionProvider provider)
    {
        _provider = provider;
    }

    public string Encrypt(string plainText)
    {
        var protector = _provider.CreateProtector(Purpose);
        return protector.Protect(plainText);
    }

    public string Decrypt(string cipherText)
    {
        var protector = _provider.CreateProtector(Purpose);
        return protector.Unprotect(cipherText);
    }
}

Voir cet article pour plus de détails.

16
Sergey Kolodiy

Vous recherchez peut-être la classe ProtectedData, qui chiffre les données à l'aide des informations d'identification de connexion de l'utilisateur.

9
SLaks