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的整合签名代码给予参考。
<?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", "\r\n" ]; $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", "\r\n" ]; $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);
Comment here is closed