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

时间:2015-06-02 13:23:16

标签: java encryption cryptography bouncycastle

我们的团队正在使用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;
};

3 个答案:

答案 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)。 希望它有所帮助。