带字符串键的BouncyCastle中的java AESCTR,不含IV或盐
我们的团队正在使用javascript片段加密数据。我的问题是我应该在Java代码中解析它们。我在识别算法的某些部分时遇到困难。代码说它是CTR,但没有提供256键、IV键或salt键,因为它只需要一个简单的字符串就可以了。键看起来像"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的算法
可以在此处找到脚本的复制粘贴: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;
};
# 1 楼答案
我在Java中的AES加密项目中工作,BouncyCastle和SunJCE是提供程序。请任何人纠正我,如果我错了,但没有方法接受密钥作为字符串。但是,您可以这样做:
` 请记住,如果您想将结果转换为字符串,您需要将其转换为Base64,您可以使用ApacheCommonsCodec。你会有这样的感觉:
还请记住,您需要将加密文本重新转换为非Base64。你可以这样做:
byte[] notBase64=new Base64().decode(encryptedString)
还要记住,您的密钥必须是16个字符(如果您安装了JCE或BouncyCastle,则必须是32个字符)。 希望有帮助# 2 楼答案
你看过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
给定密钥和nonce,您应该能够对AES-CTR解密进行编码。如果遇到问题,请检查每个阶段的输出是否与Javascript输出逐字节匹配。如果仍然找不到问题,请在此处再次询问,并向我们展示您的代码
# 3 楼答案
我不知道Bouncy Castle实现的内部结构,但在Java出现之前,我经常编写这些代码,以获取乐趣,您可以查看一些关键点,以确定问题所在。不幸的是,这个问题可能没有一个简单的答案
虽然该算法的核心很可能是OK(这是测试向量告诉您的),但我的猜测是,您没有得到您想要的,因为密码处理,以及它如何包装到密钥字节中是非常草率的
通常情况下,密钥处理是不明确的,因为在您来进行互操作性测试之前,任何事情都可以。测试向量对此没有任何帮助,因为用于设置测试(键、IV输入)和结果的向量表示为十六进制值
在上面的实现中,使用UTF8对密码字节进行编码,然后将UTF8字节放入pwBytes中,直到达到所需的密钥长度,并将其截断到预期的最大值。如果没有输入足够的字节,那么数组将保留默认值
现在,我不是JavaScript专家,但对我来说,这似乎还有很多机会。UTF8十六进制编码的结果到底是什么?如何在十六进制中处理多字节字符?未初始化的pwBytes项的十六进制值到底是多少
所以,理解问题的第一步是:用已知密钥转储pwBytes数组的十六进制字节。并将其与Bouncy Castle实现中的等效密钥字节进行比较。如果结果是一样的,你将不得不进一步研究,因为问题出在其他地方
只有一件事:如果pidder实现工作正常,为什么不使用它呢?这是GPL,它的实现让我更高兴。如果pidder能够实现互操作性,那就意味着有人已经为你经历了痛苦