我们的团队正在使用javascript片段加密数据。我的问题是我应该用Java代码解析它们。我遇到了识别算法部分的麻烦。该代码表示它的CTR,但没有提供256键,IV或盐,因为它只需要一个简单的字符串并从那里开始。密钥看起来像"aasdg-safg-gwerg-wrgwrg"
javascript示例
var encr = Aes.Ctr.encrypt('big secret', 'aasdg-safg-gwerg-wrgwrg', 256);
然后在java端发送和接收该encr字符串以进行解密
String decr = ?????? // THIS IS WHAT I'M AFTER
提示:此网页为字符串https://www.pidder.de/pidcrypt/?page=demo_aes-ctr
正确执行算法脚本的copypaste可以在这里找到:http://www.movable-type.co.uk/scripts/aes.html
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2014 / MIT Licence */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Encrypt a text using AES encryption in Counter mode of operation.
*
* Unicode multi-byte character safe
*
* @param {string} plaintext - Source text to be encrypted.
* @param {string} password - The password to use to generate a key.
* @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
* @returns {string} Encrypted text.
*
* @example
* var encr = Aes.Ctr.encrypt('big secret', 'pāşšŵōřđ', 256); // encr: 'lwGl66VVwVObKIr6of8HVqJr'
*/
Aes.Ctr.encrypt = function(plaintext, password, nBits) {
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
plaintext = String(plaintext).utf8Encode();
password = String(password).utf8Encode();
// use AES itself to encrypt password to get cipher key (using plain password as source for key
// expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
var nBytes = nBits/8; // no bytes in key (16/24/32)
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) { // use 1st 16/24/32 chars of password for key
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes)); // gives us 16-byte key
key = key.concat(key.slice(0, nBytes-16)); // expand key to 16/24/32 bytes long
// initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec,
// [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
var counterBlock = new Array(blockSize);
var nonce = (new Date()).getTime(); // timestamp: milliseconds since 1-Jan-1970
var nonceMs = nonce%1000;
var nonceSec = Math.floor(nonce/1000);
var nonceRnd = Math.floor(Math.random()*0xffff);
// for debugging: nonce = nonceMs = nonceSec = nonceRnd = 0;
for (var i=0; i<2; i++) counterBlock[i] = (nonceMs >>> i*8) & 0xff;
for (var i=0; i<2; i++) counterBlock[i+2] = (nonceRnd >>> i*8) & 0xff;
for (var i=0; i<4; i++) counterBlock[i+4] = (nonceSec >>> i*8) & 0xff;
// and convert it to a string to go on the front of the ciphertext
var ctrTxt = '';
for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
var keySchedule = Aes.keyExpansion(key);
var blockCount = Math.ceil(plaintext.length/blockSize);
var ciphertxt = new Array(blockCount); // ciphertext as array of strings
for (var b=0; b<blockCount; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
// done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8);
var cipherCntr = Aes.cipher(counterBlock, keySchedule); // -- encrypt counter block --
// block size is reduced on final block
var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
var cipherChar = new Array(blockLength);
for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter char-by-char --
cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b*blockSize+i);
cipherChar[i] = String.fromCharCode(cipherChar[i]);
}
ciphertxt[b] = cipherChar.join('');
}
// use Array.join() for better performance than repeated string appends
var ciphertext = ctrTxt + ciphertxt.join('');
ciphertext = ciphertext.base64Encode();
return ciphertext;
};
/**
* Decrypt a text encrypted by AES in counter mode of operation
*
* @param {string} ciphertext - Source text to be encrypted.
* @param {string} password - Password to use to generate a key.
* @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
* @returns {string} Decrypted text
*
* @example
* var decr = Aes.Ctr.decrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // decr: 'big secret'
*/
Aes.Ctr.decrypt = function(ciphertext, password, nBits) {
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
ciphertext = String(ciphertext).base64Decode();
password = String(password).utf8Encode();
// use AES to encrypt password (mirroring encrypt routine)
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));
key = key.concat(key.slice(0, nBytes-16)); // expand key to 16/24/32 bytes long
// recover nonce from 1st 8 bytes of ciphertext
var counterBlock = new Array(8);
var ctrTxt = ciphertext.slice(0, 8);
for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
// generate key schedule
var keySchedule = Aes.keyExpansion(key);
// separate ciphertext into blocks (skipping past initial 8 bytes)
var nBlocks = Math.ceil((ciphertext.length-8) / blockSize);
var ct = new Array(nBlocks);
for (var b=0; b<nBlocks; b++) ct[b] = ciphertext.slice(8+b*blockSize, 8+b*blockSize+blockSize);
ciphertext = ct; // ciphertext is now array of block-length strings
// plaintext will get generated block-by-block into array of block-length strings
var plaintxt = new Array(ciphertext.length);
for (var b=0; b<nBlocks; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
for (var c=0; c<4; c++) counterBlock[15-c] = ((b) >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff;
var cipherCntr = Aes.cipher(counterBlock, keySchedule); // encrypt counter block
var plaintxtByte = new Array(ciphertext[b].length);
for (var i=0; i<ciphertext[b].length; i++) {
// -- xor plaintxt with ciphered counter byte-by-byte --
plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
}
plaintxt[b] = plaintxtByte.join('');
}
// join array of blocks into single plaintext string
var plaintext = plaintxt.join('');
plaintext = plaintext.utf8Decode(); // decode from UTF8 back to Unicode multi-byte chars
return plaintext;
};
答案 0 :(得分:0)
您是否阅读过Javascript中的评论?评论很好,评论是有原因的。
use AES itself to encrypt password to get cipher key
你能用Java编写代码吗?然后:
initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2)
你能用Java编写代码吗? CTR模式使用nonce而不是IV。
鉴于密钥和随机数,您应该能够编码AES-CTR解密。如果遇到问题,请检查每个阶段的输出是否与逐字节的Javascript输出匹配。如果您仍然无法找到问题,请在此处再次询问,向我们展示您的代码。
答案 1 :(得分:0)
我不知道Bouncy Castle实现的内部结构,但我曾经在Java之前编写这些东西以获得乐趣,并且您可以检查一些关键点以确定问题所在。不幸的是,这个问题可能不是简单的一行答案。
虽然算法的核心很可能是好的(这是测试向量告诉你的),我的猜测是你没有得到你想要的东西,因为密码处理,以及如何将其包装到关键字节非常草率。
通常情况下,密钥处理不明确,因为任何事情都会直到进行互操作性测试。测试向量不会为您提供任何帮助,因为用于设置测试的向量(键,IV输入)和结果表示为十六进制值。
在上面的实现中,使用UTF8对密码字节进行编码,然后将UTF8字节放入pwBytes中,直到所需的密钥长度,将它们截断为预期的最大值。如果你没有输入足够的字节,那么数组将保留默认值。
现在,我不是JavaScript专家,但对我来说,似乎还有很多机会。 UTF8编码IN HEX的确切结果是什么?如何在HEX中处理多字节字符?未初始化的pwBytes条目在HEX中的确切值是多少?
因此,了解问题的第一步:使用已知密钥转储pwBytes数组的十六进制字节。并将其与Bouncy Castle实现中的等效键字节进行比较。如果结果相同,那么你必须进一步研究,因为问题出在其他地方。
一件事:如果pidder实现工作正常,为什么不使用它?它的GPL,实施让我高兴得多。如果互操作性与pidder一起使用,则意味着某人已经为您解决了您的痛苦。
答案 2 :(得分:-1)
我在Java中使用AES加密的一些项目,BouncyCastle和SunJCE作为提供者。如果我错了,请任何人纠正我,但是没有接受passkey作为String的方法。但是,你可以这样做:
byte[] iv="someString".getBytes();
SecretKeySpec secretKey=new SecretKeySpec(yourPasswordAsString.getBytes(), "AES");
AlgorithmParameterSpec spec=new IvParameterSpec(iv);
Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] encrypted=cipher.doFinal(plainText.getBytes());
` 请记住,如果要将结果作为String获取,则需要将其转换为Base64,您可以使用ApacheCommonsCodec。你会有类似的东西:
String encryptedString=new String(new Base64().encode(encrypted);
还要记住,您需要将加密文本重新转换为非Base64。您可以这样做:byte[] notBase64=new Base64().decode(encryptedString)
还记得你的密钥需要是16(如果你安装了JCE或BouncyCastle,则为32 charachter)。
希望它有所帮助。