BouncyCastle中的AES-CTR带有字符串键,没有IV或盐

时间:2022-07-12 18:25:36

Our team is encrypting data using a javascript snippet. My problem is I'm supposed to parse them back in my Java code. I am having troubles identifying parts of the algorithm. The code says it's CTR but doesn't provide a 256 key, an IV or a salt, as it only takes a simple string and goes from there. A key looks like "aasdg-safg-gwerg-wrgwrg"

我们的团队正在使用javascript片段加密数据。我的问题是我应该在我的Java代码中解析它们。我遇到了识别算法部分的麻烦。代码表示它是CTR,但不提供256键,IV或盐,因为它只需要一个简单的字符串并从那里开始。一键看起来像“aasdg-safg-gwerg-wrgwrg”

javascript example

javascript示例

var encr = Aes.Ctr.encrypt('big secret', 'aasdg-safg-gwerg-wrgwrg', 256);

then that encr string is sent and received on the java side to be decrypted

然后在java端发送和接收该encr字符串以进行解密

String decr = ?????? // THIS IS WHAT I'M AFTER

HINT: This webpage does the algorithm correctly for strings https://www.pidder.de/pidcrypt/?page=demo_aes-ctr

提示:此网页为字符串正确执行算法https://www.pidder.de/pidcrypt/?page=demo_aes-ctr

A copypaste of the script can found here: http://www.movable-type.co.uk/scripts/aes.html

脚本的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;
};

3 个解决方案

#1


0  

Have you read the comments in the Javascript? It is well commented, and the comments are there for a reason.

你有没有在Javascript中阅读评论?评论很好,评论是有原因的。

use AES itself to encrypt password to get cipher key

使用AES本身加密密码以获取密钥

Can you code that in Java? Then:

你能用Java编写代码吗?然后:

initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2)

用nonce初始化第8个字节的计数器块(NISTSP800-38A§B.2)

Can you code that in Java? CTR mode uses a nonce instead of an IV.

你能用Java编写代码吗? CTR模式使用nonce而不是IV。

Given the key and the nonce you should be able to code AES-CTR decryption. If you hit problems, then check that your output at every stage matches the Javascript output byte-for-byte. If you still can't find the problem, then ask again here, showing us your code.

鉴于密钥和随机数,您应该能够编码AES-CTR解密。如果遇到问题,请检查每个阶段的输出是否与逐字节的Javascript输出匹配。如果您仍然无法找到问题,请在此处再次询问,向我们展示您的代码。

#2


0  

I'm not aware of the internals of the Bouncy Castle implementation, but I used to code these things for fun before the days of Java, and there are some key points you can check to pinpoint where the problem is. Unfortunately, there probably isn't a simple one line answer to the question.

我不知道Bouncy Castle实现的内部结构,但我曾经在Java之前编写这些东西以获得乐趣,并且您可以检查一些关键点以确定问题所在。不幸的是,这个问题可能没有简单的一行答案。

While the core of the algorithm is more than likely OK (this is what the test vectors tell you), my guess is that you are not getting what you want because the password handling, and how it is wrapped into the key bytes is quite sloppy.

虽然算法的核心很可能是好的(这是测试向量告诉你的),我的猜测是你没有得到你想要的东西,因为密码处理,以及如何将它包装到关键字节中是相当草率的。

It is often the case that key handling is unclear, because anything will do until you come to do interoperability testing. The test vectors don't give you any help with this, because the vectors used to set up the test (key, IV input) and the results are expressed as hex values.

通常情况下,密钥处理不清楚,因为在您进行互操作性测试之前,任何事情都会发生。测试向量不会给你任何帮助,因为用于设置测试的向量(键,IV输入)和结果表示为十六进制值。

In the implementation above, the password bytes are encoded using UTF8, then the UTF8 bytes are put into the pwBytes up to the required key length, truncating them to the maximum expected. If you don't put in enough bytes, then the array is left with the default value.

在上面的实现中,使用UTF8对密码字节进行编码,然后将UTF8字节放入pwBytes中,直到所需的密钥长度,将它们截断为预期的最大值。如果没有输入足够的字节,则数组将保留默认值。

Now, I'm no JavaScript expert, but to me there seems a lot left to chance in that. What exactly is the result of the UTF8 encoding IN HEX? How are multibyte characters handled IN HEX? What exactly is the value of the uninitialised pwBytes entries IN HEX?

现在,我不是JavaScript专家,但对我而言似乎还有很多机会。 UTF8编码IN HEX的确切结果是什么?如何在HEX中处理多字节字符?未初始化的pwBytes条目在HEX中的确切值是多少?

So, first step in understanding your problem: dump out the hex bytes of the pwBytes array with a known key. And compare this to the equivalent key bytes in the Bouncy Castle implementation. If this turns out the same, they you'll have to look further because the problem is somewhere else.

因此,理解您的问题的第一步:使用已知密钥转储pwBytes数组的十六进制字节。并将其与Bouncy Castle实现中的等效键字节进行比较。如果结果相同,那么你将不得不进一步研究,因为问题出在其他地方。

Just one thing: If the pidder implementation is working OK, why not use that? It's GPL, and the implementation pleases me much more. If interoperability works with pidder, it means that someone has gone through your pain for you already.

只有一件事:如果pidder实现工作正常,为什么不使用它?这是GPL,实施让我高兴。如果互操作性与pidder一起使用,则意味着某人已经为您完成了痛苦。

#3


-1  

I work for some project on AES Encryption in Java, with BouncyCastle and SunJCE as providers. Please anyone correct me if i'm wrong, but there is no method that accept passkey as String. However, you could do this:

我在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());

