Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET AES decryption breaks first few bytes

I'm using AesCryptoServiceProvider to encrypt and decrypt an XML document on disk. There's an example in the MSDN reference that was helpful. I generate the AES key from the SHA-256 hash of a given password. The first half of it is assigned as IV, since I don't know of any better thing to use here. As far as I know, both key and IV must be the same for encrypting and decrypting.

When I decrypt my file, this is what the beginning of it looks like:

I���H璧�-����[�="1.0" encoding="utf-8"?>

The rest of the document is perfectly fine. There's not even some random padding after the content as I would have expected maybe.

What is causing this random garbage at the beginning of the file?

Here's more of the reading code:

using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
{
    using (SHA256CryptoServiceProvider sha = new SHA256CryptoServiceProvider())
    {
        this.cryptoKey = sha.ComputeHash(Encoding.Unicode.GetBytes(password));
    }
    aes.Key = this.cryptoKey;
    Array.Copy(this.cryptoKey, aes.IV, 16);

    ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    using (CryptoStream cs = new CryptoStream(fs, decryptor, CryptoStreamMode.Read))
    using (StreamReader sr = new StreamReader(cs))
    {
        string data = sr.ReadToEnd();
        xdoc.LoadXml(data);

        //xdoc.Load(sr);
    }
}

And that's the encryption code:

XmlWriterSettings xws = new XmlWriterSettings();
xws.Encoding = Encoding.UTF8;
xws.Indent = true;
xws.IndentChars = "\t";
xws.OmitXmlDeclaration = false;

using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
{
    aes.Key = this.cryptoKey;
    Array.Copy(this.cryptoKey, aes.IV, 16);

    ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

    using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
    using (CryptoStream cs = new CryptoStream(fs, encryptor, CryptoStreamMode.Write))
    using (StreamWriter sw = new StreamWriter(cs, Encoding.UTF8))
    {
        XmlWriter writer = XmlWriter.Create(sw, xws);
        xdoc.Save(writer);
        writer.Close();
    }
}
like image 756
ygoe Avatar asked Mar 20 '23 18:03

ygoe


2 Answers

To begin with, don't generate the key material from an ad-hoc algorithm (and yes, when it comes to key derivation, SHA256 is an ad-hoc algorithm). Follow industry standards and use a trusted Password-Based Key Derivation Function. Current standard is PBKDF-2, see also RFC2898. .Net managed crypto implementation is the Rfc2898DeriveBytes class.

Second, you must show us the encryption code. Looks to me like the sample you used appends the IV used at the beginning of the encrypted stream. Which makes perfect sense, given that the IV should not be derived from the password. The key and IV should be derived from password+random, and the 'random' must be sent as part of the file.

like image 131
Remus Rusanu Avatar answered Mar 29 '23 17:03

Remus Rusanu


Following Remus Rusanu's advice I've changed my code to use the Rfc2898DeriveBytes class for key generation and write the used salt and IV data to the encrypted file so that I don't need to transport it on a separate channel (like the password).

This is now working for me:

// Setup XML formatting
XmlWriterSettings xws = new XmlWriterSettings();
xws.Encoding = Encoding.UTF8;
xws.Indent = true;
xws.IndentChars = "\t";
xws.OmitXmlDeclaration = false;

// Encrypt document to file
byte[] salt = new byte[8];
new RNGCryptoServiceProvider().GetBytes(salt);
Rfc2898DeriveBytes keyGenerator = new Rfc2898DeriveBytes(password, salt);

using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
{
    aes.Key = keyGenerator.GetBytes(aes.KeySize / 8);

    using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
    using (CryptoStream cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write))
    using (StreamWriter sw = new StreamWriter(cs, Encoding.UTF8))
    {
        fs.Write(salt, 0, salt.Length);
        fs.Write(aes.IV, 0, aes.IV.Length);

        // Write XmlDocument to the encrypted file
        XmlWriter writer = XmlWriter.Create(sw, xws);
        xdoc.Save(writer);
        writer.Close();
    }
}

// Decrypt the file
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
    byte[] salt = new byte[8];
    fs.Read(salt, 0, salt.Length);
    Rfc2898DeriveBytes keyGenerator = new Rfc2898DeriveBytes(this.password, salt);

    using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
    {
        aes.Key = keyGenerator.GetBytes(aes.KeySize / 8);
        byte[] iv = new byte[aes.BlockSize / 8];
        fs.Read(iv, 0, iv.Length);
        aes.IV = iv;

        using (CryptoStream cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read))
        using (StreamReader sr = new StreamReader(cs))
        {
            // Read stream into new XmlDocument
            xdoc.Load(sr);
        }
    }
}
like image 32
ygoe Avatar answered Mar 29 '23 16:03

ygoe