0

I am trying to convert an existing C# encryption method to Java, but have hit a roadblock as below

For example when I encrypt a basic string "12345" with c# I get this output 8ZQZEUrctqP1PMmQxVtCcA==

And when I encrypt the same string with java I get this jkEZp2cfeGXVE/IxIW6X3g==

private static string Encrypt(string plainText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations,
                        string initVector, int keySize)
{
    try
    {
        byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
        byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
        byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
        byte[] keyBytes = password.GetBytes(keySize / 8);
        RijndaelManaged symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC };
        ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
        MemoryStream memoryStream = new MemoryStream();
        CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
        cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
        cryptoStream.FlushFinalBlock();
        byte[] cipherTextBytes = memoryStream.ToArray();
        memoryStream.Close();
        cryptoStream.Close();
        string cipherText = Convert.ToBase64String(cipherTextBytes);
        return cipherText;
    }
    catch (Exception execp)
    {
        MessageBox.Show(string.Format("Exception in Encrypt function\r\nError: {0}", execp.Message));
        return "";
    }
}

private static string Decrypt(string cipherText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations,
                                string initVector, int keySize)
{
    try
    {
        if (cipherText == string.Empty)
            return "";

        byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
        byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
        byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
        PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
        byte[] keyBytes = password.GetBytes(keySize / 8);
        RijndaelManaged symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC };
        ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
        MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
        CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
        byte[] plainTextBytes = new byte[cipherTextBytes.Length];
        int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
        memoryStream.Close();
        cryptoStream.Close();
        string plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
        return plainText;
    }
    catch (Exception execp)
    {
        MessageBox.Show(string.Format("Exception in Decrypt function\r\nError: {0}", execp.Message));
        return "";
    }
}

public static string ProtectPassword(string plainText)
{
    try
    {
        const string passPhrase = "Ydfv324232r!23%47%7^&ex>,1"; // can be any string
        const string saltValue = "s@1tValue"; // can be any string
        const string hashAlgorithm = "SHA1"; // can be "MD5"
        const int passwordIterations = 101; // can be any number
        const string initVector = "@1B2vQ94eZF6g7H1"; // must be 16 bytes
        const int keySize = 256; // can be 192 or 128

        string ret = Encrypt(plainText, passPhrase, saltValue, hashAlgorithm, passwordIterations, initVector, keySize);

        return ret;
    }
    catch (Exception execp)
    {
        MessageBox.Show(string.Format("Exception in ProtectPassword function\r\nError: {0}", execp.Message));
        return "";
    }
}

public static string UnprotectPassword(string cipherText)
{
    try
    {
        const string passPhrase = "Ydfv324232r!23%47%7^&ex>,1"; // can be any string
        const string saltValue = "s@1tValue"; // can be any string
        const string hashAlgorithm = "SHA1"; // can be "MD5"
        const int passwordIterations = 101; // can be any number
        const string initVector = "@1B2vQ94eZF6g7H1"; // must be 16 bytes
        const int keySize = 256; // can be 192 or 128

        string ret = Decrypt(cipherText, passPhrase, saltValue, hashAlgorithm, passwordIterations, initVector, keySize);

        return ret;
    }
    catch (Exception execp)
    {
        MessageBox.Show(string.Format("Exception in UnprotectPassword function\r\nError: {0}", execp.Message));
        return "";
    }
}

And below is what I have converted to java, yet still don't quite get the same encrypted inputs and outputs - I just renamed "ProtectPassword" to "Encrypt" and "UnprotectPassword" to "Decrypt"

