重点在于aes加密规则,还有加密data和sign使用的secret和aesKey不要取错了。
我也是试了好多种加密方式才得出结果,现在分享给大家。
说明一下,我用的PHP版本是PHP7.3.4 ,Laravel5.7
先上平安银行提供的 java demo:
public class demo {
/**
* 链接拼接
* @param initDTO
* @return
*/
public static String getUrl(InitailFormDTO initDTO){
//sign加密KEY
String secret="Ued234fKsd3*45fLNs3-45dfk57s";
//AES加密KEY
String aesKey="BWTAws7fQgOJlgLFK-xIhA..";
String url="";
//data值 获取
String data=paramData(initDTO,aesKey);
if (isNullOrBlank(data)) {
return "ERROR.";
}else{
System.out.println("data最终结果:"+data);
}
//sign值 获取
String sign=paramSign(initDTO,secret);
if (isNullOrBlank(sign)) {
return "ERROR.";
}else{
System.out.println("验签最终结果:"+sign);
}
//生成最终URl(注意测试环境和生产环境 请求不同)
url= "https://pacesapplystg.pingan.com/ca/applyIndex?"+"data=" + data + "&sign=" + sign;
return url;
}
/**
* AES 获取data值
* (key值顺序不一致,可能会导致加密结果不一致。可以收)
* @param initDTO
* @param aesKey
* @return
*/
public static String paramData(InitailFormDTO initDTO,String aesKey){
//生成jason格式字段 直接使用gson,可能会更好
String jsonParam = JSONObject.toJSONString(getJasonMap(initDTO));//字段转Json
System.out.println("json格式结果:"+jsonParam);
String result = encrypt(jsonParam, aesKey);//AES加密
System.out.println("AES加密后结果:"+result);
if (!isNullOrBlank(result)) { //在最前面拼上9位的SCC和20位的mt参数值(mt参数如果不足20位用0补足),形成最终的密文数据。
if (isNullOrBlank(initDTO.getMt())) {
return initDTO.getScc() + "00000000000000000000" + result;
} else {
return initDTO.getScc() + StringUtils.leftPad(initDTO.getMt(), 20, '0') + result;
}
}else{
return null;
}
}
/**
* HmacSHA256验签 获取sign值
* @param initDTO
* @param aesKey
* @return
*/
public static String paramSign(InitailFormDTO initDTO,String secret){
String generateSign = null;
try {
Field[] fields = InitailFormDTO.class.getDeclaredFields();
List<String> list = new ArrayList<String>();
if (fields != null) {
for (Field a : fields) {
String name = a.getName();
Method m = InitailFormDTO.class.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
if (!isNullOrBlank((String) m.invoke(initDTO))) {
list.add(name);
}
}
if (list.size() > 0) {
Collections.sort(list);//将所有参数按key的ASCII码从小到大排序
StringBuffer bf = new StringBuffer();
for (String key : list) {//使用&符号拼接
String value = (String) InitailFormDTO.class.getMethod("get" + key.substring(0, 1).toUpperCase() + key.substring(1)).invoke(initDTO);
bf.append("&");
bf.append(key);
bf.append("=");
bf.append(value);
}
//将最终的字符串转换成全大写,再使用HmacSHA256进行签名。
System.out.println("排序后拼接转大写:"+bf.substring(1).toString().toUpperCase());
generateSign = sign(bf.substring(1).toString().toUpperCase(),secret);
}
}
return generateSign;
} catch (Exception e) {
return null;
}
}
/**
* AES加密
* 参数使用json格式,
* AES加密后再对加密后的字符串进行UrlBase64编码,
* @param data
* 需要加密的字符串
* @param aesKey
* AES key
* @return
*/
/**
* json格式结果:{"partnerSeqId":"m180320106300000201804191255555271111","scc":"920000999","templateNo":"s180419Oot","mt":"123456789","ccp":"1a2a3a9","versionNo":"R10310","onlineSQFlag":"N","cardCatenaNo":"01a02a04","channel":"WXHZF"}
* AES加密后结果:Ke19Ek_N6FrJf6KCOjodGViA2Dapm1GRjcog4mupkeskTmfa1VHvY9F5TBLyBBqxtykc9AGBSbOkkEG-GdB9bcBd6iWj2GmmMX5WxL1XtcRxVRW081AeLgKz_jVmVKfk-Wkl2g-Fzky57ChiGTZ8begt0BKM4a4623HRuXuVCVHFpeKjTCMVS7iLaem4uRJVslBx_Yd5gGQLLHk-qH2C6CPBd-OgOv6Kp4e05L4tioAj5X-bxSsMdQsgoctjBCu28jI8_DUEFBrANyuFBEUqKfbAVVQwFefZBMmFEc11FBo.
**/
public static String encrypt(String data, String aesKey) {
try {
if (isNullOrBlank(data) || isNullOrBlank(aesKey)) {
System.out.println("AESUtil.encrypt data or aesKey is null.");
return null;
}
byte[] aesByte = UrlBase64.decode(aesKey);
SecretKeySpec skeySpec = new SecretKeySpec(aesByte, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encryptedByte = cipher.doFinal(data.getBytes("utf-8"));
return new String(UrlBase64.encode(encryptedByte));
} catch (Exception e) {
System.out.println("AESUtil.encrypt exception."+e);
return null;
}
}
public static String desEncrypt(String data, String key, String ivString)
{
byte[] iv = null;
try {
iv = ivString.getBytes();
byte[] encryp = Base64.getDecoder().decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] original = cipher.doFinal(encryp);
return new String(original);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* HmacSHA256加密
* 请确保key值顺序排列
* @param value
* 需要加密的字符串
* @param secret
* HmacSHA256 key
* @return
*/
public static String sign(String value,String secret) {
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash =new String(UrlBase64.encode(sha256_HMAC.doFinal(value.getBytes("utf-8"))));
return hash;
} catch (Exception e) {
System.out.println("Failed to generate the sign."+ e);
return null;
}
}
public static boolean isNullOrBlank(String str) {
if (str == null || str.equals("") || str.equals("null")) {
return true;
} else {
return false;
}
}
/**
* 判断每个字段是否为空
* 返回去除value的key值
* @param obj
* @return
*/
private static Map<String,String> getJasonMap(Object obj){
Field[] field = obj.getClass().getDeclaredFields();
Map<String,String> returnMap=new HashMap<String,String>();
try {
for (int j = 0; j < field.length; j++) {
String name = field[j].getName();
// 过滤serialVersionUID字段
if ("serialVersionUID".equals(name)) {
continue;
}
String name1 = name.substring(0, 1).toUpperCase() + name.substring(1);
Method m = obj.getClass().getMethod("get" + name1);
String value = (String) m.invoke(obj);
if (!isNullOrBlank(value)) {
returnMap.put(name, value);
}
}
} catch (Exception e) {
System.out.println(e);
}
return returnMap;
}
public static void main(String[] args) {
String url="";
InitailFormDTO initDTO=new InitailFormDTO();
StringBuilder random = new StringBuilder();
for (int i = 0; i < 40; i++) {
int nextInt = new Random().nextInt(10);
System.out.println(nextInt);
random.append(nextInt);
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String data = simpleDateFormat.format(new Date());
/***************************参数赋值,请和业务确定*********************************************/
/*11位商户号+ 时间(yyyyMMddHHmmssSSS) + 4位随机数据数字。
* 商户号业务分配
* 时间可以自己用工具类生成
* 随机数可自己定义
*
*流水号只能点一次。
*流水号只能用一次
*流水号只能开一次。
*/
initDTO.setPartnerSeqId("m180320106300000"+data+ random.toString());
initDTO.setScc("920000999");// 业务分配
initDTO.setTemplateNo("s180419Oot"); // 业务分配
initDTO.setMt("123456789"); // 咨询业务
initDTO.setCcp("1a2a3a9"); // 咨询业务
initDTO.setVersionNo("R10310"); // 业务分配
initDTO.setOnlineSQFlag("N"); // 定值
initDTO.setCardCatenaNo("01a02a04"); // 咨询业务
initDTO.setChannel("WXHZF"); // 定值
/***************************中文记得转码UTF-8*************************************************/
System.out.println("需要加密的入参:"+initDTO);
url=getUrl(initDTO);
System.out.println( "最终生成测试环境url:" + url);
/**
* 结果:
* 需要加密的入参:InitailFormDTO [versionNo=R10310, scc=920000999, onlineSQFlag=N, channel=WXHZF, cardCatenaNo=01a02a04, ccp=1a2a3a9, cf=null, cc=null, salesName=null, salesCode=null, salesmanPhoneNO=null, bt1=null, bt2=null, bt3=null, bt4=null, bt5=null, bt6=null, bt7=null, bt8=null, bt9=null, bt10=null, mt=123456789, recommendChineseName=null, recommendPhone=null, isDisplayRecommend=null, isDisplaySales=null, onePlusOneWB=null, onePlusOneYL=null, url=null, templateNo=s180419Oot, partnerSeqId=m180320106300000201804191255555271111, idCardStatus=null, partnerInfoIndex=null, salesmanType=null, hasZiGeSaleMan=null]
* json格式结果:{"partnerSeqId":"m180320106300000201804191255555271111","scc":"920000999","templateNo":"s180419Oot","mt":"123456789","ccp":"1a2a3a9","versionNo":"R10310","onlineSQFlag":"N","cardCatenaNo":"01a02a04","channel":"WXHZF"}
* AES加密后结果:Ke19Ek_N6FrJf6KCOjodGViA2Dapm1GRjcog4mupkeskTmfa1VHvY9F5TBLyBBqxtykc9AGBSbOkkEG-GdB9bcBd6iWj2GmmMX5WxL1XtcRxVRW081AeLgKz_jVmVKfk-Wkl2g-Fzky57ChiGTZ8begt0BKM4a4623HRuXuVCVHFpeKjTCMVS7iLaem4uRJVslBx_Yd5gGQLLHk-qH2C6CPBd-OgOv6Kp4e05L4tioAj5X-bxSsMdQsgoctjBCu28jI8_DUEFBrANyuFBEUqKfbAVVQwFefZBMmFEc11FBo.
* data最终结果:92000099900000000000123456789Ke19Ek_N6FrJf6KCOjodGViA2Dapm1GRjcog4mupkeskTmfa1VHvY9F5TBLyBBqxtykc9AGBSbOkkEG-GdB9bcBd6iWj2GmmMX5WxL1XtcRxVRW081AeLgKz_jVmVKfk-Wkl2g-Fzky57ChiGTZ8begt0BKM4a4623HRuXuVCVHFpeKjTCMVS7iLaem4uRJVslBx_Yd5gGQLLHk-qH2C6CPBd-OgOv6Kp4e05L4tioAj5X-bxSsMdQsgoctjBCu28jI8_DUEFBrANyuFBEUqKfbAVVQwFefZBMmFEc11FBo.
* 排序后拼接转大写:CARDCATENANO=01A02A04&CCP=1A2A3A9&CHANNEL=WXHZF&MT=123456789&ONLINESQFLAG=N&PARTNERSEQID=M180320106300000201804191255555271111&SCC=920000999&TEMPLATENO=S180419OOT&VERSIONNO=R10310
* 验签最终结果:iMMUARHZgiCRSzKOfp0dJ0-pbxCbH7eoPmB3eKj2nGs.
* 最终生成测试环境url:https://pacesapplystg.pingan.com/ca/applyIndex?data=92000099900000000000123456789Ke19Ek_N6FrJf6KCOjodGViA2Dapm1GRjcog4mupkeskTmfa1VHvY9F5TBLyBBqxtykc9AGBSbOkkEG-GdB9bcBd6iWj2GmmMX5WxL1XtcRxVRW081AeLgKz_jVmVKfk-Wkl2g-Fzky57ChiGTZ8begt0BKM4a4623HRuXuVCVHFpeKjTCMVS7iLaem4uRJVslBx_Yd5gGQLLHk-qH2C6CPBd-OgOv6Kp4e05L4tioAj5X-bxSsMdQsgoctjBCu28jI8_DUEFBrANyuFBEUqKfbAVVQwFefZBMmFEc11FBo.&sign=iMMUARHZgiCRSzKOfp0dJ0-pbxCbH7eoPmB3eKj2nGs.
*/
}
}
demo最后试加密过程的打印结果,按照打印结果对照执行结果是否一致
<?php
class Service
{
protected $domain = 'https://pacesapplystg.pingan.com/';
public function __construct () {
parent::__construct();
}
//入口方法
public function getBankUrl($params = [],$serial_number = '')
{
$requestData = '{"partnerSeqId":"m180320106300000201804191255555271111","scc":"920000999","templateNo":"s180419Oot","mt":"123456789","ccp":"1a2a3a9","versionNo":"R10310","onlineSQFlag":"N","cardCatenaNo":"01a02a04","channel":"WXHZF"}';
$requestData = json_decode($requestData, true);
$SECRET = 'Ued234fKsd3*45fLNs3-45dfk57s';
$AESKEY = 'BWTAws7fQgOJlgLFK-xIhA..';
$data = $this->paramData($requestData, $AESKEY);
dump("data最终结果:".$data);
$sign = $this->paramSign($requestData, $SECRET);
dump("验签最终结果:".$sign);
return $this->domain."ca/applyIndex?data=" . $data ."&sign=" . $sign;
}
public function getUrlParams($param)
{
ksort($param);
$str = '';
//循环拼接参数
foreach ($param as $key => $value) {
$str .= $key . "=" . urlencode($value) . "&";
}
$str = substr($str, 0, strlen($str) - 1);
return $str;
}
public function paramData($param = [], $aesKey)
{
$jsonParam = json_encode($param);
dump("json格式结果:".$jsonParam);
$result = $this->ssl_encrypt($jsonParam, $aesKey);
dump('AES加密后结果:'.$result);
if(!$result) {
return false;
}
$data_per = $param['scc'];
if(isset($param['mt'])){
$num_len = strlen($param['mt']);
for($i = 0; $i <20-$num_len; $i++){
$data_per = $data_per . '0';
}
$data_per .= $param['mt'];
} else{
for($i = 0; $i <20; $i++){
$data_per = $data_per . '0';
}
}
$result = $data_per.$result;
return $result;
}
/**
* 获取sign(HmacSHA256验签)
* @param array $params
* @param string $secret
* @return string
*/
public function paramSign($params = [], $secret = '')
{
ksort($params);
$str = '';
foreach ($params as $key => $value) {
$str .= $key . "=" . urlencode($value) . "&";
}
$str = substr($str, 0, strlen($str) - 1);
//字符串转大写
$str = strtoupper($str);
//HMAC-SHA256加密
$sign = hash_hmac('sha256', $str, $secret, true);
//参数编码
$sign = $this->base64UrlEncode($sign);
return $sign;
}
/**
* PHP7 AES 加密
* @param $str
* @param $aesKey
* @return mixed
*/
public function ssl_encrypt($str, $aesKey)
{
$data = openssl_encrypt($str, 'aes-128-ecb', $this->base64UrlDecode($aesKey), OPENSSL_RAW_DATA);
return $this->base64UrlEncode($data);
}
/**
* 获取sign(HmacSHA256验签)
* @param $data
* @param $secret
* @return string|string[]
*/
public function sign($data, $secret)
{
ksort($data);
$str = '';
foreach ($data as $key => $value) {
//拼接参数
$str .= $key . "=" . urlencode($value) . "&";
}
//去除最后一个字符
$str = substr($str, 0, strlen($str) - 1);
//字符串转大写
$str = strtoupper($str);
//HMAC-SHA256加密
$sign = hash_hmac('sha256', $str, $secret, true);
$sign = $this->base64UrlEncode($sign);
return $sign;
}
public function base64UrlEncode($str)
{
$data = base64_encode($str);
$data = str_replace(array('+', '/', '='), array('-', '_', '.'), $data);
return $data;
}
public function base64UrlDecode($str)
{
$data = str_replace( array('-', '_', '.'), array('+', '/', '='),$str);
return base64_decode($data);
}
}
控制打印结果: