来自 Java 脚本的 Java 无法识别加密字节?

Encrypted bytes is not recognized by Java from Javascript?

我已经在 Java 脚本上完成了 AES CTR 实施。现在我打算将加密的字节共享给 Java 以供进一步处理。但是我无法将值 return 从 JavaScript's Uint8Array 分配给 Java's byte[].

下面是 Uint8Array 格式的样本数据(AES CTR 加密后)

[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]

当我在 byte[] 下的 Java 中传递从 JavaScript 收到的硬编码值时,如图所示,我收到了以下投诉。

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

我这样做是为了解决上述问题:

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};

下面是代码的一些实现

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`.

我做错了什么吗?例如,我在 Java.

中设置计数器 (IV) 的方式

仅供参考信息

问题是由于 java 字节数据类型是 8 位有符号二进制补码整数。它的最小值为 -128,最大值为 127(含),如官方 datatypes 教程中所述。这意味着像 193 这样的值是 int 而不是 byte,所以编译器抱怨在字节数组中找到 int 值,你不得不将它们转换为字节,结果不可预测。您可以考虑改为声明 int 数组或 short 数组或 char 数组而不是字节数组来解决你的问题

在 Java 代码中,IV 指定不正确。

CTR Mode 逐块增加传递的 IV。 new aesjs.Counter(16) 在 Java 脚本代码中将 IV 的起始值设置为 16。

因此,Java代码中对应的是:

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

通过此更改,可以使用 Java 代码进行解密。


请注意,如果重复使用 key/IV 对,CTR 的安全性就会失效。由于通常密钥是固定的,这意味着不能使用固定的 IV(但是在发布的代码中是这种情况),请参阅 here 了解更多详细信息。

此外,CTR 不提供任何消息验证。这可以用 MAC 更正,或者可以使用 GCM 模式。这是基于 CTR 模式并且 隐式地 应用 MAC.


有关从 JavaScript 到 Java 代码的二进制数据的必要调整的解释,请参阅

传输二进制数据的替代方法是使用 binary to text encoding 将数据转换为字符串并传输该字符串。通常为此应用 Base64 或十六进制编码,例如使用 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 
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>

在Java端,内置Base64.Decoder#decode()可用于Base64解码。