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加密的过程没有直接贴工具上来,因为拆成好几个部分写了,涉及其它东西太多。这样应该能复原了。

你可能感兴趣的:(java,操作记录)