0

I'm looking for a way to encrypt a byte array in unity c# and decrypt on a node.js server.

I'm open to any implementation of either but I have currently gone with the below code which encrypts/decrypts fine in unity but I receive the error:

TypeError: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length

When decrypting a file encrypted in unity using RijndaelManaged 128

Find the encrypting and decrypting code below:

Unity C# Encrypt

private void GenerateEncryptionKey(string userID)
{
    //Generate the Salt, with any custom logic and using the user's ID
    StringBuilder salt = new StringBuilder();
    for (int i = 0; i < 8; i++)
    {
        salt.Append("," + userID.Length.ToString());
    }

    Rfc2898DeriveBytes pwdGen = new Rfc2898DeriveBytes (Encoding.UTF8.GetBytes(userID), Encoding.UTF8.GetBytes(salt.ToString()), 100);
    m_cryptoKey = pwdGen.GetBytes(KEY_SIZE / 8);
    m_cryptoIV = pwdGen.GetBytes(KEY_SIZE / 8);
}

public void Save(string path)
{
    string json = MiniJSON.Json.Serialize(m_saveData);

    using (RijndaelManaged crypto = new RijndaelManaged())
    {
        crypto.BlockSize = KEY_SIZE;
        crypto.Padding = PaddingMode.PKCS7;
        crypto.Key = m_cryptoKey;
        crypto.IV = m_cryptoIV;
        crypto.Mode = CipherMode.CBC;

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

        byte[] compressed = null;

        using (MemoryStream compMemStream = new MemoryStream())
        {
            using (StreamWriter writer = new StreamWriter(compMemStream, Encoding.UTF8))
            {
                writer.Write(json);
                writer.Close();

                compressed = compMemStream.ToArray();
            }
        }

        if (compressed != null)
        {
            using (MemoryStream encMemStream = new MemoryStream(compressed))
            {
                using (CryptoStream cryptoStream = new CryptoStream(encMemStream, encryptor, CryptoStreamMode.Write))
                {
                    using (FileStream fs = File.Create(GetSavePath(path)))
                    {
                        byte[] encrypted = encMemStream.ToArray();

                        fs.Write(encrypted, 0, encrypted.Length);
                        fs.Close();
                    }
                }
            }
        }
    }
}

ignore the compressed bit, I'll eventually be compressing the data for encryption but I have removed it in this example.

Node.JS Decrypt

var sUserID = "hello-me";
var sSalt = "";

for (var i = 0; i < 8; i++)
{
    sSalt += "," + sUserID.length;
}

var KEY_SIZE = 128;

crypto.pbkdf2(sUserID, sSalt, 100, KEY_SIZE / 4, function(cErr, cBuffer){
    var cKey = cBuffer.slice(0, cBuffer.length / 2);
    var cIV = cBuffer.slice(cBuffer.length / 2, cBuffer.length);

    fs.readFile("save.sav", function (cErr, cData){
        try
        {
            var cDecipher = crypto.createDecipheriv("AES-128-CBC", cKey, cIV);

            var sDecoded = cDecipher.update(cData, null, "utf8");
            sDecoded += cDecipher.final("utf8");
            console.log(sDecoded);
        }
        catch(e)
        {
            console.log(e.message);
            console.log(e.stack);
        }
    });
});

I believe the problem is something to do with padding! I am not using:

cryptoStream.FlushFinalBlock();

when saving the file in c# land because for some reason after doing that c# can't decrypt it anymore and it doesn't really have an effect on the ability of node to decrypt it either, but maybe I'm just missing something in the decryption of it with padding?

Any help is appreciated

