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);
Last modification:December 29, 2021
如果觉得我的文章对你有用,请随意赞赏