import java.io.ByteArrayOutputStream;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class Encryption {

    public static String Encrypt(String str)
    {
        try 
        {
            String passPhrase = "Ydfv324232r!23%47%7^&ex>,1";
            String saltValue = "s@1tValue";
            int passwordIterations = 101;
            int keySize = 256;
            String initVector = "@1B2vQ94eZF6g7H1";

            SecureRandom random = new SecureRandom();
            byte[] salt = new byte[16];
            random.nextBytes(salt);

            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), saltValue.getBytes("UTF-8"), passwordIterations, keySize);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(initVector.getBytes("UTF-8")));            
            byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8"));

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();           
            outputStream.write(encryptedText);

            // properly encode the complete cipher text
            return DatatypeConverter.printBase64Binary(outputStream.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }   

    public static String Decrypt(String str)
    {
        try 
        {
            String passPhrase = "Ydfv324232r!23%47%7^&ex>,1";
            String saltValue = "s@1tValue";
            int passwordIterations = 101;
            int keySize = 256;
            String initVector = "@1B2vQ94eZF6g7H1";

            byte[] ciphertext = DatatypeConverter.parseBase64Binary(str);           

            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), saltValue.getBytes(), passwordIterations, keySize);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector.getBytes("UTF-8")));
            byte[] plaintext = cipher.doFinal(ciphertext);

            return new String(plaintext, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

2 Answers2

1

The ciphertexts differ because PasswordDeriveBytes in the C#-code and PBKDF2WithHmacSHA1 in the Java-Code generate different keys:

In addition to the C#-type PasswordDeriveBytes, there is also the C#-type Rfc2898DeriveBytes which is an implementation of PBKDF2 with SHA-1 and thus, the counterpart to PBKDF2WithHmacSHA1 in the Java-code.

If possible, the Rfc2898DeriveBytes should be used in the C#-code instead of PasswordDeriveBytes, see e.g. here or here, section PBKDF2. Then both codes return the same ciphertext.

To my knowledge, there is no provider that offers a Java implementation of the C#-type PasswordDeriveBytes. However, there are functionally identical Java implementations on the Internet, e.g. here. If the Java-code uses such an implementation instead of PBKDF2WithHmacSHA1, both codes return the same ciphertext. But as already mentioned, this should be the second choice.

Topaco
  • 40,594
  • 4
  • 35
  • 62
0

As promised below is the solution - thanks again for the support

public static class Encryption
{
    public static string Encrypt(string text)
    {
        var thePassword = "%cFRm*F)N9Rq[6#5";
        byte[] IV = Encoding.UTF8.GetBytes("7!,V5u]Bu>q>7zY'");

        var md5 = new MD5CryptoServiceProvider();
        var password = md5.ComputeHash(Encoding.ASCII.GetBytes(thePassword));
        var cipher = new RijndaelManaged();            
        var encryptor = cipher.CreateEncryptor(password, IV);

        var buffer = Encoding.ASCII.GetBytes(text);
        return Convert.ToBase64String(encryptor.TransformFinalBlock(buffer, 0, buffer.Length));
    }

    public static string Decrypt(string text)
    {
        var thePassword = "%cFRm*F)N9Rq[6#5";
        byte[] IV = Encoding.UTF8.GetBytes("7!,V5u]Bu>q>7zY'");

        var md5 = new MD5CryptoServiceProvider();
        var password = md5.ComputeHash(Encoding.ASCII.GetBytes(thePassword));
        var cipher = new RijndaelManaged();
        var decryptor = cipher.CreateDecryptor(password, IV);

        byte[] input = Convert.FromBase64String(text);
        var newClearData = decryptor.TransformFinalBlock(input, 0, input.Length);
        return Encoding.ASCII.GetString(newClearData);
    }
}

And the java equivalent

public class Encryption {

    public static String Encrypt(String str)
    {
        try 
        {
            String thePassword = "%cFRm*F)N9Rq[6#5";
            byte[] encryptedData;
            byte[] IV = "7!,V5u]Bu>q>7zY'".getBytes();

            MessageDigest digest = MessageDigest.getInstance("MD5");            
            SecretKeySpec password = new SecretKeySpec(digest.digest(thePassword.getBytes()), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec IVParamSpec = new IvParameterSpec(IV);
            cipher.init(Cipher.ENCRYPT_MODE, password, IVParamSpec);
            encryptedData = cipher.doFinal(str.getBytes());

            return DatatypeConverter.printBase64Binary(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }   

    public static String Decrypt(String str)
    {
        try 
        {
            String thePassword = "%cFRm*F)N9Rq[6#5";
            byte[] encryptedData = DatatypeConverter.parseBase64Binary(str); 
            byte[] IV = "7!,V5u]Bu>q>7zY'".getBytes();

            MessageDigest digest = MessageDigest.getInstance("MD5");            
            SecretKeySpec password = new SecretKeySpec(digest.digest(thePassword.getBytes()), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec IVParamSpec = new IvParameterSpec(IV);
            cipher.init(Cipher.DECRYPT_MODE, password, IVParamSpec);

            byte[] decryptedVal = cipher.doFinal(encryptedData);
            return new String(decryptedVal); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • If the key derivation is switched to MD5, the following should be considered: 1) PBKDF2 has some advantages over a regular cryptographic hash (like MD5), see e.g. [here](https://crypto.stackexchange.com/a/62833). Just a reminder: To use PBKDF2 (with SHA-1), only the class `PasswordDeriveBytes` must be replaced by `Rfc2898DeriveBytes` in the original C#-code. Changes to the Java-code are not necessary. 2) MD5 creates a 16-byte-hash. If this is used as an AES-key, then this corresponds to AES-128 (and no longer AES-256). – Topaco Apr 30 '19 at 15:05
  • Thanks, I will consider spending more time on it if time permits, its just for a side project of mine used for serial number generation, so parts of the serial are encrypted more than once and if the person has the skill to reverse the application it will take them more time than writing it from scratch. – Brett Lombard Apr 30 '19 at 19:16