Tristan
  • 3,845
  • 5
  • 35
  • 58
  • did you look at this question? [here](http://stackoverflow.com/questions/21292142/decyrpting-aes256-with-node-js-returns-wrong-final-block-length) – Rudolfwm Aug 13 '14 at 16:14
  • @Rudolfwm I have seen that one but unfortunately I have already made the suggested changes and still no luck, thanks anyway – Tristan Aug 14 '14 at 09:01
  • I saw you fixed it, I also noticed: `byte[] encrypted = encMemStream.ToArray();` is reading from the wrong stream. It should be `byte[] encrypted = cryptoStream .ToArray();` – Rudolfwm Aug 14 '14 at 13:01

2 Answers2

2

One problem is that you're using PasswordDeriveBytes which according to this article is for PBKDF1, whereas Rfc2898DeriveBytes is for PBKDF2. You're using PBKDF2 in your node script.

Then you should check that your cKey and cIV values match between C# and node.

mscdex
  • 104,356
  • 15
  • 192
  • 153
  • Hi mscdex, thanks for the answer, I previously tried Rfc2898DeriveBytes, but you are right I should be using that and that's a mistake in my example. Both my key and iv values are the same between c# and node. I believe its something to do with padding but have tried a few things without success – Tristan Aug 14 '14 at 09:10
0

Okay well it seems that order of operation is very important when encrypting and decryption using RijndaelManaged.

Below is the code to encrypt and decrypt in Unity and works with the node.js code posted in the question.

public void Save(string path)
{
    string json = MiniJSON.Json.Serialize(m_saveData);

    using (RijndaelManaged crypto = new RijndaelManaged())
    {
        crypto.BlockSize = KEY_SIZE;
        crypto.Padding = PaddingMode.PKCS7;
        crypto.Key = m_cryptoKey;
        crypto.IV = m_cryptoIV;
        crypto.Mode = CipherMode.CBC;

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

        byte[] compressed = null;

        using (MemoryStream compMemStream = new MemoryStream())
        {
            using (StreamWriter writer = new StreamWriter(compMemStream, Encoding.UTF8))
            {
                writer.Write(json);
                writer.Close();

                //compressed = CLZF2.Compress(compMemStream.ToArray());
                compressed = compMemStream.ToArray();
            }
        }

        if (compressed != null)
        {
            using (MemoryStream encMemStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream = new CryptoStream(encMemStream, encryptor, CryptoStreamMode.Write))
                {
                    cryptoStream.Write(compressed, 0, compressed.Length);
                    cryptoStream.FlushFinalBlock();

                    using (FileStream fs = File.Create(GetSavePath(path)))
                    {
                        encMemStream.WriteTo(fs);
                    }
                }
            }
        }
    }
}

public void Load(string path)
{
    path = GetSavePath(path);

    try
    {
        byte[] decrypted = null;

        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            using (RijndaelManaged crypto = new RijndaelManaged())
            {
                crypto.BlockSize = KEY_SIZE;
                crypto.Padding = PaddingMode.PKCS7;
                crypto.Key = m_cryptoKey;
                crypto.IV = m_cryptoIV;
                crypto.Mode = CipherMode.CBC;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = crypto.CreateDecryptor(crypto.Key, crypto.IV);

                using (CryptoStream cryptoStream = new CryptoStream(fs, decryptor, CryptoStreamMode.Read))
                {
                    using (MemoryStream decMemStream = new MemoryStream())
                    {
                        var buffer = new byte[512];
                        var bytesRead = 0;

                        while ((bytesRead = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            decMemStream.Write(buffer, 0, bytesRead);
                        }

                        //decrypted = CLZF2.Decompress(decMemStream.ToArray());
                        decrypted = decMemStream.ToArray();
                    }
                }
            }
        }

        if (decrypted != null)
        {
            using (MemoryStream jsonMemoryStream = new MemoryStream(decrypted))
            {
                using (StreamReader reader = new StreamReader(jsonMemoryStream))
                {
                    string json = reader.ReadToEnd();

                    Dictionary<string, object> saveData = MiniJSON.Json.Deserialize(json) as Dictionary<string, object>;

                    if (saveData != null)
                    {
                        m_saveData = saveData;
                    }
                    else
                    {
                        Debug.LogWarning("Trying to load invalid JSON file at path: " + path);
                    }
                }
            }
        }
    }
    catch (FileNotFoundException e)
    {
        Debug.LogWarning("No save file found at path: " + path);
    }
}
Tristan
  • 3,845
  • 5
  • 35
  • 58