0

Note:

The following code sample is for demonstration purposes only and implements an insecure scheme. If you are looking for a secure scheme have a look at https://stackoverflow.com/a/10177020/40347

I am using the AESCryptoServiceProvider class for testing some encryption concepts. So far in all the examples and articles out there they generate a random key to use for encryption and then immediately for decryption. Sure, it works fine because you are using the key right there, but if you encrypt, save the text and at a later time you want to decrypt it you will need the SAME key. And for that purpose also the same IV.

Now, in this code I am using the same key and IV on multiple passes, every time I run the batch that batch gives the same result (as expected). But then I close the test application and rerun the same code without change and the resulting (Base64-encoded) cypher text is different for the same input parameters, why?

I "saved" one of the B64-encoded cyphers from a previous run and fed it to the TestDecrypt method and as expected, it threw a cryptographic exception mentioning something about padding though I am sure it has to do with the fact that somehow for the same Key,IV, plain text and parameters it gives a different result on every separate run of the application.

For encrypting I have this:

    public string Test(string password, Guid guid, string text)
    {
        const int SaltSize = 16;

        string b64Cryptogram;
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        Rfc2898DeriveBytes pwbytes = new Rfc2898DeriveBytes(password, SaltSize);

        // Block 128-bits Key 128/192/256 bits (16/24/32 bytes)
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            //aes.IV = pwbytes.GetBytes(aes.BlockSize / 8);
            aes.IV = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            aes.Key = guid.ToByteArray();

            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {

                        //Write all data to the stream.
                        swEncrypt.Write(text);
                    }
                    b64Cryptogram = Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
            Console.WriteLine("E: {0}", b64Cryptogram);
            aes.Clear();
        }
        return b64Cryptogram;
    }

Notice I am not using the RFC2898DeriveBytes because it will randomly derive something I will no longer remember :) The idea of encrypting it is precisely that I KNOW what I used to encrypt it.

The decryption method looks like this:

    public void TestDecrypt(string password, Guid guid, string ciphertextB64)
    {
        const int SaltSize = 16;

        byte[] cipher = Convert.FromBase64String(ciphertextB64);
        string plaintext;

        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        Rfc2898DeriveBytes pwbytes = new Rfc2898DeriveBytes(password, SaltSize);

        // Block 128-bits Key 128/192/256 bits (16/24/32 bytes)
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            //aes.IV = pwbytes.GetBytes(aes.BlockSize / 8);
            aes.IV = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            aes.Key = guid.ToByteArray();

            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            using (MemoryStream msEncrypt = new MemoryStream(cipher))
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader swEncrypt = new StreamReader(csEncrypt))
                    {
                        plaintext = swEncrypt.ReadToEnd();
                    }
                }
            }
            Console.WriteLine("D: {0}", plaintext);
            aes.Clear();
        }
    }

Now, just put that in a console application and run it. Then exit and run it again and you will see that for the same Mode, Padding, IV, Key and plain text data, the output cipher will not be the same on every application run. They will be the same provided you run the method repeatedly in the same run of the application.

In case it was not obvious, here is the console code I used to test:

        Guid guid = Guid.NewGuid();
        string plain = "Text to be encrypted 123458970";
        string password = "This is a test of the emergency broadcast system";

        TestDecrypt(password, guid, Test(password, guid, plain));
        TestDecrypt(password, guid, Test(password, guid, plain));
        Test(password, guid, plain);
        Test(password, guid, plain);
        Test(plain, guid, password);
        TestDecrypt(password, guid, "W4Oi0DrKnRpxFwtE0xVbYJwWgcA05/Alk6LrJ5XIPl8=");
    }    
Community
  • 1
  • 1
Lord of Scripts
  • 3,579
  • 5
  • 41
  • 62
  • Is the guid passed in always the same? – Frozenthia Mar 10 '16 at 20:51
  • Yes, as I said, the parameters are constant. – Lord of Scripts Mar 10 '16 at 21:02
  • Tried this, I get same cyphertext every time. – Kevin Mar 10 '16 at 21:03
  • 3
    How are you generating your guid? var guid = Guid.NewGuid(); This will generate a new guid every run and produce different text every run. IF you are hard coding a guid value then I don't know, because that gives me the same text every run. – Kevin Mar 10 '16 at 21:05
  • @Kevin, as I mentioned, when you run it the result will be the same. BUT you must then EXIT the application and run it again, the results of BATCH 1 are NOT the same as those in BATCH 2. – Lord of Scripts Mar 10 '16 at 21:05
  • I'm exiting every run, then running again. Still get identical cyphertext every run. – Kevin Mar 10 '16 at 21:06
  • "ghKliXeGkzICxTa9lcKbmpQ4q0j1ThIYYNRPl0fpsEPmfcSbwZqYs1j5NPXoj/e3yfdfpS5nCQ1q2DjcObXsiwO4TXs755BRaoWdIvPsWE4=" – Kevin Mar 10 '16 at 21:07
  • "ghKliXeGkzICxTa9lcKbmpQ4q0j1ThIYYNRPl0fpsEPmfcSbwZqYs1j5NPXoj/e3yfdfpS5nCQ1q2DjcObXsiwO4TXs755BRaoWdIvPsWE4=" – Kevin Mar 10 '16 at 21:07
  • How you're setting the guid matters, because obviously it will be the same during every run if you set your guid once and call it the same time. Because when you restart the application, Guid.NewGuid() will return a different guid every time. – Frozenthia Mar 10 '16 at 21:07
  • Where is the cipher text different? Is it entirely different, or only different towards the end? – Collin Dauphinee Mar 10 '16 at 21:08
  • Darn! what a stupid mistake I have made! I was not using a constant Guid, no wonder it was generating a new result every run. – Lord of Scripts Mar 10 '16 at 21:08
  • 1
    What are you using this code for? And why are you not using a random IV? This is a severe security flaw as it makes your scheme susceptible to dictionary attacks (see https://cwe.mitre.org/data/definitions/329.html). – Dirk Vollmar Mar 10 '16 at 21:30
  • 1
    See here for a better way to do it: http://stackoverflow.com/a/10177020/40347 – Dirk Vollmar Mar 10 '16 at 21:36

1 Answers1

2

The solution here is to pull in from a stored or constant Guid. Calling

Guid.NewGuid();

will return a different result every time. From the docs:

This is a convenient static method that you can call to get a new Guid. The method wraps a call to the Windows CoCreateGuid function. The returned Guid is guaranteed to not equal Guid.Empty.

Alternatively when testing you can use Guid.Empty which will return all zeroes.

Or, you can store it as such using its string constructor overload:

var guid = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e");
Frozenthia
  • 759
  • 3
  • 9