这个AES JavaScript函数中的IV是什么?

时间:2022-10-08 18:23:38

I'm trying to use a JavaScript library to encrypt data and send that to a Java based server where that data can be decrypted.

我正在尝试使用一个JavaScript库来加密数据,并将其发送到基于Java的服务器,在那里数据可以被解密。

The problem I'm having, is in looking at the JavaScript code, I only see evidence of a 8 byte IV, even though Java wants a 16 byte IV :-P.

我遇到的问题是,在查看JavaScript代码时,我只看到了8字节的证据,尽管Java想要一个16字节的IV:-P。

Is it possible to have Java decode what is sent from the JavaScript, or modify the JavaScript so it can be? This is definitely waaaay above my crypto abilities :-P.

是否有可能让Java解码来自JavaScript的东西,或者修改JavaScript ?这绝对是我的秘密能力:-P。

The script is available here: enter link description here

这里可以使用脚本:在这里输入链接描述。

My slightly modified version is below:

我稍微修改过的版本如下:

The current version of the Java code where I've been playing around with different things is here:

当前版本的Java代码我在这里玩的是不同的东西:

package com.myclass.util;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidParameterSpecException;
import java.util.regex.Pattern;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

   public class AES {
       private static Charset PLAIN_TEXT_ENCODING = Charset.forName("UTF-8");
       private static String CIPHER_TRANSFORMATION = "AES/CTR/NoPadding";
       private static String KEY_TYPE = "AES";
       private static int KEY_SIZE_BITS = 128;

       private SecretKey key;
       private Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
       private byte[] ivBytes = new byte[KEY_SIZE_BITS/8];

       public AES() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidParameterSpecException, InvalidKeyException, InvalidAlgorithmParameterException{
           KeyGenerator kgen = KeyGenerator.getInstance(KEY_TYPE);
           kgen.init(KEY_SIZE_BITS); 
           key = kgen.generateKey();
           cipher.init(Cipher.ENCRYPT_MODE, key);
           ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
       }

       public String getIVAsHex(){
           return byteArrayToHexString(ivBytes);
       }

       public String getKeyAsHex(){
           return byteArrayToHexString(key.getEncoded());
       }

       public void setStringToKey(String keyText) throws NoSuchAlgorithmException, UnsupportedEncodingException{
           setKey(getHash(keyText));
       }

       public void setHexToKey(String hexKey){
           setKey(hexStringToByteArray(hexKey));
       }

       private void setKey(byte[] bArray){
           byte[] bText = new byte[KEY_SIZE_BITS/8];
           int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
           System.arraycopy(bArray, 0, bText, 0, end);
           key = new SecretKeySpec(bText, KEY_TYPE);
       }

       public void setStringToIV(String ivText){
           setIV(ivText.getBytes());
       }

       public void setHexToIV(String hexIV){
           setIV(hexStringToByteArray(hexIV));
       }

       private void setIV(byte[] bArray){
           byte[] bText = new byte[KEY_SIZE_BITS/8];
           int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
           System.arraycopy(bArray, 0, bText, 0, end);
           ivBytes = bText;
       }

        public String encrypt(String message) throws InvalidKeyException,
                IllegalBlockSizeException, BadPaddingException,
                InvalidAlgorithmParameterException {
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] encrypted = cipher.doFinal(message.getBytes(PLAIN_TEXT_ENCODING));
            String result = byteArrayToHexString(ivBytes).concat(byteArrayToHexString(encrypted).substring(2));
            return result;
        }

        public String decrypt(String hexCiphertext)
                throws IllegalBlockSizeException, BadPaddingException,
                InvalidKeyException, InvalidAlgorithmParameterException,
                UnsupportedEncodingException {
            byte[] dec = hexStringToByteArray(hexCiphertext);
            byte[] iv = new byte[16];
            System.arraycopy(dec, 0, iv, 0, 16);
            byte[] cText = new byte[dec.length - 16];
            System.arraycopy(dec, 16, cText, 0, cText.length);
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));

            byte[] decrypted = cipher.doFinal(cText);
            return new String(decrypted, PLAIN_TEXT_ENCODING);
        }

        private static String byteArrayToHexString(byte[] raw) {
            StringBuilder sb = new StringBuilder(2 + raw.length * 2);
            sb.append("0x");
            for (int i = 0; i < raw.length; i++) {
                sb.append(String.format("%02X", Integer.valueOf(raw[i] & 0xFF)));
            }
            return sb.toString();
        }

       private static byte[] hexStringToByteArray(String hex) {
            Pattern replace = Pattern.compile("^0x");
            String s = replace.matcher(hex).replaceAll("");

            byte[] b = new byte[s.length() / 2];
            for (int i = 0; i < b.length; i++){
              int index = i * 2;
              int v = Integer.parseInt(s.substring(index, index + 2), 16);
              b[i] = (byte)v;
            }
            return b;
       }
       private byte[] getHash(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
           MessageDigest digest = MessageDigest.getInstance("SHA-256");
           digest.reset();
           return digest.digest(password.getBytes("UTF-8"));
        }

       public String getHashasHex(String password) throws UnsupportedEncodingException, NoSuchAlgorithmException{
           return byteArrayToHexString(getHash(password));
       }

   }


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  AES implementation in JavaScript (c) Chris Veness 2005-2011                                   */
/*   - see http://csrc.nist.gov/publications/PubsFIPS.html#197                                    */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Aes = {};  // Aes namespace

