1、OpenSSL工具生成密钥
- 1.OpenSSL工具下载地址
https://www.openssl.org/source/ - 2.在指定目录下生成1024位rsa密钥pem文件
##生成 private.pem 私钥
mkdir temp_key && cd temp_key && openssl genrsa -out private.pem 1024
- 3.通过密钥生成cer证书
##生成 public.cer 公钥
openssl req -new -x509 -key private.pem -out public.cer -days 3650 -subj /CN=topbooking.natapp1.cc
执行此命令可能报以下错误:
vagrant@homestead:~/code/top/public/ticket_key$ openssl req -new -x509 -key private.pem -out public.cer -days 3650 -subj /CN=topbooking.natapp1.cc
Can't open /usr/local/ssl/openssl.cnf for reading, No such file or directory
140674040239360:error:02001002:system library:fopen:No such file or directory:crypto/bio/bss_file.c:69:fopen('/usr/local/ssl/openssl.cnf','r')
140674040239360:error:2006D080:BIO routines:BIO_new_file:no such file:crypto/bio/bss_file.c:76:
vagrant@homestead:~/code/top/public/ticket_key$ ll /usr/local/ssl/
ls: cannot access '/usr/local/ssl/': No such file or directory
通过下面的方式解决
# 查找本地的配置文件
find / -name openssl.cnf 2>/dev/null
# Ubuntu 文件一般在 /etc/ssl/openssl.cnf
# 建立一个链接
sudo mkdir /usr/local/ssl
sudo ln -s /etc/ssl/openssl.cnf /usr/local/ssl/openssl.cnf
# 重新执行
openssl req -new -x509 -key private.pem -out public.cer -days 3650 -subj /CN=topbooking.natapp1.cc
- 4.通过pem密钥和cer证书生成pfx文件
## 会需要设置密码,比如密码设置为111111,生成文件 openssl.pfx
openssl pkcs12 -export -out openssl.pfx -inkey private.pem -in public.cer
- 5.通过pem密钥生成pem公钥给数据交互方
比如长隆配置自己的公钥
#生成公钥 app_public_key.pem
openssl rsa -in private.pem -pubout -out app_public_key.pem
- 6.将生成的公钥
app_public_key.pem发给数据交互方
2、数据签名
<?php
namespace App\Services\ChimeLong;
use Illuminate\Support\Str;
/**
* RSA加密
* Class RSACryptoService
* @package App\Services\MPay
*/
class RSACryptoService
{
private $_mer_no;
private $_aes_key;
private $_ver_no;
private $_privateKey;
public $_publicKey;
/**
* 参数初始化
* RSACryptoService constructor.
*/
public function __construct()
{
if (app()->environment('production')) {//正式环境
$this->_mer_no = config('api_token.chimeLong.prod.mer_no');
$this->_aes_key = substr(md5(config('api_token.chimeLong.prod.key')), 8, 16);
$this->_ver_no = config('api_token.chimeLong.prod.ver_no');
$privatePemPath = public_path(config('api_token.chimeLong.prod.privatePemPath'));
$publicPemPath = public_path(config('api_token.chimeLong.prod.publicPemPath'));
$publicPemPathCL = public_path(config('api_token.chimeLong.prod.publicPemPathCL'));
} else {//测试环境
$this->_mer_no = config('api_token.chimeLong.test.mer_no');
$this->_aes_key = substr(md5(config('api_token.chimeLong.test.key')), 8, 16);
$this->_ver_no = config('api_token.chimeLong.test.ver_no');
$privatePemPath = public_path(config('api_token.chimeLong.test.privatePemPath'));
$publicPemPath = public_path(config('api_token.chimeLong.test.publicPemPath'));
$publicPemPathCL = public_path(config('api_token.chimeLong.test.publicPemPathCL'));
}
$this->get_private_key($privatePemPath);//请求的数据用己方的私钥签名,长隆用我们的公钥验签接收请求数据
$this->get_public_key($publicPemPathCL);//接收的响应数据用长隆的公钥验签
}
/**
* 获取私钥
* @param $privatePemPath
*/
private function get_private_key($privatePemPath)
{
$this->_privateKey = openssl_pkey_get_private(file_get_contents($privatePemPath));
}
/**
* 从证书中解析公钥,以供使用
* @param $publicPemPath
*/
private function get_public_key($publicPemPath)
{
$this->_publicKey = openssl_pkey_get_public(file_get_contents($publicPemPath));
}
/**
* step 1
* 生成签名 使用各自语言对应的 SHA256WithRSA签名函数利用 发送方 私钥对待签
* 名字符串进行签名,并进行 Base64编码。
* @param $data
* @return string
* @throws \Exception
*/
public function rsa_sign($data)
{
$signData = $this->_mer_no . $data['method'] . $data['uuid'] . $data['timestamp'] . $this->_ver_no . json_encode($data['body']);
$signature = '';
$res = openssl_sign($signData, $signature, $this->_privateKey, OPENSSL_ALGO_SHA256);
openssl_free_key($this->_privateKey);
if ($res) {
return base64_encode($signature);
} else {
throw new \Exception("请求数据签名错误", 10004);
}
}
/**
* step 2
* 传输数据 加密 AES算法 ECB方式加密 ,密钥长度 128位,块长 128位。
* @param $data
* @return string
* @throws \Exception
*/
public function aes_sign($data)
{
if (!key_exists('timestamp', $data)) {
$data['timestamp'] = now()->toDateTimeString();
}
if (!key_exists('uuid', $data)) {
$data['uuid'] = (string)Str::uuid();
}
if (!key_exists('body', $data)) {
$data['body'] = [];
}
$sign = $this->rsa_sign($data);
$encryptData = [
"method" => $data['method'],
"sign" => $sign,
"nonce_str" => $data['uuid'],
"timestamp" => $data['timestamp'],
"ver_no" => $this->_ver_no,
"body" => $data['body'],
];
return $this->aes_encrypt(json_encode($encryptData));
}
/**
* @param string $resource
* @param string $signature
* @return bool
* @todo :待完善,暂无用
* 字符串验签方式一
*/
public function verifyResData(string $resource, string $signature): bool
{
$signature = base64_decode($signature);
$res = openssl_verify($resource, $signature, $this->_publicKey, OPENSSL_ALGO_SHA256);
//openssl_free_key($this->_publicKey);
return $res === 1;
}
/**
* @param $response
* @return int
* @todo :待完善,暂无用
* 验证签名方式二
*/
public function check_sign($response)
{
$json = json_decode($response, false);
$signature = base64_decode($json->sign);
unset($json->sign);
$data = $json->code . $json->msg . $json->nonce_str . $json->body;
$res = openssl_verify($data, $signature, $this->_publicKey, OPENSSL_ALGO_SHA256);
//dd($data, $signature, $res);
openssl_free_key($this->_publicKey);
return $res === 1;
}
/**
* 解密
* @param $str
* @return false|string
*/
public function aes_decrypt($str)
{
return openssl_decrypt(base64_decode($str), "AES-128-ECB", $this->_aes_key, OPENSSL_RAW_DATA);
}
/**
* 加密字符串
* @param $str
* @return string
*/
public function aes_encrypt($str)
{
return base64_encode(openssl_encrypt($str, "AES-128-ECB", $this->_aes_key, OPENSSL_RAW_DATA));
}
/**
* 接收到的数据经过解密、反系列化以及签名验证后返回希望接收的数据
* @param string $resource
* @return mixed
* @throws \Exception
*/
public function handleRes(string $resource)
{
// step1: aes解密
$decryptRes = $this->aes_decrypt($resource);
// step2: json反系列化
$resData = json_decode($decryptRes, true);
// step3: 验签 #响应签名字符串 code+msg+nonce_str+body
$body = $resData['body'];
$signData = $resData['code'] . $resData['msg'] . $resData['nonce_str'] . $body;
$signature = $resData['sign'];
if ($this->verifyResData($signData, $signature) || $this->check_sign($decryptRes)) {//验签成功
return json_decode($body, true);
} else {
throw new \Exception("接收数据签名错误", 10004);
}
}
}