<?php
/**
* Created by PhpStorm.
* User: xiaolong
* Date: 2017/9/6
* Time: 11:51
*/
class RSA
{
/**
* 返回数据验签失败错误码,和其他错误码有冲突时可自行修改该参数值
*/
const SIGN_INVALID = 403001;
/**
* 返回数据bizContent非法(无bizContent或为空串)的错误码,和其他错误码有冲突时可自行修改该参数值
*/
const BIZCONTENT_INVALID = 403002;
/**
* @var array 错误码对应数组 第一个元素始终为0, 处理errorCode为0的情况
*/
private $_errorCodeMap = array(0);
/**
* @var 私钥
*/
protected $privateKey = '-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDlRTEqGarpu6eesOseMMw4S7orIL43DJEU7LbOc8g1YWCRiQRK
BIiz3iyeAxAgOjRuSP+4mIL8V6FOS75lPPVM7F4FhrZzrqJaOcsCXrIUZ2cDoQ9+
Z17gHWmPVPVA9Q4QPO1A6KpeXpGjRmSSqIeLBmm3GtuSAN6tEP9KNSf/JQIDAQAB
AoGAWhUDqj0HkGqxA4MT/nrB4uSuMz/cPRjs8EHJ2fhYE9E89jHVw5dgdvu2oCcn
8OGttB9uioMyBCASOwc7ud9nqaEm7A8vS0QAV5thYC7hrn4x4HmskfpxdkGUol3i
xy7roe3sjtyG6E4VtVvCWr05HxBYPBSVXl9w6ODBHS3SeYkCQQD94Js1IlQpnPwx
OUpBopMOfr5qnu9sJoaxsCdo22B3alc5phP+2+Fz9ElaLi/3Jnn92hGuFTjF1pmd
M/mgY6v7AkEA5y/q0xsq1x+ya/oXjT6dkC4D1xaCduvASaKUiSEp3SbYjWx4FP7v
jgl2nrCGXjpPjW36rVyjbYDEAE+ZrvT3XwJAZlqFeJiMgfJuopHMZEXdL/zdXDMT
p/CoYT75xIadj8dpvy475YZUkOEuKZNxdx0mFbgzZJHdv7VTXVO1EnrcvQJBAJw3
KLnVVbFfXbTQnTF36ggOz9F7CFVLH/ehwDSZECy7nwCRFuM5EK4tftXj+ieZxz+N
3SFfw56ur8J2BybNqIkCQBLSa4KhNzHUcD7ZgfNTkvc6X2ajhgEBD47UIxQ80kRd
fPiAiZcfZnoRmi7NykALVilO32fUgovvfopi3aPeKCo=
-----END RSA PRIVATE KEY-----';
/**
* @var 公钥
*/
protected $publicKey = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlRTEqGarpu6eesOseMMw4S7or
IL43DJEU7LbOc8g1YWCRiQRKBIiz3iyeAxAgOjRuSP+4mIL8V6FOS75lPPVM7F4F
hrZzrqJaOcsCXrIUZ2cDoQ9+Z17gHWmPVPVA9Q4QPO1A6KpeXpGjRmSSqIeLBmm3
GtuSAN6tEP9KNSf/JQIDAQAB
-----END PUBLIC KEY-----';
/*
* 加密测试数据
*/
private $params = [
'BaseInfo' => [
'name' => 'Mark',
'age' => 21,
'TransrDate' => '2017-12-20', //交易日期
'TransrTime' => '2017-12-20', //交易时间
'TellerNo' => '8617001',
'TransrNo' => 'HM-sds-2017-12-20', //交易流水号
'SaleChannel' => 'w',
'SellType' => 20,
'FunctionFlag' => 'ST000051',
'Source' => 'HM',
'SubSource' =>''
],
'TranRequest' => [
'ContNoType' => 1
]
];
/*
* 解密测试数据
*/
private $paramsDecode = [
'format' => 'json',
'signType' => 'RSA',
'charset' => 'UTF-8',
'timestamp' => '20170906140222551',
'bizContent' => '32BFaHfScjoYWC00np/9gsnnyyHcTgvEWXbJJlpGAk7LthzOjwCSEmeU4SXaHSmLOB+B52NqWo8/6qax2eJzN1J9V2RP2U0HjpVLdYFwUKaLvKzHokpQbwhLxoc00VvepBch2tAAZ2j2nQ4f9f++qqUHGoU+FoOH/y3HMxderhkk5LRTYGWlUmLBhnhEtRfde+hlW7poX1erQYuIoKmqLLLgKbUU4ajCwd0rxA9O16mjxvCmv6VYpQqJNYHN2i4tDgTi5BMZbGW1MZ9MQ1vGK6sVEEToNHU7UycFrUTjn3UOJ2YxPRNmHyikpwhMKqqHuG+HQgZz/2HmBWpoRxOGH96TRkhFazK/YKCMggyRWL6sNEM0S2EK/qyZoWnKzl1mB+s4NeZymHZ3UxhB59cW7NVtebSvlWI7TPUtH56V1lX4mMY1nZlrQCpKgDyBfK2GfErD9LFH99SPB0Iv5pUrYfswSlWDzstY5JEJFyu4TuRlSqhpr5zHr1q9UF8p2Upu',
'sign' => 'iXHOej6kNSVodfbNnbA+Nr2FZ+E/S0VetSNcRd3pxWnfg5JcECvoDlYPzZA3KOPryRimKgkv4Ot0pENdwTs7Ex3sC3IFQJPUljebf4Dvj1yUe2hMGMIJrTkiqjrL1FOXceayKGsfHTn1VDDIRjUC0LY01NY1y4ZgIG84MeRP1+o='
];
public function test()
{
dump($this->doEncrypt($this->params)); //加密
dump($this->doDecrypt($this->encode($this->paramsDecode))); //解密
}
/**
* php 加密
* @param $bizParams array 需加密的数据
* @return array
*/
public function doEncrypt($bizParams)
{
$requestParams = array(
'format' => 'json',
'signType' => 'RSA',
'charset' => 'UTF-8',
'timestamp' => date('YmdHis') . mt_rand(100, 999),
'bizContent' => $this->_buildBizContent($bizParams),
);
$requestParams['sign'] = $this->_sign($requestParams);
return $requestParams;
}
/**
* php 解密
* @param $params array 需解密的数据
* @return array
*/
public function doDecrypt($params)
{
try {
$data = json_decode($params, true);
if (isset($data['sign']) && !$this->_verify($data)) { //如果返回数据验签失败
throw new Exception('返回数据验签失败!', self::SIGN_INVALID);
}
$this->_checkError($data); //检查系统级别的错误
if (isset($data['bizContent']) && strlen($data['bizContent'])) { //如果返回数据包含bizContent且不为空串,则解密
$rawBizContent = $this->phpDecrypt($data['bizContent']);
return json_decode($rawBizContent, true);
} else {
throw new Exception('返回bizContent为空或不存在!', self::BIZCONTENT_INVALID);
}
} catch (Exception $e) {
return array(
'errorCode' => $e->getCode(),
'errorMsg' => $e->getMessage()
);
}
}
/**
* 组装业务参数
* @param array $bizParams 业务参数数组
* @return string
*/
private function _buildBizContent($bizParams)
{
if (is_array($bizParams) && empty($bizParams)) {
$bizParams = '{}'; //参数为空的时候,bizContent必须为空map {}而不是空list []
}
return $this->phpEncrypt($bizParams);
}
/**
* php加密数据
* @param array|string $data 待加密的数据
* @return string
*/
public function phpEncrypt($data)
{
is_array($data) && ksort($data) && $data = $this->encode($data);
$encryptedList = array();
$step = 117; //加密长度限制
for ($i = 0, $len = strlen($data); $i < $len; $i += $step) {
$tmpData = substr($data, $i, $step);
$encrypted = '';
openssl_public_encrypt($tmpData, $encrypted, $this->publicKey);
$encryptedList[] = ($encrypted);
}
$encryptedData = base64_encode(join('', $encryptedList));
return $encryptedData;
}
/**
* php解密数据
* @param string $encryptedData 待解密的数据
* @return string
*/
public function phpDecrypt($encryptedData)
{
if (empty($encryptedData)) {
return '';
}
$encryptedData = base64_decode($encryptedData);
$decryptedList = array();
$step = 128; //解密长度限制
for ($i = 0, $len = strlen($encryptedData); $i < $len; $i += $step) {
$data = substr($encryptedData, $i, $step);
$decrypted = '';
openssl_private_decrypt($data, $decrypted, $this->privateKey);
$decryptedList[] = $decrypted;
}
return join('', $decryptedList);
}
/**
* 数据签名
* @param array|string $data 需签名的数据
* @return string
*/
private function _sign($data)
{
is_array($data) && ksort($data) && $data = $this->encode($data);
$data = stripslashes($data);
$res = openssl_get_privatekey($this->privateKey);
openssl_sign($data, $sign, $res); // openssl_sign() 第一个参数必须与 验签 openssl_verify() 的第一个参数对应
openssl_free_key($res);
$sign = base64_encode($sign);
return $sign;
}
/**
* 数据验签
* @param array|string $data 待验签的数据
* @return bool
*/
private function _verify($data)
{
/**
* 返回数据验签
* @param array $data 返回数据数组
* @return bool
*/
$sign = $data['sign'];
unset($data['sign']);
is_array($data) && ksort($data) && $data = $this->encode($data);
$data = stripslashes($data);
$res = openssl_get_publickey($this->publicKey);
$result = (bool) openssl_verify($data, base64_decode($sign), $res);
openssl_free_key($res);
return $result;
}
/**
* 检查返回数据中的错误信息
* @param array $data 返回数据数组
* @throws Exception
*/
protected function _checkError($data)
{
if (isset($data['errorMsg']) || isset($data['errorCode']) || isset($data['errorMessage'])) {
//对errorMsg为空的情况,统一errorMsg为未知错误
if (isset($data['errorMsg'])) {
$errorMsg = $data['errorMsg'];
} elseif (isset($data['errorMessage'])) {
$errorMsg = $data['errorMessage'];
} else {
$errorMsg = 'unknown error';
}
//接口返回errorCode统一转为整数 如未设置则默认errorCode为0
$errorCode = isset($data['errorCode']) ? $data['errorCode'] : 0;
throw new Exception($errorMsg, $this->_encodeErrorCode($errorCode));
}
}
/**
* json编码,对不同的php版本json_encode函数进行封装
* @param mixed $value 需编码的内容
* @param int $options 掩码
* @param int $depth 最大深度
* @return mixed|string
*/
private function encode($value, $options = 0, $depth = 512)
{
if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
return json_encode($value, $options | JSON_UNESCAPED_UNICODE, $depth);
} elseif (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($value, $options | JSON_UNESCAPED_UNICODE);
} else {
$data = version_compare(PHP_VERSION, '5.3.0', '>=') ? json_encode($value, $options) : json_encode($value);
return preg_replace_callback(
"/\\\\u([0-9a-f]{2})([0-9a-f]{2})/iu",
create_function(
'$pipe',
'return iconv(
strncasecmp(PHP_OS, "WIN", 3) ? "UCS-2BE" : "UCS-2",
"UTF-8",
chr(hexdec($pipe[1])) . chr(hexdec($pipe[2]))
);'
),
$data
);
}
}
/**
* 编码errorCode,对于非int型的errorCode,抛出异常时需先编码成int型
* @param mixed $errorCode 需编码的errorCode
* @return int
*/
protected function _encodeErrorCode($errorCode)
{
return array_push($this->_errorCodeMap, $errorCode) - 1;
}
}