对接最麻烦的是对方没有PHP的demo,只有java的代码,并且在相同的算法在PHP中和java是不同的写法。
签名验签
在java中签名使用的是SHA1withRSA算法,在PHP中对应的是openssl_sign默认算法,所以只需要按照以下代码编写即可:
/**
* 获取签名
* @author shenzx
* @date 2023-02-27 15:27
*/
protected function setSign(){
//获取私钥
$privateKey = openssl_pkey_get_private(file_get_contents($this->privateKeyPath));
//签名 如果失败openssl_sign会返回false
openssl_sign($this->reqData, $sign, $privateKey);
//释放私钥资源
openssl_free_key($privateKey);
//签名进行base64编码
$sign = base64_encode($sign);
//设置签名
$this->sign = $sign;
}
/**
* 验签
* @author shenzx
* @date 2023-02-28 11:27
* @param $result 解密后的验签数据
* @param $sign 京东返回的签名
* @return bool|mixed
* @throws \Exception
*/
public function verifySign($result,$sign){
//获取京东公钥
$publicKey = openssl_pkey_get_public(file_get_contents($this->JdPublicKeyPath));
//使用公钥进行签名验证
$verify = (bool)openssl_verify($result, base64_decode($sign), $publicKey);
//判断验签结果
if($verify == false){
throw new \Exception("验签失败");
}
//释放公钥
openssl_free_key($publicKey);
//验签成功 返回数据
return $result;
}
AES/ECB/PKCS5Padding算法
在京东的demo中它不止是单纯的AES/ECB/PKCS5Padding算法,还包含了SHA1PRNG,京东代码:
所以在PHP中我们应该这样写:
//适用版本必须是PHP7+以上版本
/**
* 加密请求数据
* @author shenzx
* @date 2023-02-27 17:02
* @return string
*/
private function aesEncrypt($data,$password){
$key = $this->_sha1prng($password);
$iv = '';
$data = openssl_encrypt($data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($data);
return $data;
}
/**
* SHA1PRNG算法
* @param [type] $key [description]
* @return [type] [description]
*/
private function _sha1prng($key)
{
return substr(openssl_digest(openssl_digest($key, 'sha1', true), 'sha1', true), 0, 16);
}
/**
* 解密返回数据
* @author shenzx
* @date 2023-02-28 11:19
* @param $resData
* @param $password
* @return string
*/
private function aesDecrypt($resData,$password){
$password = $this->_sha1prng($password);
$iv = '';
$decrypted = openssl_decrypt(base64_decode($resData), 'AES-128-ECB', $password, OPENSSL_RAW_DATA, $iv);
return $decrypted;
}
其中最容易忽略的就是SHA1PRNG算法,其他的我们按照AES-128-ECB算法加密即可。
RSA/ECB/PKCS1Padding算法
这个算法在PHP中只要按照正常的公私钥加解密写法编写即可,但是在第四个参数必须要加OPENSSL_PKCS1_PADDING设置填充模式
/**
* 京东公钥加密
* @author shenzx
* @date 2023-02-28 11:14
* @return mixed
*/
public function rsaPublicEncrypt($data,$JdPublicKeyPath)
{
//获取京东公钥
$publicKey = openssl_pkey_get_public(file_get_contents($JdPublicKeyPath));
//加密字符串
$result = openssl_public_encrypt($data, $encrypted, $publicKey,OPENSSL_PKCS1_PADDING);
if (!$result)
{
throw new \Exception("秘钥加密失败");
}
$encrypted = base64_encode($encrypted);
return $encrypted;
}
/**
* 私钥解密返回数据 encryptKey
* @author shenzx
* @date 2023-02-27 17:02
* @return string
*/
private function rsaPrivateDecrypt($encryptKey,$privateKeyPath){
$encryptKey = base64_decode($encryptKey);
//获取私钥
$privateKey = openssl_pkey_get_private(file_get_contents($privateKeyPath));
//使用私钥进行解密
$result = (bool)openssl_private_decrypt($encryptKey, $decrypted, $privateKey,OPENSSL_PKCS1_PADDING);
//判断解密结果
if($result == false){
throw new \Exception("解密失败");
}
//释放私钥
openssl_free_key($privateKey);
return $decrypted;
}
openssl_public_encrypt函数加密数据是有长度限制的,如果需要加密的数据太大,就需要分段加密