1

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?

3
  • 1
    It's better to use fixed IV (just for testing purposes of course, not in real use), and attach the expected result from java code, and the result you get from C#. Commented Oct 11, 2021 at 10:53
  • 1
    I think C# uses UTF16 by default for strings, so you should enforce the use of UTF8 to get the byte array: byte[] bytes = System.Text.UTF8Encoding.UTF8.GetBytes(utf8string); Commented Oct 11, 2021 at 10:56
  • 3
    sb.append(new String(generateIV, Charsets.UTF_8)); this code is not translated at all. It is an intrinsically dubious piece of code, of course, since there is no reason to assume generateIV contains a valid UTF-8 sequence, unless generateIV() somehow restricts its output, but there you go. Commented Oct 11, 2021 at 10:56

1 Answer 1

2

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);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.