/**
 * AES Cipher function: encrypt 'input' state with Rijndael algorithm
 *   applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
 *
 * @param {Number[]} input 16-byte (128-bit) input state array
 * @param {Number[][]} w   Key schedule as 2D byte-array (Nr+1 x Nb bytes)
 * @returns {Number[]}     Encrypted output state array
 */
Aes.cipher = function(input, w) {    // main Cipher function [§5.1]
  var Nb = 4;               // block size (in words): no of columns in state (fixed at 4 for AES)
  var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

  var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];

  state = Aes.addRoundKey(state, w, 0, Nb);

  for (var round=1; round<Nr; round++) {
    state = Aes.subBytes(state, Nb);
    state = Aes.shiftRows(state, Nb);
    state = Aes.mixColumns(state, Nb);
    state = Aes.addRoundKey(state, w, round, Nb);
  }

  state = Aes.subBytes(state, Nb);
  state = Aes.shiftRows(state, Nb);
  state = Aes.addRoundKey(state, w, Nr, Nb);

  var output = new Array(4*Nb);  // convert state to 1-d array before returning [§3.4]
  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
  return output;
}

/**
 * Perform Key Expansion to generate a Key Schedule
 *
 * @param {Number[]} key Key as 16/24/32-byte array
 * @returns {Number[][]} Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes)
 */
Aes.keyExpansion = function(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
  var Nb = 4;            // block size (in words): no of columns in state (fixed at 4 for AES)
  var Nk = key.length/4  // key length (in words): 4/6/8 for 128/192/256-bit keys
  var Nr = Nk + 6;       // no of rounds: 10/12/14 for 128/192/256-bit keys

  var w = new Array(Nb*(Nr+1));
  var temp = new Array(4);

  for (var i=0; i<Nk; i++) {
    var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
    w[i] = r;
  }

  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
    w[i] = new Array(4);
    for (var t=0; t<4; t++) temp[t] = w[i-1][t];
    if (i % Nk == 0) {
      temp = Aes.subWord(Aes.rotWord(temp));
      for (var t=0; t<4; t++) temp[t] ^= Aes.rCon[i/Nk][t];
    } else if (Nk > 6 && i%Nk == 4) {
      temp = Aes.subWord(temp);
    }
    for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
  }

  return w;
}

/*
 * ---- remaining routines are private, not called externally ----
 */

Aes.subBytes = function(s, Nb) {    // apply SBox to state S [§5.1.1]
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) s[r][c] = Aes.sBox[s[r][c]];
  }
  return s;
}

Aes.shiftRows = function(s, Nb) {    // shift row r of state S left by r bytes [§5.1.2]
  var t = new Array(4);
  for (var r=1; r<4; r++) {
    for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
    for (var c=0; c<4; c++) s[r][c] = t[c];         // and copy back
  }          // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
  return s;  // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
}

