2

I've done the AES CTR implementation on Javascript. Now I'm intended to share the encrypted bytes to Java for further processing. However I'm unable to assign the value return from JavaScript's Uint8Array to Java's byte[].

Below's a sample data in Uint8Array format (After AES CTR encryption)

[71, 193, 223, 190, 6, 104, 11, 235, 249, 96, 54, 192, 233, 41, 198, 188, 15, 218, 10, 0, 61, 95, 58, 122, 74, 169, 27, 228, 121, 224, 128, 124, 198, 183, 23, 36, 89, 105, 184, 59, 245, 115, 244, 22, 122, 207, 217, 219, 160, 2, 227, 175, 134, 66, 165, 73, 102, 52, 14, 150, 182, 187, 228, 173, 96, 68, 11, 35, 166, 247, 45, 18, 202, 99, 81, 185, 216, 240, 66, 10, 105, 122, 45, 83]

When I pass try hardcoding value received from JavaScript in Java under byte[] as show, I received following complain.

byte[] resp = {71, 193, 223, 190, 6, 104, 11, 235, 249, 96, 54, 192, 233, 41, 198, 188, 15, 218, 10, 0, 61, 95, 58, 122, 74, 169, 27, 228, 121, 224, 128, 124, 198, 183, 23, 36, 89, 105, 184, 59, 245, 115, 244, 22, 122, 207, 217, 219, 160, 2, 227, 175, 134, 66, 165, 73, 102, 52, 14, 150, 182, 187, 228, 173, 96, 68, 11, 35, 166, 247, 45, 18, 202, 99, 81, 185, 216, 240, 66, 10, 105, 122, 45, 83};

// The complain is as follow:
Required type: byte
Provided: int

I did this for resolving to the above issue:

byte[] resp = {71, (byte)193, (byte)223, (byte)190, 6, 104, 11, (byte)235, (byte)249, 96, 54, (byte)192, (byte)233, 41, (byte)198, (byte)188, 15, (byte)218, 10, 0, 61, 95, 58, 122, 74, (byte)169, 27, (byte)228, 121, (byte)224, (byte)128, 124, (byte)198, (byte)183, 23, 36, 89, 105, (byte)184, 59, (byte)245, 115, (byte)244, 22, 122, (byte)207, (byte)217, (byte)219, (byte)160, 2, (byte)227, (byte)175, (byte)134, 66, (byte)165, 73, 102, 52, 14, (byte)150, (byte)182, (byte)187, (byte)228, (byte)173, 96, 68, 11, 35, (byte)166, (byte)247, 45, 18, (byte)202, 99, 81, (byte)185, (byte)216, (byte)240, 66, 10, 105, 122, 45, 83};

Below is to show some implemented of the code

JS

    var key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    var textBytes = aesjs.utils.utf8.toBytes("textToEncrypt");
    var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(16));
    var encryptedBytes = aesCtr.encrypt(textBytes);

# Outcome of encryptedBytes
[71, 193, 223, 190, 6, 104, 11, 235, 249, 96, 54, 192, 233, 41, 198, 188, 15, 218, 10, 0, 61, 95, 58, 122, 74, 169, 27, 228, 121, 224, 128, 124, 198, 183, 23, 36, 89, 105, 184, 59, 245, 115, 244, 22, 122, 207, 217, 219, 160, 2, 227, 175, 134, 66, 165, 73, 102, 52, 14, 150, 182, 187, 228, 173, 96, 68, 11, 35, 166, 247, 45, 18, 202, 99, 81, 185, 216, 240, 66, 10, 105, 122, 45, 83]

Java

byte[] keyBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte[] ivBytes = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};

# Here I assign the value I've received from `Javascript` into `resp`

byte[] resp = {71, (byte)193, (byte)223, (byte)190, 6, 104, 11, (byte)235, (byte)249, 96, 54, (byte)192, (byte)233, 41, (byte)198, (byte)188, 15, (byte)218, 10, 0, 61, 95, 58, 122, 74, (byte)169, 27, (byte)228, 121, (byte)224, (byte)128, 124, (byte)198, (byte)183, 23, 36, 89, 105, (byte)184, 59, (byte)245, 115, (byte)244, 22, 122, (byte)207, (byte)217, (byte)219, (byte)160, 2, (byte)227, (byte)175, (byte)134, 66, (byte)165, 73, 102, 52, 14, (byte)150, (byte)182, (byte)187, (byte)228, (byte)173, 96, 68, 11, 35, (byte)166, (byte)247, 45, 18, (byte)202, 99, 81, (byte)185, (byte)216, (byte)240, 66, 10, 105, 122, 45, 83};

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] plaintext = cipher.doFinal(decoded);
String plaintextString = new String(plaintext, StandardCharsets.UTF_8); 

