有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

带字符串键的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;
};

共 (3) 个答案

  1. # 1 楼答案

    我在Java中的AES加密项目中工作,BouncyCastle和SunJCE是提供程序。请任何人纠正我,如果我错了,但没有方法接受密钥作为字符串。但是,您可以这样做:

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

    ` 请记住,如果您想将结果转换为字符串,您需要将其转换为Base64,您可以使用ApacheCommonsCodec。你会有这样的感觉:

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

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

  2. # 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. # 3 楼答案

    我不知道Bouncy Castle实现的内部结构,但在Java出现之前,我经常编写这些代码,以获取乐趣,您可以查看一些关键点,以确定问题所在。不幸的是,这个问题可能没有一个简单的答案

    虽然该算法的核心很可能是OK(这是测试向量告诉您的),但我的猜测是,您没有得到您想要的,因为密码处理,以及它如何包装到密钥字节中是非常草率的

    通常情况下,密钥处理是不明确的,因为在您来进行互操作性测试之前,任何事情都可以。测试向量对此没有任何帮助,因为用于设置测试(键、IV输入)和结果的向量表示为十六进制值

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

    现在,我不是JavaScript专家,但对我来说,这似乎还有很多机会。UTF8十六进制编码的结果到底是什么?如何在十六进制中处理多字节字符?未初始化的pwBytes项的十六进制值到底是多少

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

    只有一件事:如果pidder实现工作正常,为什么不使用它呢?这是GPL,它的实现让我更高兴。如果pidder能够实现互操作性,那就意味着有人已经为你经历了痛苦