Search code examples
c#encryptionaesaescng

AesCng changing encryption between constructions


This the wrapper class I created:

public class AesCngWithSalt
{
    public AesCngWithSalt() { }
    public AesCngWithSalt(string password, string salt)
    {
        Password = password;
        Salt = salt;
        Build();
    }

    public string Password {  get; set; }
    public string Salt { get; set; }
    protected Aes Encryptor { get; set; }
    protected Aes Decryptor { get; set; }

    public void Build()
    {
        if (string.IsNullOrWhiteSpace(Password) || string.IsNullOrWhiteSpace(Salt))
        {
            throw new ArgumentException("Password and Salt must be set first");
        }
        Rfc2898DeriveBytes rfcKey1 = new Rfc2898DeriveBytes(Password, Encoding.Unicode.GetBytes(Salt), 1000);
        Rfc2898DeriveBytes rfcKey2 = new Rfc2898DeriveBytes(Password, Encoding.Unicode.GetBytes(Salt));
        Encryptor = AesCng.Create();
        Encryptor.Padding = PaddingMode.PKCS7;
        Encryptor.KeySize = 256;
        Encryptor.Key = rfcKey1.GetBytes(32);
        Decryptor = AesCng.Create();
        Decryptor.Padding = PaddingMode.PKCS7;
        Decryptor.KeySize = 256;
        Decryptor.Key = rfcKey2.GetBytes(32);
        Decryptor.IV = Encryptor.IV;
    }

    public string Encrypt(string text)
    {
        try
        {
            MemoryStream encryptionStream = new MemoryStream();
            CryptoStream encrypt = new CryptoStream(encryptionStream, Encryptor.CreateEncryptor(), CryptoStreamMode.Write);
            byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(text);
            encrypt.Write(utfD1, 0, utfD1.Length);
            encrypt.FlushFinalBlock();
            encrypt.Close();
            byte[] edata1 = encryptionStream.ToArray();
            return Convert.ToBase64String(edata1);
        }
        catch
        {
            return string.Empty;
        }
    }

    public string Decrypt(string text)
    {
        try
        {
            MemoryStream decryptionStreamBacking = new MemoryStream();
            CryptoStream decrypt = new CryptoStream(decryptionStreamBacking, Decryptor.CreateDecryptor(), CryptoStreamMode.Write);
            char[] data = text.ToArray();
            byte[] bdata = Convert.FromBase64CharArray(data, 0, data.Length);
            decrypt.Write(bdata, 0, bdata.Length);
            decrypt.Flush();
            decrypt.Close();
            return new UTF8Encoding(false).GetString(decryptionStreamBacking.ToArray());
        }
        catch
        {
            return string.Empty;
        }
    }
}

I construct the class twice, each time with the same password value and Salt. I've made sure Rfc2898DeriveBytes is creating the exact same array given the same input. However, the AesCng class on the second construction is not behaving the same.

For example:

AesCngWithSalt aes1=new AesCngWithSalt("MyPassword", "ASaltValue");

string t1 = aes1.Encrypt("Test Text");

AesCngWithSalt aes2=new AesCngWithSalt("MyPassword", "ASaltValue");

string t2 = aes2.Encrypt("Test Text");

t1 == "ZU4bkJtc6H2nP1Eg4v9Y6w==";

t2 == "eB6HTJ3mp5ffUwzguvRyHA==";

I discovered this after encrypting a value and storing it in the database. Then later decrypting it and not getting the same value. (In fact decryption fails). My assumption is that if two distinct instances don't encrypt and create the same value, then the decryption will also not create the same values.

However, in a test app I built, if I use the same instance to encrypt and decrypt it works fine.

Is this the right encryption to use in this circumstance?


Solution

  • The answer was as the first commenter suggested. I needed to set the IV explicitly for the Encryptor.

    I was looking for a way to create an encryption that used a user supplied password. The salt was a plus one. In my searching I found an example that was as my code is above.

    As for the key that was asked about. I don't know what the creator of the example had in mind. But, the second construction Rfc2898DeriveBytes created the exact same set of bytes (another thing I discovered while debugging this). Which means I could get rid of that.