Java、JS、PHP关于AES的加密解密互通问题
本文由 小茗同学 发表于 2017-03-18 浏览(15923)
最后修改 2017-03-18 标签:java php aes javascript

背景

最近对接统一登录碰到PHP和Java的AES加密结果不一致问题,花了不少的时间,特此记录一下。

大致逻辑就是第三方平台对接统一登录平台时,拿到对方给的一些数据然后采用AES加密生成一个token给对方校验,第三方平台是Java写的,统一登录平台时php写的,而且php那边肯定是不能随意修改代码的,只能改Java这边。

首先,php那边的加密代码如下:

/**
 * 加密
 * @return HTML
 */
public function encrypt($content, $key)
{
	return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $content, MCRYPT_MODE_CBC, $key));
}

然后Java这边试了好几种网上的代码,算出来的结果都不一致,不过好在后来总算找到一个可行的。

正确代码

php

<?php
/**
 * AES加密
 * @param $content
 * @param $key
 * @param $iv
 * @return string
 */
function encrypt($content, $key, $iv)
{
	return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $content, MCRYPT_MODE_CBC, $iv));
}

/**
 * AES解密
 * @param $content
 * @param $key
 * @param $iv
 * @return string
 */
function decrypt($content, $key, $iv)
{
	return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($content), MCRYPT_MODE_CBC, $iv);
}

$content = '这是要加密的数据';
$key = 'asdfgqwert';
$encrypted = encrypt($content, $key, $key);
echo '加密之前:'.$content.'<br>';
echo '加密之后:'.$encrypted.'<br>';
echo '加密之前:'.decrypt($encrypted, $key, $key).'<br>';
?>

输出:

加密之前:这是要加密的数据
加密之后:ajvinaEMRlM3R5OCxVa9WlBq3IwDFClHuCVmSNwsIQ4=
加密之前:这是要加密的数据

如果key的长度不是16会有下面的警告:

Warning: mcrypt_encrypt(): The IV parameter must be as long as the blocksize

Java

写好的AES工具类:

package com.lutongnet.base;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * AES工具类
 * @author LXA
 * @start 2017-03-18
 * @last 2017-03-18
 */
public class AESUtil
{

	/**
	 * AES加密
	 * @param content 要加密的内容
	 * @param key 密钥
	 * @param iv iv
	 * @return
	 */
	public static String encrypt(String content, String key, String iv)
	{
		if(key == null || key.length() != 16)
		{
			System.err.println("AES key 的长度必须是16位!");
			return null;
		}
		if(iv == null || iv.length() != 16)
		{
			System.err.println("AES iv 的长度必须是16位!");
			return null;
		}
		try
		{
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			int blockSize = cipher.getBlockSize();
			byte[] dataBytes = content.getBytes();
			int plaintextLength = dataBytes.length;
			if (plaintextLength % blockSize != 0)
			{
				plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
			}
			byte[] plaintext = new byte[plaintextLength];
			System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
			cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
			byte[] encrypted = cipher.doFinal(plaintext);
			return new BASE64Encoder().encode(encrypted);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * AES加密,key和iv一样
	 * @param content 要加密的内容
	 * @param key 密钥
	 * @return
	 */
	public static String encrypt(String content, String key)
	{
		return encrypt(content, key, key);
	}

	/**
	 * AES解密
	 * @param content 要解密的内容
	 * @param key 密钥
	 * @param iv iv
	 * @return
	 */
	public static String decrypt(String content, String key, String iv)
	{
		try
		{
			byte[] encrypted = new BASE64Decoder().decodeBuffer(content);
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
			cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
			byte[] original = cipher.doFinal(encrypted);
			return new String(original);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * AES解密
	 * @param content 要解密的内容
	 * @param key 密钥
	 * @param iv iv
	 * @return
	 */
	public static String decrypt(String content, String key)
	{
		return decrypt(content, key, key);
	}

	public static void main(String[] args)
	{
		String content = "这是要加密的数据";
		String key = "aaaabbbbccccdddd";
		String encrypted = encrypt(content, key);
		System.out.println("加密之前:" + content);
		System.out.println("加密之后:" + encrypted);
		System.out.println("解密之后:" + decrypt(encrypted, key));
	}
}

测试结果:

加密之前:这是要加密的数据
加密之后:ajvinaEMRlM3R5OCxVa9WlBq3IwDFClHuCVmSNwsIQ4=
解密之后:这是要加密的数据

JS版

暂时没去研究,参考这篇文章:

http://blog.csdn.net/wd4871/article/details/51461048

仅做记录

下面是之前试过的2种不合适的代码,记录下来。

代码一(原文):

import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class AESUtil2
{
	/**
	 * 加密
	 * @param content 需要加密的内容
	 * @param password 加密密码
	 * @return
	 */
	public static byte[] encrypt(String content, String password)
	{
		try
		{
			KeyGenerator kgen = KeyGenerator.getInstance("AES");
			kgen.init(128, new SecureRandom(password.getBytes()));
			SecretKey secretKey = kgen.generateKey();
			byte[] enCodeFormat = secretKey.getEncoded();
			SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");// 创建密码器
			byte[] byteContent = content.getBytes("utf-8");
			cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
			byte[] result = cipher.doFinal(byteContent);
			return result; // 加密
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}
}

代码二(原文地址):

  /*
   * 加密
   * 1.构造密钥生成器
   * 2.根据ecnodeRules规则初始化密钥生成器
   * 3.产生密钥
   * 4.创建和初始化密码器
   * 5.内容加密
   * 6.返回字符串
   */
	public static String AESEncode(String encodeRules,String content){
		try {
			//1.构造密钥生成器,指定为AES算法,不区分大小写
			KeyGenerator keygen=KeyGenerator.getInstance("AES");
			//2.根据ecnodeRules规则初始化密钥生成器
			//生成一个128位的随机源,根据传入的字节数组
			keygen.init(128, new SecureRandom(encodeRules.getBytes()));
			  //3.产生原始对称密钥
			SecretKey original_key=keygen.generateKey();
			  //4.获得原始对称密钥的字节数组
			byte [] raw=original_key.getEncoded();
			//5.根据字节数组生成AES密钥
			SecretKey key=new SecretKeySpec(raw, "AES");
			  //6.根据指定算法AES自成密码器
			Cipher cipher=Cipher.getInstance("AES");
			  //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
			cipher.init(Cipher.ENCRYPT_MODE, key);
			//8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
			byte [] byte_encode=content.getBytes("utf-8");
			//9.根据密码器的初始化方式--加密:将数据加密
			byte [] byte_AES=cipher.doFinal(byte_encode);
		  //10.将加密后的数据转换为字符串
			//这里用Base64Encoder中会找不到包
			//解决办法:
			//在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
			String AES_encode=new String(new BASE64Encoder().encode(byte_AES));
		  //11.将字符串返回
			return AES_encode;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

		//如果有错就返加nulll
		return null;
	}

参考

http://www.cnblogs.com/shoubianxingchen/p/5869373.html