web-dev-qa-db-fra.com

Bonne pratique de vecteur d'initialisation AES

par ma question Aes Encryption ... manque une pièce importante , j'ai maintenant appris que mon hypothèse pour créer un cryptage réversible sur une chaîne était un peu décalée. J'ai maintenant

    public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
    {
        var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
        using (var provider = new AesCryptoServiceProvider())
        {
            provider.Key = encryptionKey;
            provider.Mode = CipherMode.CBC;
            provider.Padding = PaddingMode.PKCS7;
            using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
            {
                using (var ms = new MemoryStream())
                {
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
                        cs.FlushFinalBlock();
                    }
                    return ms.ToArray();
                }
            }
        }
    }

et cela produit des résultats cohérents; cependant, je ne pourrai pas décrypter sans connaître/paramétrer le vecteur d'initialisation. Je ne veux vraiment pas passer trois valeurs dans cette méthode (pour l'IV), ce qui me laisse avec le codage en dur de l'IV ou le dérivant de la clé. Je voudrais savoir si c'est une bonne pratique, ou si cela rendra la valeur cryptée vulnérable aux attaques d'une manière ou d'une autre ... ou suis-je vraiment en train de trop réfléchir et devrais-je simplement coder en dur l'IV?

[~ # ~] mise à jour [~ # ~] Selon la suggestion d'Iridium, j'ai plutôt essayé quelque chose comme ceci:

    public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
    {
        if (string.IsNullOrEmpty(toEncrypt)) throw new ArgumentException("toEncrypt");
        if (encryptionKey == null || encryptionKey.Length == 0) throw new ArgumentException("encryptionKey");
        var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
        using (var provider = new AesCryptoServiceProvider())
        {
            provider.Key = encryptionKey;
            provider.Mode = CipherMode.CBC;
            provider.Padding = PaddingMode.PKCS7;
            using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
            {
                using (var ms = new MemoryStream())
                {
                    ms.Write(provider.IV, 0, 16);
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
                        cs.FlushFinalBlock();
                    }
                    return ms.ToArray();
                }
            }
        }
    }

    public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
    {
        using (var provider = new AesCryptoServiceProvider())
        {
            provider.Key = encryptionKey;
            provider.Mode = CipherMode.CBC;
            provider.Padding = PaddingMode.PKCS7;
            using (var ms = new MemoryStream(encryptedString))
            {
                byte[] buffer;
                ms.Read(buffer, 0, 16);
                provider.IV = buffer;
                using (var decryptor = provider.CreateDecryptor(provider.Key, provider.IV))
                {
                    using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] decrypted = new byte[encryptedString.Length];
                        var byteCount = cs.Read(decrypted, 0, encryptedString.Length);
                        return Encoding.UTF8.GetString(decrypted, 0, byteCount);
                    }
                }
            }
        }
    }

cependant, cela montre quelque chose d'étrange dans mon test unitaire:

    [TestMethod]
    public void EncryptionClosedLoopTest()
    {
        var roundtrip = "This is the data I am encrypting.  There are many like it but this is my encryption.";
        var encrypted = Encryption.EncryptString(roundtrip, encryptionKey);
        var decrypted = Encryption.DecryptString(encrypted, encryptionKey);
        Assert.IsTrue(roundtrip == decrypted);
    }

mon texte déchiffré apparaît comme "92ʪ�F" �, hpv0�� Je suis en train de chiffrer. Il y en a beaucoup comme ça mais c'est mon cryptage. "Qui semble presque correct mais bien sûr complètement faux. Il semble que je suis proche cependant. Ai-je manqué un décalage sur le flux de mémoire?

48
Jeremy Holovacs

L'IV doit être aléatoire et unique pour chaque exécution de votre méthode de cryptage. Le dériver de la clé/du message ou le coder en dur n'est pas suffisamment sécurisé. L'IV peut être généré dans cette méthode, au lieu de lui être transmis, et écrit dans le flux de sortie avant les données chiffrées.

Lors du décryptage, l'IV peut alors être lu à partir de l'entrée avant les données cryptées.

47
Iridium

