1
votes

I'm trying to replicate an encryption method based on another C# method that I found.

The C# Encryption method EncryptText(word, password) call to another method AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes) to encrypt plain text:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.IO;
using System.Text;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var f = EncryptText("763059", "515t3ma5m15B4d35");//(word, password)
            Console.WriteLine(f);
        }

        public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
        {
            byte[] encryptedBytes = null;
            byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

            using (MemoryStream ms = new MemoryStream())
            {
                using (RijndaelManaged AES = new RijndaelManaged())
                {
                    AES.KeySize = 256;
                    AES.BlockSize = 128;

                    var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                    AES.Key = key.GetBytes(AES.KeySize / 8);
                    AES.IV = key.GetBytes(AES.BlockSize / 8);

                    AES.Mode = CipherMode.CBC;

                    using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                        cs.Close();
                    }
                    encryptedBytes = ms.ToArray();
                }
            }

            return encryptedBytes;
        }

        public static string EncryptText(string input, string password)
        {
            byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
            byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

            passwordBytes = SHA256.Create().ComputeHash(passwordBytes);

            byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);
            string result = Convert.ToBase64String(bytesEncrypted);

            return result;
        }
    }
}

Using word 763059 and password 515t3ma5m15B4d35, the output is the following:

3cHrXxxL1Djv0K2xW4HuCg==

UPDATE:

Now, I created a Java Class main where I'm trying to replicate previous code:

public class main {

    final static String PASSWORD = "515t3ma5m15B4d35";
    final static byte[] SALT = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
    final static int KEY_SIZE = 256;
    final static int BLOCK_SIZE = 128;
    final static int ITERATIONS = 1000;

    public static void main(String[] args) {
        System.out.println(encryptText("763059", PASSWORD));
    }

    public static String encryptText(String word, String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes("UTF-8"));
            password = new String(md.digest(), "UTF-8");

            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec spec = new PBEKeySpec(password.toCharArray(), SALT, ITERATIONS, KEY_SIZE);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES");

            byte[] iv = new byte[BLOCK_SIZE / 8];
            IvParameterSpec ivspec = new IvParameterSpec(iv);
            Cipher ci = Cipher.getInstance("AES/CBC/PKCS5Padding");
            ci.init(Cipher.ENCRYPT_MODE, skey, ivspec);
            byte[] result = ci.doFinal(word.getBytes("UTF-8"));

            return DatatypeConverter.printBase64Binary(result);

        } catch (NoSuchAlgorithmException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | InvalidKeySpecException ex) {
            return null;
        }
    }

}

UPDATE:

I read about using 256 bits keys in Java, and I found that I need to add Java Cryptography Extensions to allow 256 keys (Because I'm working with JDK7).

Then I added the libreries to the project, also I change the line:

KeySpec spec = new PBEKeySpec(password.toCharArray(), SALT, ITERATIONS, KEY_SIZE);

With the Key Value:

final static int KEY_SIZE = 256;

Now the output is the following:

J1xbKOjIeXbQ9njH+67RNw==

I still can't achieve my goal. Any Suggestion?

3
Maybe I am misreading, but are you not using a 256-bit key in c# and a 128-bit key in Java?John Wu
@JohnWu, Oh, really? In what part?TimeToCode
I would suggest you debug.log the inputs and outputs of each individual cryptographic call so you can isolate the problem more easily.John Wu
Wow, you're completely missing the part where you first SHA-256 the password (for some unknown reason). Are you even trying? Voted down; why don't you start by making the Java code at least look like the C# code?Maarten Bodewes
Another problem is that the data of a byte-array (the hash of the password) is stored in a char-array since PBEKeySpec expects a char-array. Inside the PBKDF2-provider the char-array is converted back into a byte-array using UTF-8 encoding. The original (used in the C#-code) and the reconverted byte-array will differ concerning values >= 0x80 (which are mostly present). This results in different keys and IVs in both codes and thus, in different encrypted data. An alternative is PKCS5S2ParametersGenerator from Bouncy Castle for the generation of key and IV which expects a byte-array.Topaco

3 Answers

1
votes

Finally I decided to use the BouncyCastle API to use the functionality of RijndaelEngine, as well as to generate the 256-bit key with PKCS5S2ParametersGenerator.

I created the RijndaelEncryption class to be able to perform the encryption as in the C# code:

public class RijndaelEncryption {

    public String encryptString(String word, String password, byte[] salt, int iterations, int keySize, int blockSize) {
        try {
            byte[] pswd = sha256String(password, "UTF-8");
            PKCS5S2ParametersGenerator key = keyGeneration(pswd, salt, iterations);
            ParametersWithIV iv = generateIV(key, keySize, blockSize);
            BufferedBlockCipher cipher = getCipher(true, iv);
            byte[] inputText = word.getBytes("UTF-8");
            byte[] newData = new byte[cipher.getOutputSize(inputText.length)];
            int l = cipher.processBytes(inputText, 0, inputText.length, newData, 0);
            cipher.doFinal(newData, l);
            return new String(Base64.encode(newData), "UTF-8");
        } catch (UnsupportedEncodingException | IllegalStateException | DataLengthException | InvalidCipherTextException e) {
            return null;
        }
    }

    public BufferedBlockCipher getCipher(boolean encrypt, ParametersWithIV iv) {
        RijndaelEngine rijndael = new RijndaelEngine();
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndael));
        cipher.init(encrypt, iv);
        return cipher;
    }

    public ParametersWithIV generateIV(PKCS5S2ParametersGenerator key, int keySize, int blockSize) {
        try {
            ParametersWithIV iv = null;
            iv = ((ParametersWithIV) key.generateDerivedParameters(keySize, blockSize));
            return iv;
        } catch (Exception e) {
            return null;
        }
    }

    public PKCS5S2ParametersGenerator keyGeneration(byte[] password, byte[] salt, int iterations) {
        try {
            PKCS5S2ParametersGenerator key = new PKCS5S2ParametersGenerator();
            key.init(password, salt, iterations);
            return key;
        } catch (Exception e) {
            return null;
        }
    }

    public byte[] sha256String(String password, Charset charset) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes(charset));
            return md.digest();
        } catch (NoSuchAlgorithmException ex) {
            return null;
        }
    }

    public byte[] sha256String(String password, String charset) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes(charset));
            return md.digest();
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
            return null;
        }
    }
}