` Remeber that if you want to get the result as String you need to convert it as Base64, you can use ApacheCommonsCodec. You will have something like:

`记住,如果你想将结果作为String获得,你需要将其转换为Base64,你可以使用ApacheCommonsCodec。你会有类似的东西:

String encryptedString=new String(new Base64().encode(encrypted);

Remember also that you need to re-convert the encrypted text as non-Base64. You can do so: byte[] notBase64=new Base64().decode(encryptedString) Remember also taht yoour passkey need to be 16 (or 32 charachter if you have JCE installed or BouncyCastle). Hope it helps.

还要记住,您需要将加密文本重新转换为非Base64。您可以这样做:byte [] notBase64 = new Base64()。decode(encryptedString)还要记住,如果你安装了JCE或者BouncyCastle,你的密钥需要是16(或者32 charachter)。希望能帮助到你。

#1


0  

Have you read the comments in the Javascript? It is well commented, and the comments are there for a reason.

你有没有在Javascript中阅读评论?评论很好,评论是有原因的。

use AES itself to encrypt password to get cipher key

使用AES本身加密密码以获取密钥

Can you code that in Java? Then:

你能用Java编写代码吗?然后:

initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2)

用nonce初始化第8个字节的计数器块(NISTSP800-38A§B.2)

Can you code that in Java? CTR mode uses a nonce instead of an IV.

你能用Java编写代码吗? CTR模式使用nonce而不是IV。

Given the key and the nonce you should be able to code AES-CTR decryption. If you hit problems, then check that your output at every stage matches the Javascript output byte-for-byte. If you still can't find the problem, then ask again here, showing us your code.

鉴于密钥和随机数,您应该能够编码AES-CTR解密。如果遇到问题,请检查每个阶段的输出是否与逐字节的Javascript输出匹配。如果您仍然无法找到问题,请在此处再次询问,向我们展示您的代码。

#2


0  

I'm not aware of the internals of the Bouncy Castle implementation, but I used to code these things for fun before the days of Java, and there are some key points you can check to pinpoint where the problem is. Unfortunately, there probably isn't a simple one line answer to the question.

我不知道Bouncy Castle实现的内部结构,但我曾经在Java之前编写这些东西以获得乐趣,并且您可以检查一些关键点以确定问题所在。不幸的是,这个问题可能没有简单的一行答案。

While the core of the algorithm is more than likely OK (this is what the test vectors tell you), my guess is that you are not getting what you want because the password handling, and how it is wrapped into the key bytes is quite sloppy.

虽然算法的核心很可能是好的(这是测试向量告诉你的),我的猜测是你没有得到你想要的东西,因为密码处理,以及如何将它包装到关键字节中是相当草率的。

It is often the case that key handling is unclear, because anything will do until you come to do interoperability testing. The test vectors don't give you any help with this, because the vectors used to set up the test (key, IV input) and the results are expressed as hex values.

通常情况下,密钥处理不清楚,因为在您进行互操作性测试之前,任何事情都会发生。测试向量不会给你任何帮助,因为用于设置测试的向量(键,IV输入)和结果表示为十六进制值。

In the implementation above, the password bytes are encoded using UTF8, then the UTF8 bytes are put into the pwBytes up to the required key length, truncating them to the maximum expected. If you don't put in enough bytes, then the array is left with the default value.

在上面的实现中,使用UTF8对密码字节进行编码,然后将UTF8字节放入pwBytes中,直到所需的密钥长度,将它们截断为预期的最大值。如果没有输入足够的字节,则数组将保留默认值。

Now, I'm no JavaScript expert, but to me there seems a lot left to chance in that. What exactly is the result of the UTF8 encoding IN HEX? How are multibyte characters handled IN HEX? What exactly is the value of the uninitialised pwBytes entries IN HEX?

现在,我不是JavaScript专家,但对我而言似乎还有很多机会。 UTF8编码IN HEX的确切结果是什么?如何在HEX中处理多字节字符?未初始化的pwBytes条目在HEX中的确切值是多少?

So, first step in understanding your problem: dump out the hex bytes of the pwBytes array with a known key. And compare this to the equivalent key bytes in the Bouncy Castle implementation. If this turns out the same, they you'll have to look further because the problem is somewhere else.

因此,理解您的问题的第一步:使用已知密钥转储pwBytes数组的十六进制字节。并将其与Bouncy Castle实现中的等效键字节进行比较。如果结果相同,那么你将不得不进一步研究,因为问题出在其他地方。

Just one thing: If the pidder implementation is working OK, why not use that? It's GPL, and the implementation pleases me much more. If interoperability works with pidder, it means that someone has gone through your pain for you already.

只有一件事:如果pidder实现工作正常,为什么不使用它?这是GPL,实施让我高兴。如果互操作性与pidder一起使用,则意味着某人已经为您完成了痛苦。

#3


-1  

I work for some project on AES Encryption in Java, with BouncyCastle and SunJCE as providers. Please anyone correct me if i'm wrong, but there is no method that accept passkey as String. However, you could do this:

我在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());

` Remeber that if you want to get the result as String you need to convert it as Base64, you can use ApacheCommonsCodec. You will have something like:

`记住,如果你想将结果作为String获得,你需要将其转换为Base64,你可以使用ApacheCommonsCodec。你会有类似的东西:

String encryptedString=new String(new Base64().encode(encrypted);

Remember also that you need to re-convert the encrypted text as non-Base64. You can do so: byte[] notBase64=new Base64().decode(encryptedString) Remember also taht yoour passkey need to be 16 (or 32 charachter if you have JCE installed or BouncyCastle). Hope it helps.

还要记住,您需要将加密文本重新转换为非Base64。您可以这样做:byte [] notBase64 = new Base64()。decode(encryptedString)还要记住,如果你安装了JCE或者BouncyCastle,你的密钥需要是16(或者32 charachter)。希望能帮助到你。