Aes.mixColumns = function(s, Nb) {   // combine bytes of each col of state S [§5.1.3]
  for (var c=0; c<4; c++) {
    var a = new Array(4);  // 'a' is a copy of the current column from 's'
    var b = new Array(4);  // 'b' is a•{02} in GF(2^8)
    for (var i=0; i<4; i++) {
      a[i] = s[i][c];
      b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;

    }
    // a[n] ^ b[n] is a•{03} in GF(2^8)
    s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
    s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
    s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
    s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
  }
  return s;
}

Aes.addRoundKey = function(state, w, rnd, Nb) {  // xor Round Key into state S [§5.1.4]
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
  }
  return state;
}

Aes.subWord = function(w) {    // apply SBox to 4-byte word w
  for (var i=0; i<4; i++) w[i] = Aes.sBox[w[i]];
  return w;
}

Aes.rotWord = function(w) {    // rotate 4-byte word w left by one byte
  var tmp = w[0];
  for (var i=0; i<3; i++) w[i] = w[i+1];
  w[3] = tmp;
  return w;
}

// sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1]
Aes.sBox =  [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
             0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
             0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
             0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
             0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
             0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
             0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
             0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
             0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
             0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
             0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
             0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
             0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
             0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
             0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
             0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];

// rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
Aes.rCon = [ [0x00, 0x00, 0x00, 0x00],
             [0x01, 0x00, 0x00, 0x00],
             [0x02, 0x00, 0x00, 0x00],
             [0x04, 0x00, 0x00, 0x00],
             [0x08, 0x00, 0x00, 0x00],
             [0x10, 0x00, 0x00, 0x00],
             [0x20, 0x00, 0x00, 0x00],
             [0x40, 0x00, 0x00, 0x00],
             [0x80, 0x00, 0x00, 0x00],
             [0x1b, 0x00, 0x00, 0x00],
             [0x36, 0x00, 0x00, 0x00] ]; 


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2011                      */
/*   - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf                       */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

Aes.Ctr = {};  // Aes.Ctr namespace: a subclass or extension of Aes

/** 
 * 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, or 256)
 * @returns {string}         Encrypted text
 */
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 = Utf8.encode(plaintext);
  password = Utf8.encode(password);
  //var t = new Date();  // timer

  var nBytes = nBits/8;  // no bytes in key (16/24/32)
  var hash = Sha256.hash(password);

  for (var i=0; i<nBytes; i++) {  // use 1st 16/24/32 chars of hash for key
    key[i] = isNaN(password.charCodeAt(i)) ? 0 : hash.charCodeAt(i);
  }

  // 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 (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(''); 
  }

  // Array.join is more efficient than repeated string concatenation in IE
  var ciphertext = ctrTxt + ciphertxt.join('');
  ciphertext = stringToHex(ciphertext);  // encode in base64

  //alert((new Date()) - t);
  return ciphertext;
}

/** 
 * Decrypt a text encrypted by AES in counter mode of operation
 *
 * @param {String} ciphertext 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, or 256)
 * @returns {String}          Decrypted text
 */
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 = hexToString(ciphertext);
  password = Utf8.encode(password);
  //var t = new Date();  // timer

  // use SHA256 to hash password (mirroring encrypt routine)
  var nBytes = nBits/8;  // no bytes in key
  var hash = Sha256.hash(password);

  for (var i=0; i<nBytes; i++) {  // use 1st 16/24/32 chars of hash for key
    key[i] = isNaN(password.charCodeAt(i)) ? 0 : hash.charCodeAt(i);
  }

  // recover nonce from 1st 8 bytes of ciphertext
  var counterBlock = new Array(8);
  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 = Utf8.decode(plaintext);  // decode from UTF8 back to Unicode multi-byte chars

  //alert((new Date()) - t);
  return plaintext;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
/*              single-byte character encoding (c) Chris Veness 2002-2011                         */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Utf8 = {};  // Utf8 namespace

/**
 * Encode multi-byte Unicode string into utf-8 multiple single-byte characters 
 * (BMP / basic multilingual plane only)
 *
 * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
 *
 * @param {String} strUni Unicode string to be encoded as UTF-8
 * @returns {String} encoded string
 */
