Question

I have a problem decrypting a simple value in a sample application. The value was encrypted with the same sample application. I have listed the code below.

When the code block completes, the result is that plain is an empty string. No exceptions are raised.

string plain = null;

using (AesManaged alg = new AesManaged())
{
    // Extract the initialization vector from the entire ciphertext
    byte[] IV = new byte[alg.IV.Length];
    Buffer.BlockCopy(cipherText, 0, IV, 0, alg.IV.Length);

    alg.IV = IV;
    alg.Key = GetKey();

    ICryptoTransform transform = alg.CreateDecryptor();

    // Extract the encrypted value from the entire ciphertext
    byte[] encrypted = new byte[cipherText.Length - alg.IV.Length];
    Buffer.BlockCopy(cipherText, alg.IV.Length, encrypted, 0, cipherText.Length - alg.IV.Length);

    using (MemoryStream ms = new MemoryStream(encrypted))
    {
        using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read))
        {
            using (StreamReader r = new StreamReader(cs))
            {
                cs.Flush();
                plain = r.ReadToEnd();
            }
        }
    }
}

I have verified that the cipherText (a 32-byte byte[] passed as a parameter) has byte-for-byte the same values as what resulted from the encryption. The IV and Key are also byte-for-byte the same. Note: the IV is prepended to the encrypted value by the encryption logic. That's why the first lines extract it from the code block.

I've also verified that the contents f the encrypted byte[] are the same as what they are coming out of the encryption routine. The contents are just 16 bytes.

I have verified that the Position in the MemoryStream ms is 0. After the routine, it is 16. So it seems that the MemoryStream is read. My suspicion is that I am using the StreamReader incorrectly, but I can't see where I've made the mistake.

I have tried to modify one of the bytes in the encrypted byte[], and as expected I then get a CryptographicException (Padding is invalid). So it appears that from a cryptographic perspective all is in order with my IV, Key and encrypted value. For some reason, maybe it doesn't get processed all the way?

Thanks for any insight.

For completeness, encryption routine below: byte[] encrypted = null;

using (AesManaged alg = new AesManaged())
{
    System.Diagnostics.Debug.WriteLine("Key size: {0}", alg.KeySize);

    alg.GenerateIV();
    alg.Key = GetKey();

    ICryptoTransform transform = alg.CreateEncryptor(alg.Key, alg.IV);

    using (MemoryStream ms = new MemoryStream())
    using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write))
    using (StreamWriter w = new StreamWriter(cs))
    {
        w.Write(plainText);
        cs.FlushFinalBlock();

        // Create a byte array big enough to hold the IV and the encrypted value
        encrypted = new byte[alg.IV.Length + ms.Length];
        // Copy the random generated initialization vector to the start of the encrypted bytes
        Buffer.BlockCopy(alg.IV, 0, encrypted, 0, alg.IV.Length);
        // Copy the encrypted value at the end
        Buffer.BlockCopy(ms.ToArray(), 0, encrypted, alg.IV.Length, (int)ms.Length);
    }
}
Was it helpful?

Solution

I've figured out the mistake I made. I was testing with very short strings and didn't think it strange that my encrypted size was always 16 bytes.

When I tested with longer strings, that didn't feel right. It turns out that my issue was in the encryption logic: I needed to flush the StreamWriter in addition to the CryptoStream. So below is my now working encryption and decryption logic.

Note that an additional improvement to the logic is that I am now also generating a random salt during key generation and that random salt is also prepended to the encrypted byte[].

private const int SaltLength = 8;

public class KeyPackage
{
    public byte[] KeySalt { get; set; }
    public byte[] Key { get; set; }
}

public KeyPackage GetKey()
{
    // Read the password from the configuration
    string Key = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];

    // Generate Key from the password stored in configuration using a random salt
    using (Rfc2898DeriveBytes db = new Rfc2898DeriveBytes(Key, SaltLength))
    {
        // Using AES-128, we need 128 / 8 bytes in the key
        return new KeyPackage() { Key = db.GetBytes(128 / 8), KeySalt = db.Salt };
    }
}

public byte[] GetKey(byte[] keySalt)
{
    // Read the password from the configuration
    string Key = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];

    // Generate Key from the password stored in configuration using the known salt
    using (Rfc2898DeriveBytes db = new Rfc2898DeriveBytes(Key, keySalt))
    {
        // Using AES-128, we need 128 / 8 bytes in the key
        return db.GetBytes(128 / 8);
    }
}

/// <summary>
/// Encrypts the specified plain text using the
/// encryption key found in the configuration file.
/// </summary>
/// <param name="plainText">The plain text to encrypted.</param>
/// <returns>The cipher text.</returns>
public byte[] Encrypt(string plainText)
{
    byte[] encrypted = null;

    using (AesManaged alg = new AesManaged())
    {
        System.Diagnostics.Debug.WriteLine("Key size: {0}", alg.KeySize);

        alg.GenerateIV();
        KeyPackage kp = GetKey();
        alg.Key = kp.Key;

        ICryptoTransform transform = alg.CreateEncryptor();

        using (MemoryStream ms = new MemoryStream())
        using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write))
        using (StreamWriter w = new StreamWriter(cs))
        {
            w.Write(plainText);
            w.Flush(); // This was missing from my first post
            cs.FlushFinalBlock();

            // Create a byte array big enough to hold the IV and the encrypted value
            encrypted = new byte[kp.KeySalt.Length + alg.IV.Length + ms.Length];
            // Copy the random generated salt to the start of the encrypted bytes
            Buffer.BlockCopy(kp.KeySalt, 0, encrypted, 0, kp.KeySalt.Length);
            // Copy the random generated initialization vector to the start of the encrypted bytes
            Buffer.BlockCopy(alg.IV, 0, encrypted, kp.KeySalt.Length, alg.IV.Length);
            // Copy the encrypted value at the end
            Buffer.BlockCopy(ms.ToArray(), 0, encrypted, alg.IV.Length + kp.KeySalt.Length, (int)ms.Length);
        }
    }
    return encrypted;
}

/// <summary>
/// 
/// </summary>
/// <param name="cipherText"></param>
/// <returns></returns>
public string Decrypt(byte[] cipherText)
{
    string plain = null;

    using (AesManaged alg = new AesManaged())
    {
        // Extract the initialization vector from the entire ciphertext
        byte[] IV = new byte[alg.IV.Length];
        byte[] KeySalt = new byte[SaltLength];

        Buffer.BlockCopy(cipherText, 0, KeySalt, 0, SaltLength);
        Buffer.BlockCopy(cipherText, SaltLength, IV, 0, alg.IV.Length);

        alg.IV = IV;
        alg.Key= GetKey(KeySalt);

        ICryptoTransform transform = alg.CreateDecryptor();

        // Extract the encrypted value from the entire ciphertext
        int ActualCipherLength = cipherText.Length - alg.IV.Length - SaltLength;
        byte[] encrypted = new byte[ActualCipherLength];
        Buffer.BlockCopy(cipherText, alg.IV.Length + SaltLength, encrypted, 0, ActualCipherLength);

        using (MemoryStream ms = new MemoryStream(encrypted))
        using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read))
        using (StreamReader r = new StreamReader(cs))
        {
            plain = r.ReadToEnd();
        }
    }
    return plain;
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top