zl程序教程

您现在的位置是:首页 >  后端

当前栏目

API签名设计(php版)

PHPAPI 设计 签名
2023-06-13 09:12:24 时间

API签名设计

可变性

  • 每次的签名必须是不一样的。

时效性

  • 每次请求的时效,过期作废等。

唯一性

  • 每次的签名是唯一的。

完整性

  • 能够对传入数据进行验证,防止篡改。

步骤

  • 将所有参数(注意是所有参数),除去sign本身,以及值是空的参数,按参数名字母升序排序。
  • 然后把排序后的参数按参数1值1参数2值2…参数n值n(这里的参数和值必须是传输参数的原始值,不能是经过处理的)的方式拼接成一个字符串。
  • 把分配给接入方的验证密钥key拼接在第2步得到的字符串前面。
  • 在上一步得到的字符串前面加上验证密钥key(这里的密钥key是接口提供方分配给接口接入方的),然后计算md5值,得到32位字符串,然后转成大写。
  • 计算第3步字符串的md5值(32位),然后转成大写,得到的字符串作为sign的值。

RSA公钥密钥生成

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);