Utf8.encode = function(strUni) {
  // use regular expressions & String.replace callback function for better efficiency 
  // than procedural approaches
  var strUtf = strUni.replace(
      /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
      function(c) { 
        var cc = c.charCodeAt(0);
        return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
    );
  strUtf = strUtf.replace(
      /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
      function(c) { 
        var cc = c.charCodeAt(0); 
        return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
    );
  return strUtf;
}

/**
 * Decode utf-8 encoded string back into multi-byte Unicode characters
 *
 * @param {String} strUtf UTF-8 string to be decoded back to Unicode
 * @returns {String} decoded string
 */
Utf8.decode = function(strUtf) {
  // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
  var strUni = strUtf.replace(
      /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f); 
        return String.fromCharCode(cc); }
    );
  strUni = strUni.replace(
      /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
        return String.fromCharCode(cc); }
    );
  return strUni;
}

function stringToHex (s) {
  var r = "0x";
  var hexes = new Array ("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
  for (var i=0; i<s.length; i++) {r += hexes [s.charCodeAt(i) >> 4] + hexes [s.charCodeAt(i) & 0xf];}
  return r;
}

function hexToString (h) {
  var r = "";
  for (var i= (h.substr(0, 2)=="0x")?2:0; i<h.length; i+=2) {r += String.fromCharCode (parseInt (h.substr (i, 2), 16));}
  return r;
}

Thank you for your help!

谢谢你的帮助!

2 个解决方案

#1


2  

Technically yes they can read what the other is writing. However, the Javascript code says the InputVector (IV) is 16 bytes:

从技术上讲,他们能读懂对方在写什么。然而,Javascript代码表示InputVector (IV)是16字节:

* @param {Number[]} input 16-byte (128-bit) input state array

And, Java has 128/8 = 16 IV byte array in it. So your original question seems like isn't an issue since both use 16 byte IVs.

Java有128/8 = 16 IV字节数组。所以你最初的问题似乎不是一个问题,因为它们都使用16字节的IVs。

Now for all of the issues I see with what you're doing. Encrypting on the client (web browser) is NOT secure. You can't make it secure. AES is a symmetric encryption algorithm and requires the same secret key at both ends. Which means you have to share that secret between the client and server. That basically means that secret key has to be visible so the Javascript code can get a key that's agreeable with the server. You can even transfer it over SSL or whatever, but no matter what anyone with a browser can grab that secret key. And not to mention its tremendously easy to fire up firebug and grab whatever I want out of your Javascript program. Bottom line is encrypting the data using Javascript is a pointless exercise. If you really need to transfer secrets on the client to the server use SSL. It's designed to be secure, it's battle tested, there are and have been issues with it. However, they have and will be fixed.

现在,对于我看到的所有问题和你正在做的事情。对客户端(web浏览器)进行加密是不安全的。你不能让它安全。AES是一种对称加密算法,在两端都需要相同的密钥。这意味着您必须在客户端和服务器之间共享这个秘密。这基本上意味着秘密密钥必须是可见的,这样Javascript代码就能得到与服务器一致的密钥。你甚至可以通过SSL或其他任何方式传输它,但是无论任何人使用浏览器都可以获取那个密钥。更不用提它非常容易触发firebug并从Javascript程序中获取任何我想要的东西。底线是使用Javascript加密数据是一种毫无意义的练习。如果您真的需要将客户端的机密转移到服务器上,请使用SSL。它的设计是安全的,它是经过考验的,有问题也有问题。然而,他们有并且将被修正。

#2


1  

The IV was actually only 8, not 16.

IV实际上只有8,而不是16。

I found the following discussion eventually: https://forums.oracle.com/forums/thread.jspa?threadID=1525978&start=15&tstart=0

最后,我发现了下面的讨论:https://forums.oracle.com/forums/thread.jspa?

Based on that I updated my code to the following:

基于此,我将我的代码更新如下:

package com.myclass.util;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.regex.Pattern;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

   public class AES {
       private static Charset PLAIN_TEXT_ENCODING = Charset.forName("UTF-8");
       private static String CIPHER_TRANSFORMATION = "AES/CTR/NoPadding";
       private static String KEY_TYPE = "AES";
       private static int KEY_SIZE_BITS = 128;

       private SecretKey key;
       private Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
       private byte[] ivBytes = new byte[KEY_SIZE_BITS/8];

       public AES() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidParameterSpecException, InvalidKeyException, InvalidAlgorithmParameterException{
           KeyGenerator kgen = KeyGenerator.getInstance(KEY_TYPE);
           kgen.init(KEY_SIZE_BITS); 
           key = kgen.generateKey();
           cipher.init(Cipher.ENCRYPT_MODE, key);
           ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
       }

       public String getIVAsHex(){
           return byteArrayToHexString(ivBytes);
       }

       public String getKeyAsHex(){
           return byteArrayToHexString(key.getEncoded());
       }

       public void setCrtKey(String keyText) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException{
           byte[] bText = keyText.getBytes();
           SecretKey secretKey = new SecretKeySpec(bText, "AES");
           Cipher c2 = Cipher.getInstance("AES/ECB/NoPadding");
           c2.init(Cipher.ENCRYPT_MODE, secretKey);
           bText = c2.doFinal(bText);
           key = new SecretKeySpec(bText, "AES");
       }

       public void setStringToKey(String keyText) throws NoSuchAlgorithmException, UnsupportedEncodingException{
           setKey(keyText.getBytes());
       }

       public void setHexToKey(String hexKey){
           setKey(hexStringToByteArray(hexKey));
       }

       private void setKey(byte[] bArray){
           byte[] bText = new byte[KEY_SIZE_BITS/8];
           int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
           System.arraycopy(bArray, 0, bText, 0, end);
           key = new SecretKeySpec(bText, KEY_TYPE);
       }

       public void setStringToIV(String ivText){
           setIV(ivText.getBytes());
       }

       public void setHexToIV(String hexIV){
           setIV(hexStringToByteArray(hexIV));
       }

       private void setIV(byte[] bArray){
           byte[] bText = new byte[KEY_SIZE_BITS/8];
           int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
           System.arraycopy(bArray, 0, bText, 0, end);
           ivBytes = bText;
       }

        public String encryptCRT(String message) throws InvalidKeyException,
                IllegalBlockSizeException, BadPaddingException,
                InvalidAlgorithmParameterException {
            String hexMessage = encrypt(message);
            return byteArrayToHexString(ivBytes).concat(hexMessage.substring(2));
        }

        public String encrypt(String message) throws InvalidKeyException,
                IllegalBlockSizeException, BadPaddingException,
                InvalidAlgorithmParameterException {
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] encrypted = cipher.doFinal(message.getBytes(PLAIN_TEXT_ENCODING));
            String result = byteArrayToHexString(encrypted);
            return result;
        }

        public String decryptCrt(String hexCipherText) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException{
            byte[] ciphertextBytes = hexStringToByteArray(hexCipherText);
            ivBytes = Arrays.copyOf(Arrays.copyOf(ciphertextBytes, 8), 16);
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] recoveredCleartext = cipher.doFinal(ciphertextBytes, 8, ciphertextBytes.length - 8);
            return new String(recoveredCleartext);
        }


        public String decrypt(String hexCiphertext)
                throws IllegalBlockSizeException, BadPaddingException,
                InvalidKeyException, InvalidAlgorithmParameterException,
                UnsupportedEncodingException {
            byte[] dec = hexStringToByteArray(hexCiphertext);
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] decrypted = cipher.doFinal(dec);
            return new String(decrypted, PLAIN_TEXT_ENCODING);
        }

        private static String byteArrayToHexString(byte[] raw) {
            StringBuilder sb = new StringBuilder(2 + raw.length * 2);
            sb.append("0x");
            for (int i = 0; i < raw.length; i++) {
                sb.append(String.format("%02X", Integer.valueOf(raw[i] & 0xFF)));
            }
            return sb.toString();
        }

       private static byte[] hexStringToByteArray(String hex) {
            Pattern replace = Pattern.compile("^0x");
            String s = replace.matcher(hex).replaceAll("");

            byte[] b = new byte[s.length() / 2];
            for (int i = 0; i < b.length; i++){
              int index = i * 2;
              int v = Integer.parseInt(s.substring(index, index + 2), 16);
              b[i] = (byte)v;
            }
            return b;
       }
   }