# Here I expect the value `textToEncrypt` to be decrypted under variable `plaintextString`.

Am I doing something wrong? Eg, the way Im setting the counter(IV) in Java.

FYI info

  • cipher.getAlgorithm() is SunJCE version 15
Tommy Leong
  • 2,509
  • 6
  • 30
  • 54
  • Try `new Int8Array(`…`)` around the resulting `Uint8Array`. Reminds me of [JavaScript equivalent of Java's String.getBytes(StandardCharsets.UTF\_8)](/q/69710627/4642212). – Sebastian Simon Nov 02 '21 at 17:37
  • The CTR mode doesn't use padding, i.e. the ciphertext should have the same length as the plaintext, so for `textToEncrypt` 13 bytes. Your ciphertext is much longer, so the data is inconsistent (maybe `textToEncrypt` is a dummy, then please share the plaintext belonging to the posted ciphertext). – Topaco Nov 02 '21 at 18:34
  • Also, the IV applied in the Java code is incorrect. The IV corresponding to `new aesjs.Counter(16)` to be used in the Java code is `{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16}`. If I apply this IV, I can successfully decrypt the ciphertext from the JavaScript with the Java code. – Topaco Nov 02 '21 at 18:36
  • @SebastianSimon, thanks for sharing the link. It will be helpful for me later when im trying to do the same in Javascript. To handle this in Java, what would you suggest me to process them in `byte[]`? As you may have notice, some value generated from Javascript is consider as `Int`. I can't accept this parameter directly as a `byte[]`, Java will change the value itself. – Tommy Leong Nov 03 '21 at 04:43
  • @Topaco Sharp eye there. Yes, I was giving a dummy data as a sample. A 90' degree bow to you Sir, you have corrected my IV counter as you pointed `{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16}`. I wasn't too sure how this could be done earlier. If you could post this as an answer, I would like to accept this :) Thanks! – Tommy Leong Nov 03 '21 at 04:44
  • You' re welcome, I' ve posted an answer. – Topaco Nov 03 '21 at 09:20
  • @TommyLeong I’m not a Java dev, so I can’t answer your question, sorry. – Sebastian Simon Nov 03 '21 at 13:20

2 Answers2

1

In the Java code the IV is specified incorrectly.

The CTR Mode increments the passed IV from block to block. new aesjs.Counter(16) in the JavaScript code sets the starting value of the IV to 16.

Therefore, the counterpart in the Java code is:

byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16};

With this change, decryption with the Java code works.


Note that the security of CTR breaks down if key/IV pairs are used repeatedly. Since usually the key is fixed, this means that no fixed IV may be used (which however is the case in the posted code), see here for more details.

Also, CTR does not provide any authentication of the message. This can be corrected with a MAC, alternatively the GCM mode can be used. This is based on the CTR mode and implicitly applies a MAC.


For an explanation of the necessary adaptation of the binary data regarding the transfer from JavaScript to Java code, see the other answer.

An alternative to the transfer of binary data would be to convert the data into a string using a binary to text encoding and to transfer this string. Typically Base64 or hex encoding is applied for this, e.g. with Base64:

var key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var textBytes = aesjs.utils.utf8.toBytes("The quick brown fox jumps over the lazy dog");
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(16));
var encryptedBytes = aesCtr.encrypt(textBytes);

document.getElementById("bin").innerHTML = encryptedBytes; // 12,218,38,59,177,203,183,97,62,47,34,81,230,30,130,88,98,127,198,220,167,147,249,59,26,253,111,11,142,145,186,233,212,59,4,153,120,222,196,212,28,222,190
document.getElementById("b64").innerHTML = ui8ToB64(encryptedBytes); // DNomO7HLt2E+LyJR5h6CWGJ/xtynk/k7Gv1vC46RuunUOwSZeN7E1Bzevg==    

// from https://stackoverflow.com/a/11562550/9014097
function ui8ToB64( arr ) {
    return btoa(String.fromCharCode.apply(null, arr));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/aes-js/3.1.2/index.min.js"></script>
<p style="font-family:'Courier New', monospace;" id="bin"></p>
<p style="font-family:'Courier New', monospace;" id="b64"></p>

On the Java side, the built-in Base64.Decoder#decode() can be used for the Base64 decoding.

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

The problem is due to the fact in java the byte data type is an 8-bit signed two's complement integer. It has a minimum value of -128 and a maximum value of 127 (inclusive), as stated for example in the official datatypes tutorial. This implies that values like 193 are int and not byte, so the compiler complains about to find int values inside a byte array and you are obliged to cast them to byte with unpredictable results. You can consider to instead declare an int array or a short array or a char array instead of a byte array to solve your problem

dariosicily
  • 4,239
  • 2
  • 11
  • 17