And I tested in main method:

public static void main(String[] args) {
     RijndaelEncryption s = new RijndaelEncryption();
     byte[] salt = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
     String encryptStr = s.encryptString("763059", "515t3ma5m15B4d35", salt, 1000, 256, 128);
     System.out.println("Encryptation: " + encryptStr);
}

To get:

Encryptation: 3cHrXxxL1Djv0K2xW4HuCg==

0
votes

I am not any C# expert, but there are a few things to be checked:

Reading the documentation about Rfc2898DeriveBytes I see the function is using SHA1 hash, so try you may try to use PBKDF2WithHmacSHA1

On both instances (Rfc2898DeriveBytes, PBEKeySpec) you should make sure you the key size is the same (256 bit), it is surely wrong in your Java code

You may try to encode and print the keys to really make sure they are the same.

I need to add Java Cryptography Extensions to allow 256 keys.

Depends on your JVM version. I believe Oracle JDK since v. 1.8u162 by default contains the Unlimited Strength JCE policy. If you take any current JRE version, you should be ok

Additional: you are using (static) zero array IV, which is not secure

0
votes

I have an answer for the original question. For future reference without bouncycastle.

You had a few problems.

  1. Key size needed to be 256 + 128 (blocksize as well)
  2. C# and Java byte[] don't act the same because java bytes are always signed which messes with the encryption of the password.

Both of these pieces of code give as output:

xD4R/yvV2tHajUS9p4kqJg==

C# code:

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

    namespace tryencryption
    {
        class Program
        {


        static void Main(string[] args)
        {
            var f = EncryptText("yme", "515t3ma5m15B4d35");//(word, password)
            Console.WriteLine(f);
            Console.ReadKey();
        }

        public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, string passwordString)
        {
            byte[] encryptedBytes = null;
            byte[] salt = new byte[] { (byte)0x49, (byte)0x64, (byte)0x76, (byte)0x65, (byte)0x64, (byte)0x65, (byte)0x76, (byte)0x61, (byte)0x6e, (byte)0x20, (byte)0x4d, (byte)0x65, (byte)0x76 };

            using (MemoryStream ms = new MemoryStream())
            {
                using (RijndaelManaged AES = new RijndaelManaged())
                {
                    AES.KeySize = 256;
                    AES.BlockSize = 128;

                    var key = new Rfc2898DeriveBytes(passwordString, salt, 1000);
                    AES.Key = key.GetBytes(AES.KeySize / 8);
                    AES.IV = key.GetBytes(AES.BlockSize / 8);

                    AES.Mode = CipherMode.CBC;

                    using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                        cs.Close();
                    }
                    encryptedBytes = ms.ToArray();
                }
            }

            return encryptedBytes;
        }

        public static string EncryptText(string input, string password)
        {
            byte[] bytesToBeEncrypted = Encoding.Unicode.GetBytes(input);

            byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, password);
            string result = Convert.ToBase64String(bytesEncrypted);

            return result;
        }


    }
}

Java code (this was from an android project bcs that's my usecase but should work everywhere):

        package com.example.myapplication;

    import androidx.appcompat.app.AppCompatActivity;

    import android.os.Bundle;
    import android.util.Base64;

    import java.nio.charset.StandardCharsets;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.Key;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.security.spec.AlgorithmParameterSpec;
    import java.security.spec.InvalidKeySpecException;

    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;

    public class MainActivity extends AppCompatActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            String result = encrypt("yme", "515t3ma5m15B4d35");
        }


        private static String encrypt(String word, String password) {

            byte[] salt = new byte[] { (byte)0x49, (byte)0x64, (byte)0x76, (byte)0x65, (byte)0x64, (byte)0x65, (byte)0x76, (byte)0x61, (byte)0x6e, (byte)0x20, (byte)0x4d, (byte)0x65, (byte)0x76};

            try {
                SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
                PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256 + 128);
                Key secretKey = factory.generateSecret(pbeKeySpec);
                byte[] test = secretKey.getEncoded();
                byte[] key = new byte[32];
                byte[] iv = new byte[16];
                System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);
                System.arraycopy(secretKey.getEncoded(), 32, iv, 0, 16);


                SecretKeySpec secret = new SecretKeySpec(key, "AES");
                AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
   //Realise Im using UTF16 here! Maybe you need UTF8
                byte[] plaintextintobytes  =word.getBytes(StandardCharsets.UTF_16LE);
                byte[] encrypted = cipher.doFinal(plaintextintobytes);
                String encryptedInformation = Base64.encodeToString(encrypted, Base64.NO_WRAP);
                return encryptedInformation;

            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (InvalidAlgorithmParameterException e) {
                e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            } catch (BadPaddingException e) {
                e.printStackTrace();
            }

            return "";
        }


    }