1

I wrote this class to allow me to encrypt and decrypt the json representation of objects but it doesn't appear to work as the MSDN documentation (here: https://msdn.microsoft.com/en-us/library/system.security.cryptography.aesmanaged%28v=vs.95%29.aspx?f=255&MSPPError=-2147217396) suggests it should ...

using Newtonsoft.Json;
using System;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web.Configuration;

namespace Core.Data
{
    public class AesCrypto<T> : ICrypto<T>
    {
    string DecryptionKey { get { return ((MachineKeySection)ConfigurationManager.GetSection("system.web/machineKey")).DecryptionKey; } }

    public string Encrypt(T source, string salt)
    {
        var sourceString = JsonConvert.SerializeObject(source);

        using (var aes = new AesManaged())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.GenerateIV();

            using (var stream = new MemoryStream())
            {
                var deriveBytes = new Rfc2898DeriveBytes(DecryptionKey, Encoding.Unicode.GetBytes(salt));
                aes.Key = deriveBytes.GetBytes(128 / 8);
                stream.Write(BitConverter.GetBytes(aes.IV.Length), 0, sizeof(int));
                stream.Write(aes.IV, 0, aes.IV.Length);

                using (var cs = new CryptoStream(stream, aes.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    byte[] rawPlaintext = Encoding.Unicode.GetBytes(sourceString);
                    cs.Write(rawPlaintext, 0, rawPlaintext.Length);
                    cs.FlushFinalBlock();

                    stream.Seek(0, SeekOrigin.Begin);

                    using (var reader = new StreamReader(stream, Encoding.Unicode))
                        return reader.ReadToEnd();
                }
            }
        }
    }

    public T Decrypt(string sourceString, string salt)
    {
        using (Aes aes = new AesManaged())
        {
            aes.Padding = PaddingMode.PKCS7;
            var deriveBytes = new Rfc2898DeriveBytes(DecryptionKey, Encoding.Unicode.GetBytes(salt));
            aes.Key = deriveBytes.GetBytes(128 / 8);

            using (var stream = new MemoryStream(Encoding.Unicode.GetBytes(sourceString)))
            {
                stream.Seek(0, SeekOrigin.Begin);

                // Get the initialization vector from the encrypted stream
                aes.IV = ReadIV(stream);

                using (var reader = new StreamReader(new CryptoStream(stream, aes.CreateDecryptor(aes.Key, aes.IV), CryptoStreamMode.Read), Encoding.Unicode))
                {
                    var resultString = reader.ReadToEnd();
                    return JsonConvert.DeserializeObject<T>(resultString);
                }
            }
        }
    }

    byte[] ReadIV(Stream s)
    {
        byte[] rawLength = new byte[sizeof(int)];
        if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
        {
            throw new SystemException("Stream did not contain properly formatted byte array");
        }

        byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)];
        if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
        {
            throw new SystemException("Did not read byte array properly");
        }

        return buffer;
    }
}

}

I wrote the following unit test to test this functionality out ...

using Core.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Core.Tests
{
    [TestClass]
    public class CryptoTests
    {
        class EncryptableObject
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public DateTimeOffset When { get; set; }
        }

        [TestMethod]
        public void TestAesCrypto()
        {
            var testInput = new EncryptableObject { Id = 123, Name = "Victim", When = DateTimeOffset.UtcNow };
            var crypto = new AesCrypto<EncryptableObject>();

            var testSalt = "testtest";

            var magicString = crypto.Encrypt(testInput, testSalt);
            var testOutput = crypto.Decrypt(magicString, testSalt);

            Assert.AreEqual(testInput.Id, testOutput.Id);
            Assert.AreEqual(testInput.Name, testOutput.Name);
            Assert.AreEqual(testInput.When, testOutput.When);
        }
    }
}

Problems seem to be endless ...

  • For some reason I get what seems to be chineese characters in my json output in the decrypt method on the line that reads "var resultString = reader.ReadToEnd();"
  • The output is different each time I run it.
  • It throws an exception about padding when I don't attach the debugger, but throws an exception about failing to deserialise the json when I do.
  • Not sure why it appears to be reading form the wrong config value (but that's likely unrelated to the encryption not working)

What am I doing wrong?

War
  • 8,539
  • 4
  • 46
  • 98
  • 1
    Break it down to the basics. Hard-code a Key and IV and see if you can get the same string in/out of the `Encrypt` and `Decrypt` methods, then go from there. A different output each run implies a different key / IV each run to me. – Haney Aug 31 '16 at 17:21
  • yeh that was my first reaction, hard code everything ... it didn't help :( – War Aug 31 '16 at 17:40
  • Where is the test data and the encrypted data? You need to dump all input and output just prior to and after the encrypt and decrypt calls. Add that data to the question. Provide all binary in hex. – zaph Aug 31 '16 at 19:15
  • 1
    uh .. if you read the full question I provide a unit test that calls the problem code ... it would take about 30 seconds to copy this question in to a console app and run it. oh and WTF ... Did you down vote because you couldn't be bothered to read the question properly? – War Sep 01 '16 at 08:43
  • 1. Read my comment, it requests more data. Data which you do not put the effort into providing but expect the potential answerer to. 2. There are no intermediate values shown, no effort to debug is shown. 3. You assume that everyone who would help has an environment that can run the C# code: I can't. 4. If you want help provide the a great question complete with debugging data. – zaph Sep 01 '16 at 13:06

1 Answers1

0

Ok I figured out it was basically encoding that was my problem here, so taking this step further I went and grabbed the code from the examples by @jbtule (thanks James) over @ https://gist.github.com/jbtule/4336842#file-aesthenhmac-cs

Having grabbed the "AESThenHMAC" class I could then write this ...

public class AesCrypto<T> : ICrypto<T>
{
    public string Encrypt(T source, string key)
    {
        var e = Encoding.UTF8;
        var rawData = e.GetBytes(JsonConvert.SerializeObject(source));
        var cipherData = AESThenHMAC.SimpleEncryptWithPassword(rawData, key);
        return Convert.ToBase64String(cipherData);
    }

    public T Decrypt(string source, string key)
    {
        var e = Encoding.UTF8;
        var decryptedBytes = AESThenHMAC.SimpleDecryptWithPassword(Convert.FromBase64String(source), key);
        return JsonConvert.DeserializeObject<T>(e.GetString(decryptedBytes));
    }
}

... which passes the above unit test perfectly :)

War
  • 8,539
  • 4
  • 46
  • 98