1

Code in angular using crypto-js:

let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';

let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
    JSON.stringify(data),
    secret_key,
    {
      iv: secret_iv,
      padding: CryptoJS.pad.ZeroPadding
    }
 ).toString();

let requestObj = {
    input: encryptedString.trim()
  }

I am not able to do same encryption in android. Android Code

String key32Char = "12345123451234512345123451234509";
String iv32Char = "12345123451234512345123451234509";
byte[] srcBuff = jsonString.getBytes("UTF-8");

//SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char.getBytes(), "AES");
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char.getBytes());

SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.decode(key32Char, Base64.NO_WRAP), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.decode(iv32Char, Base64.NO_WRAP));

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = Base64.encodeToString(dstBuff, Base64.NO_WRAP);

JSONObject requestObj = new JSONObject();
requestObj.put("input", encryptedString);
  1. What CryptoJS.enc.Hex.parse(key) line does ?
  2. How to do same encryption ?
Mitul Varmora
  • 3,832
  • 1
  • 12
  • 21

2 Answers2

1
  1. IV and Key:To match the key and IV part both must use either base64 or hex decodings.

    In Hex encoded string there are 32 hex char that makes 128-bit. However, the same string can be rejected by base64 decode and if not rejected the output will not be 128-bit. You need to use

    byte[] bytes = new BigInteger("7F" + str, 16).toByteArray();
    SecretKeySpec key = new SecretKeySpec(bytes, 1, bytes.length-1, "AES");
    

    to convert the hex string into byte array.

  2. padding: CryptoJS.pad.ZeroPadding is useful if your data size is an exact multiple of 128. Otherwise, you need to use this parameter to say that I'll use this for testing my new padding scheme. You need to better use Pkcs7 that was the default.

    In Java you need getInstance("AES/CBC/PKCS5Padding");

  3. Mode of operation: The default in JS is CBC, therefor you need the same in Java, as above getInstance("AES/CBC/PKCS5Padding");

  4. Output: To compare the outputs you need to see the same result. In Java you convert the output into base64, so you need the same for JS.

As you can see, you must do the same steps, parameters for both.

Note that: CBC mode is archaic and you should prefer authenticated encryption modes like AES-GCM or ChaCha20-Poly1305. They not only provides confidentiality but also integrity and authentication. Unfortunately, crypto-js doesn't have them. But you can use some other JS libraries for that.

kelalaka
  • 5,064
  • 5
  • 27
  • 44
  • 1
    Did you apply `(bytes, 1, bytes.length-1, "AES");` because `"7F"` is added to save the leading zeroes. – kelalaka Nov 29 '19 at 11:05
  • Sorry, I forgot to do that, after doing exact same again, encryption works but output is not achieved same in android. – Mitul Varmora Nov 29 '19 at 13:12
  • Did you use the same output encoding? – kelalaka Nov 29 '19 at 13:13
  • Angular side output is shown as in question. In Android i used: Base64.encodeToString(dstBuff, Base64.DEFAULT); Angular side zero padding is used. how can i do same zero padding in android? – Mitul Varmora Nov 29 '19 at 13:21
  • That is just string, you need to convert base64 or try to convert both hex. I've given you link for the base64 for JS – kelalaka Nov 29 '19 at 13:24
  • Actually i am replicating angular code to android. So i need string conversion same in android as done in angular after CryptoJS.AES.encrypt(...).toString(). How can i do same string conversion in android ? – Mitul Varmora Nov 29 '19 at 13:43
  • Why do you store as a string. Encryption outputs are binary and instead of string base64 is preferred. If you are insist the see [UTF-8 byte[] to String](https://stackoverflow.com/questions/8512121/utf-8-byte-to-string) – kelalaka Nov 29 '19 at 13:49
  • If i use PKCS7 or PKCS5 in both android and angular. then i am getting same output. but if i use CryptoJS.pad.ZeroPadding in angular and AES/CBC/NoPadding in android, then outputs are not same. – Mitul Varmora Dec 03 '19 at 06:10
1

As name suggests, CryptoJS.enc.Hex.parse(key) parses a Hex String and uses it as key. So you need to do the same for your java code.

In addition, you need to select correct encryption mode and padding too. Your CryptoJs code uses CBC mode so you need to do same in Java Code. Your are using zero padding in CryptoJs side which is not available in java, so you need to do it manually. But in general, using zero padding is a bad idea and it is better to use PKCS5 padding for example which is default for CryptoJs.

With these things, these 2 codes match:

let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';

let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
    "0123456789012345x",
    secret_key,
    {
      iv: secret_iv,
    }
 ).toString();

