API签名设计
可变性
- 每次的签名必须是不一样的。
时效性
- 每次请求的时效,过期作废等。
唯一性
- 每次的签名是唯一的。
完整性
- 能够对传入数据进行验证,防止篡改。
步骤
- 将所有参数(注意是所有参数),除去sign本身,以及值是空的参数,按参数名字母升序排序。
- 然后把排序后的参数按参数1值1参数2值2…参数n值n(这里的参数和值必须是传输参数的原始值,不能是经过处理的)的方式拼接成一个字符串。
- 把分配给接入方的验证密钥key拼接在第2步得到的字符串前面。
- 在上一步得到的字符串前面加上验证密钥key(这里的密钥key是接口提供方分配给接口接入方的),然后计算md5值,得到32位字符串,然后转成大写。
- 计算第3步字符串的md5值(32位),然后转成大写,得到的字符串作为sign的值。
RSA公钥密钥生成
- 使用支付宝的在线加密
- OpenSSL windows下载
- 其他参考
OpenSSL> genrsa -out rsa_private_key.pem 2048 #生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥
OpenSSL> exit #退出OpenSSL程序
#rsa_public_key.pem 和 rsa_private_key.pem 即为所需
下面给出一套RSA和md5的整合签名代码给予参考。
代码语言:javascript复制<?php
class Sign
{
protected $md5Key = 'c4ca4238a0b923820dcc509a6f75849b';//公钥
protected $md5secret = '28c8edde3d61a0411511d3b1866f0636';//私钥
protected $md5invalid = 600;
protected $publicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiSvrdwvEjgeh /sgY QPowx rE/Ou17yM4iFAnQEugi9MqrRX x Y0PTBoenqmH qis79LaS4R3BL6Fi2F83EQBIDk38RDrzDYrlpTCbKg4iPCPCDOolxSNlF4xs9z2feb2QQmYcJ7H/QISabCnEV/U9TJK bUlMGySQ5vpwImKioTvQ/R4vMwNi0R3NZH6IwTPukgs0wIlIEQ/SHH7ZurMobfaXHkqQLgFdH38MDsSfoLYunWtfCnxsbQa/z5qPDcWbvAY569UjcMPUz3Me4oOo2VMpOgVYFJRe3KhBpC4vx5NSHxBAHXrb6uO5j5kmEFIRMpW4PXONQnmrpN93ZwIDAQAB';
protected $privateKey = 'MIIEogIBAAKCAQEAiSvrdwvEjgeh /sgY QPowx rE/Ou17yM4iFAnQEugi9MqrRX x Y0PTBoenqmH qis79LaS4R3BL6Fi2F83EQBIDk38RDrzDYrlpTCbKg4iPCPCDOolxSNlF4xs9z2feb2QQmYcJ7H/QISabCnEV/U9TJK bUlMGySQ5vpwImKioTvQ/R4vMwNi0R3NZH6IwTPukgs0wIlIEQ/SHH7ZurMobfaXHkqQLgFdH38MDsSfoLYunWtfCnxsbQa/z5qPDcWbvAY569UjcMPUz3Me4oOo2VMpOgVYFJRe3KhBpC4vx5NSHxBAHXrb6uO5j5kmEFIRMpW4PXONQnmrpN93ZwIDAQABAoIBAC0a4gx9NB63583h39646WNmAmlKvOHj8KR9aa9K0xsRMJVukfaG33BopwVoqfteyczO9qIbPuUDUbkFymj3tjXC7 60OhV9hNqZJ7ZP61XC3AMGhxKUE NlJiK LD6IZt4zNTKAPRXYc SVNeoHOebqX0PEpRVumrX6KiOpiiHj6l8AMTih6/KjMu53cqhPCbG FHrlAUy9GWD/ J2DsIPUAZGhFfNt/coXOO32XgHwS90lrn2a0K4c72pwM37tjMjeddOWR9HjtY/PW4oRHJhTsmAWUzHH1oOt06sHBgfVB6X7SaFzOITgIU7sClbwwNUCzF3DaqO6MuqldX4fiFECgYEA7mBn7KlXJ6EuB5XjUib2kLZD6SV0Oe9/Bd3wWMMJHfCPBNSDdUOVYrEndc9rq4yvEpVnvJ5loMASAVbUwphp7mOpv3sl1asJmJFRGndtspHs3TgkfmyThMMTGl7UHSKyWo/SpJXlPlncfnavUh07emwIZF4hPUqQAkP6/0E8zo0CgYEAk1AS9/dFcVY1j3sQRclCeYS0H3O/S8P30gigWIVi2FRc9AnwfPZ7eiHd lprSNdBaW 9ZxlzSpMlSTztL0kXcmCn6N5lO8o qO7ti1sgNw3xQp2iSRsBKH/CU52cDztJtUw yy2LWUpAX5p82mdIB2ymOP/jc8697N6zgtcXKsMCgYAE/YevcKwebEVma0DjC2XGCcrKKrqQK 9g1BCgCxU5xzt3QmuuHMgX1NWapcj/Qma34ODXFgnSn7LAzGyP1lkBYJzBIXbdTkNZKlGkWDO3tU5cIzzAWM2NzfesaafPJFbPhotGXsz5zS/MhfeNpIcGPRS/5SiU af5YRvq5H2UQKBgHXbnaF32r4ne iUS9uZfq6cRkPXphfm7JHExwyrgv6S2F CyD4iMX3wNJmE18rKNRI3DPC8guoKOc2TiivHrZOb0xrTO2kPkPw1VCWnPWnupLRoS5tzmISfWojtUxs4kusS2jZR9Of2KPSUNAnEkfMmsQJvb7mKkZc QZ6PmYBjAoGAN0EGeGV1nMfHnzqsjUt0bEK5SNAztoLeLt8Z1FvFk5nPSdOIsNCr9X7BtvPtvHEmdocevOZ aJO4rIH2N4SkkHLzXevZnDzmoq5NVRNdUE5/zHHRf56NwNbqZdDz3Wfkwyx hMBZROKt K aqi Vbj/hpKjYbqycmMgdcV5rxo4=';
protected $tag;
const MD5 = 'md5';
const RSA = 'rsa';
const RSA2 = 'rsa2';
public function __construct($tag = 'md5')
{
$this->tag = strtolower($tag);
}
/**
* @param array $data
* @return array|mixed|string
*/
public function makeSign($data = [])
{
if (empty($data)) static::message(['code' => 202, 'msg' => '签名数据为空!']);
$data['timestamp'] = time();
$data['ip'] = static::getClientIp();
switch ($this->tag) {
case self::MD5:
$data['key'] = $this->md5Key;
$this->message(['code' => 200, 'msg' => '签名成功!', 'data' => $this->makeMd5Sign($data)]);
break;
case self::RSA:
case self::RSA2:
$this->message(['code' => 200, 'msg' => '签名成功!', 'data' => $this->makeRsaSign($data)]);
break;
default:
$this->message(['code' => 202, 'msg' => 'tag错误!']);
}
}
/**
* @param array $data
*/
public function verifySign($data = [])
{
if (empty($data)) static::message(['code' => 203, 'msg' => '验签数据为空!']);
if (!isset($data['sign']) || !$data['sign']) static::message(['code' => 204, 'msg' => '数据签名不存在!']);
if (!isset($data['timestamp']) || !$data['timestamp']) static::message(['code' => 205, 'msg' => '发送的数据参数不合法!']);
if (time() - $data['timestamp'] > $this->md5invalid) static::message(['code' => 207, 'msg' => '验证失效, 请重新发送请求!']);
switch ($this->tag) {
case self::MD5:
$this->verifyMd5Sign($data);
break;
case self::RSA:
case self::RSA2:
$this->verifyRsaSign($data);
break;
default:
$this->message(['code' => 202, 'msg' => 'tag错误!']);
}
}
public function makeRsaSign($data)
{
if (isset($data['sign'])) unset($data['sign']);
ksort($data);
$params = urldecode(http_build_query($data));
$search = [
"-----BEGIN RSA PRIVATE KEY-----",
"-----END RSA PRIVATE KEY-----",
"n",
"r",
"rn"
];
$privateKey = str_replace($search, "", $this->privateKey);
$privateKey = $search[0] . PHP_EOL . wordwrap($privateKey, 64, "n", true) . PHP_EOL . $search[1];
$res = openssl_get_privatekey($privateKey);
if ($res) {
if (self::RSA == $this->tag) {
openssl_sign($params, $sign, $res);
} else {
openssl_sign($params, $sign, $res, OPENSSL_ALGO_SHA256);
}
openssl_free_key($res);
} else {
static::message(['code' => 300, 'msg' => '私钥格式有误!']);
exit;
}
$data['sign'] = base64_encode($sign);
return $data;
}
public function verifyRsaSign($data)
{
if (!isset($data['sign'])) $this->message(['code' => 301, 'msg' => 'sign不存在!']);
$sign = $data['sign'];
unset($data['sign']);
ksort($data);
$params = urldecode(http_build_query($data));
$search = [
"-----BEGIN PUBLIC KEY-----",
"-----END PUBLIC KEY-----",
"n",
"r",
"rn"
];
$publicKey = str_replace($search, "", $this->publicKey);
$publicKey = $search[0] . PHP_EOL . wordwrap($publicKey, 64, "n", true) . PHP_EOL . $search[1];
$res = openssl_get_publickey($publicKey);
if ($res) {
if (self::RSA == $this->tag) {
$result = (bool)openssl_verify($params, base64_decode($sign), $res);
} else {
$result = (bool)openssl_verify($params, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
}
openssl_free_key($res);
} else {
static::message(['code' => 300, 'msg' => '公钥格式有误!']);
exit;
}
if ($result) {
static::message(['code' => 200, 'msg' => '验签成功!']);
}
static::message(['code' => 300, 'msg' => '验签失败!']);
}
/**
* @param array $data
* @return array|mixed
*/
protected function makeMd5Sign($data = [])
{
ksort($data);
$params = http_build_query($data);
$data['sign'] = md5($params . $this->md5secret);
unset($data['key']);
unset($data['ip']);
return $data;
}
/**
* @param array $data
*/
protected function verifyMd5Sign($data = [])
{
$sign = $data['sign'];
unset($data['sign']);
$data['ip'] = $this->getClientIp();
$data['key'] = $this->md5Key;
ksort($data);
$params = http_build_query($data);
if ($sign !== md5($params . $this->md5secret)) static::message(['code' => 208, 'msg' => '验签错误!']);
$this->message(['code' => 200, 'msg' => '验签通过!']);
}
/**
* @param array $msg
*/
static public function message($msg = [])
{
echo json_encode($msg);
exit();
}
/**
* @param int $type
* @return mixed
*/
protected function getClientIp($type = 0)
{
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if ($_SERVER['HTTP_X_REAL_IP']) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown', $arr);
if (false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
$long = sprintf("%u", ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}
}
//MD5签名
//以用户提交抢购商品为例
$data = [
'username' => '17521181231',
'sex' => 1,
'age' => 18,
'address' => '上海徐汇区xx'
];
$data = (new Sign())->makeSign($data);
//MD5验签
$data = [
'address' => '上海徐汇区xx',
'age' => 18,
'sex' => 1,
'timestamp' => 1640770444,
'username' => '17521181231',
'sign' => '28f62f3803d684b000a7efa9ef2a1f7c'
];
$result = (new Sign())->verifySign($data);
//RSA或者RSA2
$data = [
'username' => '17521181231',
'sex' => 1,
'age' => 18,
'address' => '上海徐汇区xx'
];
//RSA签名
$data = (new Sign('rsa'))->makeSign($data);
$data = [
'address' => '上海徐汇区xx',
'age' => 18,
'sex' => 1,
'timestamp' => 1640770444,
'username' => '17521181231',
'sign' => 'IucOoBdmubOtkWGKvz3OshVINXi0EjX6LsTPxddzvy4iu/RMTQVkvR22b3IY0VyUcJjOPjM5ZO7pwMv7XZEDahCcuHq2oCwVeGchVYB9MzGC2swvaFjpaJS5qa0LMb8wpwgo2wqqx7wO1nG 94Oxwp7S5 ko97YwF3 C7298raldLDFyUj8fKD1nEbhdRUfTcFmOH5JwiETLkd uLkNexM/39y9N4z3YfqUfTwEivvybbVL8EIrkxOjdjZ9sc7lAUaUoiGvjScRsTF0GduDPDr1dYV6KagE6/CRYSuI6WMFJfmtRO/GvIaGjc/ha9 CIg60/Xshh0ntF2E3O1HOd5g=='
];
$result = (new Sign('rsa'))->verifySign($data);
//RSA2签名
$data = (new Sign('rsa2'))->makeSign($data);
$data = [
'address' => '上海徐汇区xx',
'age' => 18,
'sex' => 1,
'timestamp' => 1640770444,
'username' => '17521181231',
'sign' => 'IucOoBdmubOtkWGKvz3OshVINXi0EjX6LsTPxddzvy4iu/RMTQVkvR22b3IY0VyUcJjOPjM5ZO7pwMv7XZEDahCcuHq2oCwVeGchVYB9MzGC2swvaFjpaJS5qa0LMb8wpwgo2wqqx7wO1nG 94Oxwp7S5 ko97YwF3 C7298raldLDFyUj8fKD1nEbhdRUfTcFmOH5JwiETLkd uLkNexM/39y9N4z3YfqUfTwEivvybbVL8EIrkxOjdjZ9sc7lAUaUoiGvjScRsTF0GduDPDr1dYV6KagE6/CRYSuI6WMFJfmtRO/GvIaGjc/ha9 CIg60/Xshh0ntF2E3O1HOd5g=='
];
//RSA2验签
$result = (new Sign('rsa2'))->verifySign($data);