Which is called like the following:

它的名称如下:

String keyString = "0123456789ABCDEF";
String hexCipherText = "0xe001ea0658fc084fe1f80204f8659484025cdcfb461f2a2e1e4090581a188870bc331b0328a7c94c030bddabf2a1";
AES e = new AES();

e.setCrtKey(keyString);
out.println(e.decryptCrt(hexCipherText));

And it works beautifully ;-).

而且效果很好。

#1


2  

Technically yes they can read what the other is writing. However, the Javascript code says the InputVector (IV) is 16 bytes:

从技术上讲,他们能读懂对方在写什么。然而,Javascript代码表示InputVector (IV)是16字节:

* @param {Number[]} input 16-byte (128-bit) input state array

And, Java has 128/8 = 16 IV byte array in it. So your original question seems like isn't an issue since both use 16 byte IVs.

Java有128/8 = 16 IV字节数组。所以你最初的问题似乎不是一个问题,因为它们都使用16字节的IVs。

Now for all of the issues I see with what you're doing. Encrypting on the client (web browser) is NOT secure. You can't make it secure. AES is a symmetric encryption algorithm and requires the same secret key at both ends. Which means you have to share that secret between the client and server. That basically means that secret key has to be visible so the Javascript code can get a key that's agreeable with the server. You can even transfer it over SSL or whatever, but no matter what anyone with a browser can grab that secret key. And not to mention its tremendously easy to fire up firebug and grab whatever I want out of your Javascript program. Bottom line is encrypting the data using Javascript is a pointless exercise. If you really need to transfer secrets on the client to the server use SSL. It's designed to be secure, it's battle tested, there are and have been issues with it. However, they have and will be fixed.