let requestObj = {
    input: encryptedString.trim()
  }

Java:

  public void doit()
  {
    byte[] key32Char = hexStringToByteArray("12345123451234512345123451234509");
    byte[] iv32Char = hexStringToByteArray("12345123451234512345123451234509");
    byte[] srcBuff = "0123456789012345x".getBytes();

    SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char, "AES");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char);
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] dstBuff = cipher.doFinal(srcBuff);
        String encryptedString = new String(Base64.getEncoder().encode(dstBuff));
      System.out.print(encryptedString);
    }
    catch(Exception e) {
      System.out.print(e.toString());
      return;
    }

  }
  public byte[] hexStringToByteArray(String s) 
  {
    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                             + Character.digit(s.charAt(i+1), 16));
    }
    return data;
  }

Update:

If you are forced to use bad idea of zero padding, you need to keep real size of data and do padding manually:

public void doitZeroPadding()
{
   ...
   // For the simplicity, I assume that data size is smaller than 128. 
   // You need to change this part as needed.
   Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
   int dsize = srcBuff.length + 1; // 1 is for plain buffer size
   // This line align size to the multiple of block size.
   int newBufSize = ((dsize + cipher.getBlockSize() - 1) / cipher.getBlockSize()) * cipher.getBlockSize();
   byte[] newSrcBuf = new byte[newBufSize];
   // You need real buffer size, or you don't know how long is decrypted buffer.
   // I add it inside encrypting buffer to prevent other to see real decrypted buffer size.
   // But if you want to have exact same encrypted buffer on both sides, you must remove it.
   newSrcBuf[0] = (byte)(srcBuff.length);
   System.arraycopy(srcBuff, 0, newSrcBuf, 1, srcBuff.length);   
  // Now use newSrcBuf/newBufSize 
   ...
}

on the decryption side, check real size from decrypted buffer and use that size starting byte 1 for creating string.

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • Thanks for helpful information, encryption works but still i can not achieve same output in android. Angular side zero padding is used. how can i do same zero padding in android? – Mitul Varmora Nov 29 '19 at 13:14
  • To do so, you need to manually align your input buffer for encryption as multiple of block size. In addition you need to send your real size data too.I update and add it. – Afshin Nov 29 '19 at 13:26
  • 1. newSrcBuf[0] = srcBuff.length; (Error: required byte found int). 2. what is getBlockSize() ? – Mitul Varmora Nov 29 '19 at 13:53
  • @MitulVarmora oops, miss typing. in line it is another one. Just updating. – Afshin Nov 29 '19 at 13:56
  • What about this newSrcBuf[0] = srcBuff.length; (Error: required byte found int) – Mitul Varmora Nov 29 '19 at 14:09
  • @MitulVarmora just cast it. updated. Remember, you need to update this part of code as needed to keep bigger buffer sizes too. – Afshin Nov 29 '19 at 14:12
  • If i use PKCS7 or PKCS5 in both android and angular. then i am getting same output. but if i use CryptoJS.pad.ZeroPadding in angular and AES/CBC/NoPadding in android, then outputs are not same – Mitul Varmora Dec 03 '19 at 06:10
  • @MitulVarmora I forgot to update cipher algorithm to `AES/CBC/NoPadding` for zero padding and it is correct now. I tested and it is create same encrypted result with zero padding in both angular and android **if you don't add original buffer size**. But **you must add it or you will not know real buffer size after decryption**. I added size inside encrypting buffer to prevent anyone knowing real decrypted buffer size but this method create different encrypted buffer in android & angular. if you want that both buffer to be same, you need to remove this byte from encrypting, but it is bad idea. – Afshin Dec 03 '19 at 07:19
  • @MitulVarmora remove original plain buffer size only if you have another method for determining original plain buffer size. – Afshin Dec 03 '19 at 07:27