1. 程式人生 > >js和java中的AES加密和解密

js和java中的AES加密和解密

每次都要在這個問題上耗費一天的時間,所以這次留下記錄免得以後麻煩。

JS端使用CryptoJS,服務端bouncy castle提供的AES演算法。
AES演算法採用“AES/CBC/PKCS7Padding”,這個在JS和JAVA中都支援。Java預設的加密演算法中,不支援PKCS7Padding,只支援PKCS5Padding,bouncy castle支援PKCS7Padding;CryptoJS中沒有Pkcs5,只有Pkcs7。所以最後才選擇js部分用CryptoJS和java部分用bouncy castle的實現。

Java部分的“AES/CBC/PKCS7Padding”描述的內容是這樣的,AES是加密解密演算法;CBC是加密過程中如何分塊,還有加密各個塊的時候如何更換金鑰;PKCS7Padding是加密資料不夠一塊的時候如何填補剩餘空間的。AES+CBC+PKCS7Padding這樣的組合是CryptoJS的預設設定。

//JsonFormatter是用來把加密結果格式化的工具。如果不設定,加密結果toString()之後是base64編碼的密文。
var JsonFormatter = {
          stringify: function (cipherParams) {
              // create json object with ciphertext
              var jsonObj = {
                  ct: cipherParams.ciphertext.toString(CryptoJS.enc.Hex)
              };
              // optionally add iv and salt
if (cipherParams.iv) { jsonObj.iv = cipherParams.iv.toString(); } if (cipherParams.salt) { jsonObj.s = cipherParams.salt.toString(); } // stringify json object return JSON.stringify(jsonObj); }, parse: function
(jsonStr) {
// parse json string var jsonObj = JSON.parse(jsonStr); // extract ciphertext from json object, and create cipher params object var cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Hex.parse(jsonObj.ct) }); // optionally extract iv and salt if (jsonObj.iv) { cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv) } if (jsonObj.s) { cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s) } return cipherParams; } }; var pwd = CryptoJS.enc.Hex.parse("00000000000000000000000000000000");//密碼必須用Hex或其他方式處理為byte陣列,如果直接使用字串,CryptoJS會用加鹽的方法重新生成密碼,而且加的鹽是隨機數,這樣在java端就沒法解祕了。 var iv = CryptoJS.enc.Hex.parse('11111111111111111111111111111111');//iv在java中必須為16byte長,所以js中也必須設定為相同的長度,否則加密後的結果在java中無法解密。 var setting={iv:iv, //mode:CryptoJS.mode.CBC, //預設值,可以不設定 //padding:CryptoJS.pad.Pkcs7,//同上 format: JsonFormatter}; var mi=CryptoJS.AES.encrypt(args.data,pwd, setting); mi=JSON.parse(mi.toString());//mi本身是個物件,而且內部屬性迴圈引用,所以不用直接用JSON處理,需要先toString()。因為我們設定過format,所以得到一個son字串,這樣可以獲得密文和iv。 args.data=mi.ct;//ct是密文,iv是引數向量,s是鹽(密碼為字串的時候出現,否則不出現)

java的AES工具

Security.addProvider(new BouncyCastleProvider());//新增BouncyCastle的實現, 放static塊中
//下面是一些常量
/**
   * IV大小.
   */
  private static final int IV_SIZE = 16;

  /**
   * BC包中AES演算法名.
   */
  public static final String ALGORITHM_LONG_NAME = "AES/CBC/PKCS7Padding";

  /**
   * BC包中AES演算法名.
   */
  public static final String ALGORITHM_SHORT_NAME = "AES";

  /**
   * BC Provider名稱.
   */
  public static final String PROVIDER_NAME = "BC";

//獲得加密器的函式
private static Cipher generateCipher(final int mode, final byte[] key,
      final byte[] ivp) throws NoSuchAlgorithmException,
      NoSuchProviderException, NoSuchPaddingException, InvalidKeyException,
      InvalidAlgorithmParameterException {
    Cipher res = null;
    final SecretKey secretkey = new SecretKeySpec(key, ALGORITHM_SHORT_NAME);
    final IvParameterSpec ivparameter = new IvParameterSpec(ivp);
    res = Cipher.getInstance(ALGORITHM_LONG_NAME, PROVIDER_NAME);
    res.init(mode, secretkey, ivparameter);

    return res;
  }

//java安全加密的部分對隨機數又要求,普通的隨機數是不行的,需要特殊處理,應該是長度、演算法上有區別,而且好像儲存也不一樣。使用的方法如下:
/**
   * 獲得金鑰.
   * @return 金鑰.
   */
  public static byte[] generateKey() {
    byte[] res = null;
    KeyGenerator keyGen = null;
    SecretKey key = null;
    try {
      keyGen = KeyGenerator.getInstance(ALGORITHM_SHORT_NAME, PROVIDER_NAME);
      keyGen.init(new SecureRandom());
      key = keyGen.generateKey();
      res = key.getEncoded();
    } catch (NoSuchAlgorithmException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchProviderException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    }
    return res;
  }

//Java的cipher可以完成加密和解密兩種功能,處理過程如下
/**
   * 處理加密解密過程.
   * @param input
   *          輸入.
   * @param cipher
   *          cipher.
   * @return 結果.
   */
  private static byte[] process(final byte[] input, final Cipher cipher) {

    byte[] res = null;
    ByteArrayOutputStream bOut = new ByteArrayOutputStream();
    CipherOutputStream cOut = new CipherOutputStream(bOut, cipher);

    try {
      cOut.write(input);
      cOut.flush();
      cOut.close();
      res = bOut.toByteArray();
    } catch (IOException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    }
    return res;
  }

//加密和解密介面

  /**
   * 加密.
   * @param data
   *          加密的資料.
   * @param key
   *          金鑰.
   * @param iv
   *          CBC演算法所需初始矩陣.
   * @return 加密結果.
   */
  public static byte[] encrypt(
      final byte[] data, final byte[] key, final byte[] iv
  ) {
    byte[] res = null;
    try {
      res = process(data, generateCipher(Cipher.ENCRYPT_MODE, key, iv));
    } catch (InvalidKeyException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchAlgorithmException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchProviderException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchPaddingException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (InvalidAlgorithmParameterException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    }
    return res;
  }

  /**
   * 解密.
   * @param data
   *          解密的資料.
   * @param key
   *          金鑰.
   * @param iv
   *          CBC演算法所需初始矩陣.
   * @return 解密結果.
   */
  public static byte[] decrypt(
      final byte[] data, final byte[] key, final byte[] iv
  ) {
    byte[] res = null;
    try {
      res = process(data, generateCipher(Cipher.DECRYPT_MODE, key, iv));
    } catch (InvalidKeyException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchAlgorithmException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchProviderException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (NoSuchPaddingException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    } catch (InvalidAlgorithmParameterException e) {
      staticLogger(AesUtil.class).error(e.getMessage(), e);
    }
    return res;
  }

在java中採用相同的密碼和iv就可以解密js的密文了。
解密程式碼如下:

byte[] key = Hex.decodeHex("000000000...000000000000000".toCharArray());//注意密碼長度
            byte[] iv = Hex.decodeHex("11111111111111...111".toCharArray());//注意iv長度

            byte[] ct = Hex.decodeHex(new String(ct).toCharArray());
            byte[] res = AesUtil.decrypt(ct, key, iv);

java端加密,用於返回

byte[] key = Hex.decodeHex("111...111".toCharArray());
byte[] iv = Hex.decodeHex("000...000".toCharArray());
byte[] ct = AesUtil.encrypt(ct, key, iv);
byte[] output = Hex.encodeHexString(ct).getBytes();

js端解密

//data是寫回的資料
var msg=CryptoJS.enc.Hex.parse(data);
msg=CryptoJS.enc.Base64.stringify(msg);//CryptJS的解密方法輸入密文資料必須為Base64編碼
var decrypted = CryptoJS.AES.decrypt(msg, pwd, { iv:iv});  //密碼和iv同加密過程
var txt = (CryptoJS.enc.Utf8.stringify(decrypted).toString());//獲得明文

java加密的過程沒有直接貼工具上來,因為拆成好幾個部分寫了,涉及其它東西太多。這樣應該能復原了。