Lors du cryptage, générez votre IV et ajoutez-le au texte chiffré (quelque chose comme ça)

        using (var aes= new AesCryptoServiceProvider()
        {
            Key = PrivateKey,
            Mode = CipherMode.CBC,
            Padding = PaddingMode.PKCS7
        })
        {

            var input = Encoding.UTF8.GetBytes(originalPayload);
            aes.GenerateIV();
            var iv = aes.IV;
            using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
            using (var cipherStream = new MemoryStream())
            {
                using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
                using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
                {
                    //Prepend IV to data
                    //tBinaryWriter.Write(iv); This is the original broken code, it encrypts the iv
                    cipherStream.Write(iv);  //Write iv to the plain stream (not tested though)
                    tBinaryWriter.Write(input);
                    tCryptoStream.FlushFinalBlock();
                }

                string encryptedPayload = Convert.ToBase64String(cipherStream.ToArray());
            }

        }

Lors du décryptage, récupérez les 16 premiers octets et utilisez-les dans le flux cryptographique

var aes= new AesCryptoServiceProvider()
                    {
                        Key = PrivateKey,
                        Mode = CipherMode.CBC,
                        Padding = PaddingMode.PKCS7
                    };

                    //get first 16 bytes of IV and use it to decrypt
                    var iv = new byte[16];
                    Array.Copy(input, 0, iv, 0, iv.Length);

                    using (var ms = new MemoryStream())
                    {
                        using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
                        using (var binaryWriter = new BinaryWriter(cs))
                        {
                            //Decrypt Cipher Text from Message
                            binaryWriter.Write(
                                input,
                                iv.Length,
                                input.Length - iv.Length
                            );
                        }

                        return Encoding.Default.GetString(ms.ToArray());
                    }
14
ankurpatel

J'ai modifié votre méthode de décryptage comme suit et cela fonctionne:

public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
    using (var provider = new AesCryptoServiceProvider())
    {
        provider.Key = encryptionKey;
        using (var ms = new MemoryStream(encryptedString))
        {
            // Read the first 16 bytes which is the IV.
            byte[] iv = new byte[16];
            ms.Read(iv, 0, 16);
            provider.IV = iv;

            using (var decryptor = provider.CreateDecryptor())
            {
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (var sr = new StreamReader(cs))
                    {
                        return sr.ReadToEnd();
                    }
                }
            }
        }
    }
}

Le problème avec votre implémentation est que vous lisez trop d'octets dans le CryptoStream. Vous devez vraiment lire encryptedText.Length - 16. L'utilisation d'un StreamReader simplifie cela, car vous n'avez plus à vous soucier des décalages n'importe où.

5
RobSiklos

Excellente contribution des gens. J'ai pris les réponses combinées d'Ankurpatel et de Konstantin et je les ai nettoyées et j'ai ajouté des remplacements de méthode pratiques. Cela fonctionne à partir de juin 2019 dans .NET Core 2.2.

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

private const int AesKeySize = 16;

public static void Main()
{
    // the data to encrypt
    var message = "Here is some data to encrypt!";

    // create KeySize character key
    var key = "g(KMDu(EEw63.*V`";

    // encrypt the string to a string
    var encrypted = AesEncrypt(message, key);

    // decrypt the string to a string.
    var decrypted = AesDecrypt(encrypted, key);

    // display the original data and the decrypted data
    Console.WriteLine($"Original:   text: {encrypted}");
    Console.WriteLine($"Round Trip: text: {decrypted}");
}

static string AesEncrypt(string data, string key)
{
    return AesEncrypt(data, Encoding.Default.GetBytes(key));
}

static string AesDecrypt(string data, string key)
{
    return AesDecrypt(data, Encoding.Default.GetBytes(key));
}

static string AesEncrypt(string data, byte[] key)
{
    return Convert.ToBase64String(AesEncrypt(Encoding.Default.GetBytes(data), key));
}

static string AesDecrypt(string data, byte[] key)
{
    return Encoding.Default.GetString(AesDecrypt(Convert.FromBase64String(data), key));
}

static byte[] AesEncrypt(byte[] data, byte[] key)
{
    if (data == null || data.Length <= 0)
    {
        throw new ArgumentNullException($"{nameof(data)} cannot be empty");
    }

    if (key == null || key.Length != AesKeySize)
    {
        throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
    }

    using (var aes = new AesCryptoServiceProvider
    {
        Key = key,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7
    })
    {
        aes.GenerateIV();
        var iv = aes.IV;
        using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
        using (var cipherStream = new MemoryStream())
        {
            using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
            using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
            {
                // prepend IV to data
                cipherStream.Write(iv);
                tBinaryWriter.Write(data);
                tCryptoStream.FlushFinalBlock();
            }
            var cipherBytes = cipherStream.ToArray();

            return cipherBytes;
        }
    }
}

