AES-256-CBC在js、java、php三环境下互通加密解密

AES-CBC加解密涉及到三个元素:
  1. 明文 - 需要加密的内容
  2. 密钥 - 用于加密内容的密码
  3. 向量IV - 不知道作何解释,因为对加密原理不太了解,对向量长度有要求,16字节,PHP里面的mcrypt_encrypt函数可能需要32字节
三个环境下互通加密解密的关键点在密钥的处理上,加密和解密的时候并不是直接使用密钥进行加密解密的,而是使用密钥生成的定长的hash进行加密解密的,AES-128和AES-256对密钥长度要求不一样。
向量IV可以是固定字符串,也可以自己生成。
 
 

JS环境下的加解密

JS环境下采用CryptoJS

var plaintText = '1234567812345678'; // 明文
var keyStr = '1234567812345678'; // 一般key为一个字符串
var iv = '1234567812345678';//向量IV

//首先计算密钥hash

//CryptoJS.PBKDF2第二个参数是salt,这里直接使用密码算了,实际应用中可以任意随机,但是加解密需要相同

var keyHash = CryptoJS.PBKDF2(keyStr, keyStr, {hasher:CryptoJS.algo.SHA256, keySize: 8, iterations: 1000 });

keyHash = keyHash();
iv = keyHash.substr(32, 16);
keyStr = keyHash.substr(0, 32);

var key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(iv);

var encryptedData = CryptoJS.AES.encrypt(plaintText, key, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv:iv
});
var encryptedBase64Str = encryptedData.toString();
console.log(encryptedBase64Str);//encryptedBase64Str就是最后生成的密文,已经base64过了

//解密
var decode = CryptoJS.AES.decrypt(encryptedBase64Str, key, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv:iv
});
var decodeStr = decode.toString(CryptoJS.enc.Utf8);
console.info(decodeStr);//decodeStr就是解密之后的明文

 
PHP环境的加解密

class AES{

const CIPHER = 'AES-256-CBC';

static $padding = OPENSSL_ZERO_PADDING;

public static function encode($str, $key){
$str = self::pkcs7padding($str, 32);
$key = hash_pbkdf2('sha256', $key, $key, 1000, 0, false);
$iv = substr($key, 32, 16);
$key = substr($key, 0, 32);
$data = openssl_encrypt($str, self::CIPHER, $key, self::$padding, $iv);

if(!$data){
var_dump(openssl_error_string());exit();
}
return $data;
}

public static function decode($data, $key){
//$data = base64_decode($data);
$key = hash_pbkdf2('sha256', $key, $key, 1000, 0, false);
$iv = substr($key, 32, 16);
$key = substr($key, 0, 32);
$res = openssl_decrypt($data, self::CIPHER, $key, self::$padding, $iv);
$res = self::pkcs7unPadding($res);
return $res;
}

public static function pkcs7padding($data, $blocksize) {
$padding = $blocksize - strlen($data) % $blocksize;
$padding_text = str_repeat(chr($padding), $padding);
return $data . $padding_text;
}

public static function pkcs7unPadding($data) {
$length = strlen($data);
$unpadding = ord($data[$length - 1]);
return substr($data, 0, $length - $unpadding);
}

}

 

java环境的加解密
import java.security.Security;

import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class AES {

public static String charset = "utf-8";

public static String encode(String data, String password) throws Exception {
byte[] bytes = aes(data.getBytes(charset), password, Cipher.ENCRYPT_MODE);
String encode = base64Encode(bytes);
return encode;
}

public static String decode(String data, String password) throws Exception {
byte[] base64Bytes = base64Decode(data);
byte[] bytes = aes(base64Bytes, password, Cipher.DECRYPT_MODE);
return new String(bytes);
}

private static byte[] aes(byte[] content, String password, int opmode) throws Exception{

Provider provider = new BouncyCastleProvider();

//这里传第二个参数,可以兼容android,PBKDF2WithHmacSHA256算法在最新的android8里面才支持

byte[] keys = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256", provider).generateSecret(new PBEKeySpec(password.toCharArray(), password.getBytes(), 1000, 256)).getEncoded();

 //这里比较关键,不能把keys直接传给SecretKeySpec,一定要经过下面的转换才能跟PHP和js互通

 String keyStr = bytesToHexString(keys);
IvParameterSpec ivParameterSpec = new IvParameterSpec(keyStr.substring(32, 48).getBytes());
keyStr = keyStr.substring(0, 32);
keys = keyStr.getBytes();

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", provider );

cipher.init(opmode, new SecretKeySpec(keys, "AES"), ivParameterSpec);
return cipher.doFinal(content);
}

public static String base64Encode(byte[] data){
try {
return new String(Base64.getEncoder().encode(data), charset);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

public static String base64Encode(String str){
try {
return base64Encode(str.getBytes(charset));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

public static byte[] base64Decode(String str){
try {
return Base64.getDecoder().decode(str.getBytes(charset));
} catch (Exception e1) {
e1.printStackTrace();
}
return null;
}

public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}