Home > Back-end >  Converting Java AES CBC Encryption to C#
Converting Java AES CBC Encryption to C#

Time:10-11

I am trying to convert the following Java encryption snippet to C#

private final String encryptToken(String str) {
    byte[] generateIV = generateIV(16);
    Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
    instance.init(1, new SecretKeySpec(new byte[]{55, 66, 53, 68, 55, 66, 67, 50, 52, 66, 53, 67, 52, 69, 51, 65, 56, 48, 70, 66, 66, 67, 50, 65, 49, 49, 53, 54, 69, 52, 51, 55}, "AES/CBC/PKCS5Padding"), new IvParameterSpec(generateIV));
    Charset charset = Charsets.UTF_8;
    if (str != null) {
        byte[] bytes = str.getBytes(charset);
        Intrinsics.checkExpressionValueIsNotNull(bytes, "(this as java.lang.String).getBytes(charset)");
        byte[] doFinal = instance.doFinal(bytes);
        StringBuilder sb = new StringBuilder();
        sb.append(new String(generateIV, Charsets.UTF_8));
        String encodeToString = Base64.encodeToString(doFinal, 0);
        Intrinsics.checkExpressionValueIsNotNull(encodeToString, "Base64.encodeToString(cipherText, Base64.DEFAULT)");
        if (encodeToString != null) {
            sb.append(StringsKt.trim((CharSequence) encodeToString).toString());
            return sb.toString();
        }
        throw new TypeCastException("null cannot be cast to non-null type kotlin.CharSequence");
    }
    throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
}

Here is my C# code:

        public static string Encrypt(string token)
        {
            byte[] IV;
            byte[] encrypted;
            byte[] key = new byte[] { 55, 66, 53, 68, 55, 66, 67, 50, 52, 66, 53, 67, 52, 69, 51, 65, 56, 48, 70, 66, 66, 67, 50, 65, 49, 49, 53, 54, 69, 52, 51, 55 };
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = key;
                aesAlg.IV = GenerateIv(16);
                IV = aesAlg.IV;
                aesAlg.Mode = CipherMode.CBC;
                aesAlg.Padding = PaddingMode.PKCS7;

                var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                using (var msEncrypt = new MemoryStream())
                {
                    using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (var swEncrypt = new StreamWriter(csEncrypt))
                        {
                            swEncrypt.Write(Encoding.UTF8.GetBytes(token));
                        }
                        encrypted = msEncrypt.ToArray();

                        StringBuilder sb = new StringBuilder();
                        sb.Append(Encoding.UTF8.GetString(IV));

                        string encodedString = Convert.ToBase64String(encrypted);

                        sb.Append(encodedString.ToString());
                        return sb.ToString();

                    }
                }
            }
            return "";
        }

I am using the result of the encryption routine to authorize against an API that I do not have control over. Using my C# code, the API returns "unauthorized" which is a result of my encryption routine not being quite correct.

I note that the Java method uses PKCS5 padding, but the only option available in .NET is PKCS7. However reading this previous article, I understand they are the same, and therefore it shouldn't matter.

If I use a temporary IV of h62PmLFO5Yq4SxQw and a token of 4a97adfc-d25c-485b-84af-86c93ff28b20, the Java code returns: h62PmLFO5Yq4SxQwgoOqOLrjcZmnYbKxEDhl1hsDeCGUmEv8kwwP337JfYyvjuXgaDdND9vqSeAd9NpH and my C# code returns: h62PmLFO5Yq4SxQwfUXW396ss2Pzopk0FHC/7A==

Is anyone able to advise in any obvious differences between the Java code and mine?

CodePudding user response:

The reason is you are using StreamWriter, which is intended to write text into the stream, but then you write raw bytes there:

swEncrypt.Write(Encoding.UTF8.GetBytes(token));

Overload used here is Write(object) with description "Writes text representation of the object to the stream, by calling ToString() method on that object". So basically you are encrypting "System.Byte[]" string, not the token.

You can just remove that text writer and write bytes directly into crypto stream (note also leaveOpen: true):

public static string Encrypt(string token)
{
    byte[] IV;
    byte[] encrypted;
    byte[] key = new byte[] { 55, 66, 53, 68, 55, 66, 67, 50, 52, 66, 53, 67, 52, 69, 51, 65, 56, 48, 70, 66, 66, 67, 50, 65, 49, 49, 53, 54, 69, 52, 51, 55 };
    using (var aesAlg = new RijndaelManaged())
    {
        aesAlg.Key = key;
        var iv = Encoding.UTF8.GetBytes("h62PmLFO5Yq4SxQw");
        aesAlg.IV = iv;
        IV = aesAlg.IV;
        aesAlg.Mode = CipherMode.CBC;
        aesAlg.Padding = PaddingMode.PKCS7;

        var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        using (var msEncrypt = new MemoryStream())
        {
            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write, leaveOpen: true))
            {
                // just write to stream directly
                csEncrypt.Write(Encoding.UTF8.GetBytes(token));
            }
            // read here, so that we are sure writing is finished
            encrypted = msEncrypt.ToArray();

            StringBuilder sb = new StringBuilder();
            sb.Append(Encoding.UTF8.GetString(IV));

            string encodedString = Convert.ToBase64String(encrypted);

            sb.Append(encodedString.ToString());
            return sb.ToString();
        }
    }
    return "";
}

Or you can continue using StreamWriter but then do it right:

// tell it you need UTF8 without BOM
using (var swEncrypt = new StreamWriter(csEncrypt, new UTF8Encoding(false)))
{
    // then write string there, not bytes
    swEncrypt.Write(token);
}
  • Related