现在,对于我看到的所有问题和你正在做的事情。对客户端(web浏览器)进行加密是不安全的。你不能让它安全。AES是一种对称加密算法,在两端都需要相同的密钥。这意味着您必须在客户端和服务器之间共享这个秘密。这基本上意味着秘密密钥必须是可见的,这样Javascript代码就能得到与服务器一致的密钥。你甚至可以通过SSL或其他任何方式传输它,但是无论任何人使用浏览器都可以获取那个密钥。更不用提它非常容易触发firebug并从Javascript程序中获取任何我想要的东西。底线是使用Javascript加密数据是一种毫无意义的练习。如果您真的需要将客户端的机密转移到服务器上,请使用SSL。它的设计是安全的,它是经过考验的,有问题也有问题。然而,他们有并且将被修正。

#2


1  

The IV was actually only 8, not 16.

IV实际上只有8,而不是16。

I found the following discussion eventually: https://forums.oracle.com/forums/thread.jspa?threadID=1525978&start=15&tstart=0

最后,我发现了下面的讨论:https://forums.oracle.com/forums/thread.jspa?

Based on that I updated my code to the following:

基于此,我将我的代码更新如下:

package com.myclass.util;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.regex.Pattern;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

   public class AES {
       private static Charset PLAIN_TEXT_ENCODING = Charset.forName("UTF-8");
       private static String CIPHER_TRANSFORMATION = "AES/CTR/NoPadding";
       private static String KEY_TYPE = "AES";
       private static int KEY_SIZE_BITS = 128;

       private SecretKey key;
       private Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
       private byte[] ivBytes = new byte[KEY_SIZE_BITS/8];

       public AES() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidParameterSpecException, InvalidKeyException, InvalidAlgorithmParameterException{
           KeyGenerator kgen = KeyGenerator.getInstance(KEY_TYPE);
           kgen.init(KEY_SIZE_BITS); 
           key = kgen.generateKey();
           cipher.init(Cipher.ENCRYPT_MODE, key);
           ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
       }

       public String getIVAsHex(){
           return byteArrayToHexString(ivBytes);
       }

       public String getKeyAsHex(){
           return byteArrayToHexString(key.getEncoded());
       }

       public void setCrtKey(String keyText) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException{
           byte[] bText = keyText.getBytes();
           SecretKey secretKey = new SecretKeySpec(bText, "AES");
           Cipher c2 = Cipher.getInstance("AES/ECB/NoPadding");
           c2.init(Cipher.ENCRYPT_MODE, secretKey);
           bText = c2.doFinal(bText);
           key = new SecretKeySpec(bText, "AES");
       }

       public void setStringToKey(String keyText) throws NoSuchAlgorithmException, UnsupportedEncodingException{
           setKey(keyText.getBytes());
       }

       public void setHexToKey(String hexKey){
           setKey(hexStringToByteArray(hexKey));
       }

       private void setKey(byte[] bArray){
           byte[] bText = new byte[KEY_SIZE_BITS/8];
           int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
           System.arraycopy(bArray, 0, bText, 0, end);
           key = new SecretKeySpec(bText, KEY_TYPE);
       }

       public void setStringToIV(String ivText){
           setIV(ivText.getBytes());
       }

       public void setHexToIV(String hexIV){
           setIV(hexStringToByteArray(hexIV));
       }

       private void setIV(byte[] bArray){
           byte[] bText = new byte[KEY_SIZE_BITS/8];
           int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
           System.arraycopy(bArray, 0, bText, 0, end);
           ivBytes = bText;
       }

        public String encryptCRT(String message) throws InvalidKeyException,
                IllegalBlockSizeException, BadPaddingException,
                InvalidAlgorithmParameterException {
            String hexMessage = encrypt(message);
            return byteArrayToHexString(ivBytes).concat(hexMessage.substring(2));
        }

        public String encrypt(String message) throws InvalidKeyException,
                IllegalBlockSizeException, BadPaddingException,
                InvalidAlgorithmParameterException {
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] encrypted = cipher.doFinal(message.getBytes(PLAIN_TEXT_ENCODING));
            String result = byteArrayToHexString(encrypted);
            return result;
        }

        public String decryptCrt(String hexCipherText) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException{
            byte[] ciphertextBytes = hexStringToByteArray(hexCipherText);
            ivBytes = Arrays.copyOf(Arrays.copyOf(ciphertextBytes, 8), 16);
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] recoveredCleartext = cipher.doFinal(ciphertextBytes, 8, ciphertextBytes.length - 8);
            return new String(recoveredCleartext);
        }


        public String decrypt(String hexCiphertext)
                throws IllegalBlockSizeException, BadPaddingException,
                InvalidKeyException, InvalidAlgorithmParameterException,
                UnsupportedEncodingException {
            byte[] dec = hexStringToByteArray(hexCiphertext);
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
            byte[] decrypted = cipher.doFinal(dec);
            return new String(decrypted, PLAIN_TEXT_ENCODING);
        }

        private static String byteArrayToHexString(byte[] raw) {
            StringBuilder sb = new StringBuilder(2 + raw.length * 2);
            sb.append("0x");
            for (int i = 0; i < raw.length; i++) {
                sb.append(String.format("%02X", Integer.valueOf(raw[i] & 0xFF)));
            }
            return sb.toString();
        }

       private static byte[] hexStringToByteArray(String hex) {
            Pattern replace = Pattern.compile("^0x");
            String s = replace.matcher(hex).replaceAll("");

            byte[] b = new byte[s.length() / 2];
            for (int i = 0; i < b.length; i++){
              int index = i * 2;
              int v = Integer.parseInt(s.substring(index, index + 2), 16);
              b[i] = (byte)v;
            }
            return b;
       }
   }

Which is called like the following:

它的名称如下:

String keyString = "0123456789ABCDEF";
String hexCipherText = "0xe001ea0658fc084fe1f80204f8659484025cdcfb461f2a2e1e4090581a188870bc331b0328a7c94c030bddabf2a1";
AES e = new AES();

e.setCrtKey(keyString);
out.println(e.decryptCrt(hexCipherText));

And it works beautifully ;-).

而且效果很好。