static byte[] AesDecrypt(byte[] data, byte[] key)
{
    if (data == null || data.Length <= 0)
    {
        throw new ArgumentNullException($"{nameof(data)} cannot be empty");
    }

    if (key == null || key.Length != AesKeySize)
    {
        throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
    }

    using (var aes = new AesCryptoServiceProvider
    {
        Key = key,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7
    })
    {
        // get first KeySize bytes of IV and use it to decrypt
        var iv = new byte[AesKeySize];
        Array.Copy(data, 0, iv, 0, iv.Length);

        using (var ms = new MemoryStream())
        {
            using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
            using (var binaryWriter = new BinaryWriter(cs))
            {
                // decrypt cipher text from data, starting just past the IV
                binaryWriter.Write(
                    data,
                    iv.Length,
                    data.Length - iv.Length
                );
            }

            var dataBytes = ms.ToArray();

            return dataBytes;
        }
    }
}
3
paultechguy

Afin de résoudre le paramètre IV sur le fournisseur (comme l'a souligné Iridium):

ms.Read(provider.IV, 0, 16);

J'ai ajouté ce qui suit à votre code:

var iv = new byte[provider.IV.Length];
memoryStream.Read(iv, 0, provider.IV.Length);
using (var decryptor = provider.CreateDecryptor(key, iv);

accordé, ma clé n'est pas définie par le fournisseur à chaque exécution. Je l'ai généré une fois puis l'ai stocké. L'IV est généré de manière aléatoire par le fournisseur pour chaque cryptage.

2
Randy

La réponse acceptée est correcte, mais ne fournit pas un bon exemple de la façon d'obtenir une IV aléatoire.

Il s'avère que c'est un lot plus facile que les gens essaient de le faire. AesCryptoServiceProvider dans .NET génère automatiquement un IV cryptographiquement aléatoire chaque fois que vous en construisez un. Et si vous devez utiliser la même instance pour plusieurs chiffrements, vous pouvez appeler GenerateIV ()

Vous pouvez également ajouter le IV à la valeur chiffrée avant de le renvoyer et demander à l'extrémité de déchiffrement de le retirer

private static void Main(string[] args) {
    var rnd = new Random(); 
    var key = new byte[32];  // For this example, I'll use a random 32-byte key.
    rnd.NextBytes(key);
    var message = "This is a test";

    // Looping to encrypt the same thing twice just to show that the IV changes.
    for (var i = 0; i < 2; ++i) {
        var encrypted = EncryptString(message, key);
        Console.WriteLine(encrypted);
        Console.WriteLine(DecryptString(encrypted, key));
    }
}

public static string EncryptString(string message, byte[] key) {
    var aes = new AesCryptoServiceProvider();
    var iv = aes.IV;
    using (var memStream = new System.IO.MemoryStream()) {
        memStream.Write(iv, 0, iv.Length);  // Add the IV to the first 16 bytes of the encrypted value
        using (var cryptStream = new CryptoStream(memStream, aes.CreateEncryptor(key, aes.IV), CryptoStreamMode.Write)) {
            using (var writer = new System.IO.StreamWriter(cryptStream)) {
                writer.Write(message);
            }
        }
        var buf = memStream.ToArray();
        return Convert.ToBase64String(buf, 0, buf.Length);
    }
}

public static string DecryptString(string encryptedValue, byte[] key) {
    var bytes = Convert.FromBase64String(encryptedValue);
    var aes = new AesCryptoServiceProvider();
    using (var memStream = new System.IO.MemoryStream(bytes)) {
        var iv = new byte[16];
        memStream.Read(iv, 0, 16);  // Pull the IV from the first 16 bytes of the encrypted value
        using (var cryptStream = new CryptoStream(memStream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read)) {
            using (var reader = new System.IO.StreamReader(cryptStream)) {
                return reader.ReadToEnd();
            }
        }
    }  
}

[EDIT: J'ai modifié ma réponse pour inclure comment passer l'IV dans la valeur cryptée et l'obtenir lors du décryptage. J'ai également refactorisé un peu l'exemple]

1
dwilliss