Question

We have a use case where we are looking to decrypt data in java which was previously encrypted using .NET

Here is the Crypt class used to Encrypt and Decrypt Data in .NET :

https://gist.github.com/epinapala/9400064

I separated the Decrypt method and executed, and It works fine in .NET, I am looking to port the code below:

using System.IO;
using System;
using System.Text;
using System.Security.Cryptography;

class Program
{
    static void Main()
    {
        {
            string line = "f5EBWYipPKG1FpyTEP7pyPLLJNpqrvwYJFs8iMw9mOY$";

            line = line.Replace('-', '+').Replace('_', '/').Replace('$', '=');

            while (line.Length % 4 != 0){
                    line = line.PadRight(line.Length + (4 - line.Length % 4), '=');
            }
             Console.WriteLine(line);


            byte[] inputBuffer = Convert.FromBase64String(line);
             Console.WriteLine(inputBuffer.Length);
            byte[] numArray = new byte[16];
            byte[] key = new PasswordDeriveBytes("ThisIsMyKey", new byte[13]
      {
        (byte) 73,
        (byte) 118,
        (byte) 97,
        (byte) 110,
        (byte) 32,
        (byte) 77,
        (byte) 101,
        (byte) 100,
        (byte) 118,
        (byte) 101,
        (byte) 100,
        (byte) 101,
        (byte) 118
      }).GetBytes(16);

      Rijndael rijndael = GetRijndael(key);

      for (int index = 0; index < 16; ++index){
          numArray[index] = inputBuffer[index];
      }
           rijndael.IV = numArray;

            string decodedString = Encoding.UTF8.GetString(rijndael.CreateDecryptor().TransformFinalBlock(inputBuffer, 16, inputBuffer.Length - 16));
            Console.WriteLine(decodedString);
        }
    }


    private static Rijndael GetRijndael(byte[] key)
    {
      Rijndael rijndael = Rijndael.Create();
      rijndael.Mode = CipherMode.CBC;
      rijndael.KeySize = key.Length * 8;
      rijndael.Key = key;
      rijndael.Padding = PaddingMode.PKCS7;
      return rijndael;
    }
}

Here is what I tried so far:

   public static void main() {
     ecnryptedData = "f5EBWYipPKG1FpyTEP7pyPLLJNpqrvwYJFs8iMw9mOY$";
     ecnryptedData = ecnryptedData.replace('-', '+')
         .replace('_', '/').replace('$', '=');

     while (ecnryptedData.length() % 4 != 0) {
         ecnryptedData = StringUtils.rightPad(ecnryptedData, ecnryptedData.length() + (4 - ecnryptedData.length() % 4),
             "=");
     }
     System.out.println(Decrypt(ecnryptedData, "ThisIsMyKey"));
 }

 public static String Decrypt(String text, String key) throws Exception {
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
     byte[] keyBytes = new byte[16];
     byte[] b = key.getBytes("UTF-8");
     int len = b.length;
     if (len > keyBytes.length) len = keyBytes.length;
     System.arraycopy(b, 0, keyBytes, 0, len);
     SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
     AlgorithmParameterSpec spec = new IvParameterSpec(keyBytes);
     cipher.init(Cipher.DECRYPT_MODE, keySpec, spec);

     BASE64Decoder decoder = new BASE64Decoder();
     byte[] results = cipher.doFinal(decoder.decodeBuffer(text));
     return new String(results, "UTF-8");
 }

I am getting the exception : "Given final block not properly padded". Not sure what I did wrong. I appreciate any help on this issue.

[[EDIT]]:

I figured it out. The thing I was missing was that I was using a wrong salt. I decoded the constant salt used(as bytes) in the .NET code into its String counter part, and used it as Salt in my Java code.

Also Thanks to the PasswordDerivedBytes class found here :

https://github.com/getkksingh/TimesSSO/blob/fc78e4b30d5fd347341757c02eea6c9271575515/src/java/com/timesgroup/sso/hibernate/apis/PasswordDeriveBytes.java

private static byte[] decryptData(byte[] data, String password,
            String paddingMode, String salt) throws Exception {
        if (data == null || data.length == 0)
            throw new IllegalArgumentException("data is empty");
        if (password == null || password == "")
            throw new IllegalArgumentException("password is empty");
        if (salt == null || salt == "")
            salt = ".";
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        byte[] saltBytes = salt.getBytes("UTF8");
        byte[] passBytes = password.getBytes("UTF8");

        PKCS5S1ParametersGenerator generator = new PasswordDeriveBytes(
                new SHA1Digest());
        generator.init(passBytes, saltBytes, 100);

        byte[] key = ((KeyParameter) generator.generateDerivedParameters(256))
                .getKey();
        passBytes = new byte[16];
        saltBytes = new byte[16];
        System.arraycopy(key, 0, passBytes, 0, 16);
        System.arraycopy(key, 16, saltBytes, 0, 16);

        Cipher cipher = Cipher.getInstance("AES/CBC/" + paddingMode, "BC");
        SecretKeySpec keySpec = new SecretKeySpec(passBytes, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec,
                new IvParameterSpec(saltBytes));

        byte[] original = cipher.doFinal(data);
        return original;
    }
Was it helpful?

Solution

You are using a custom Base64 variant in the C# code: _ and - as characters 62 and 63 and $ as padding. The C# code converts these to normal Base64 before attempting decoding.

Your java code passes invalid data to the Base64 decoder. This should throw an IllegalArgumentException before the code even reaches the crypto.

Another issue is that your C# code uses PBKDF1 to derive the key from the password, the java code directly uses the password as key bytes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top