上海市食药监局对外与第三方网络订餐平台之间数据交换的实现

  最近工作中有个笑脸需求。关于上海市食药监局对于餐厅食品安全评级的。简单些就是,第三方外卖公司等,需要调用他们的接口,获取餐厅评级,在自家网站上展示出来。大概是下面过程:

1)  提交第三方认证信息,取得Token;

2)  取得Token成功后,加入到Authorization header进行正常的业务接口调用;

3)  Token还在有效期内,继续上述步骤,如果Token已失效,回到步骤1。

看起来是个挺简单的,发送自己的认证信息,调接口获取就ok。可是仔细看看他们的文档,提交的第三方认证信息需要一个加密过程。对php加密不是很了解,所以犯难了。

先看看第三方认证信息吧。

名称

说明

id

第三方标识

name

第三方名称

password

密码

website

第三方网站地址

这些都是公司相关认证信息。对以上信息需要进行如下加密:

1)   用使用者信息,及随机生成的GUID组成的JSON字符串作明文

例:

{

“id”:”xx”,

“name”:”XX”,

“website”:”www.XX.com”,

“gkey”:”379043FD-7335-40F6-874D-9FCFC6F84B42”

}

2)   用AES方法加密上述明文

AES加密时的参数选择,加密模式:CBC;填充模式:PKCS7。

加密时用的IV与密钥是基于当前日期、密码及一些使用者信息生成。

初始化向量(IV)通过如下方式得到:

  1. 使用者标识、使用者名称直接拼成字符串;
  2. 对上一步的字符串进行UTF8编码得到一个byte数组;
  3. byte数组如果大于16位,截取前16位作为IV;如果不满16位则循环填充至16位作为IV

密钥由下面方法得到:

  1. 使用者密码、网站地址及当前日期的yyyyMMdd格式字符串直接拼成一个字符串;
  2. 对上一步的字符串进行UTF8编码得到一个byte数组;
  3. byte数组如果大于32位,截取前32位作为密钥;如果不满32位则循环填充至32位作为密钥

3)   上一步加密得到的byte数组转成base64编码的字符串

4)        字符串进行URL编码

网上关于AES的加密代码很多,可没有接触过php的mcrypt加密的我来说,还是遇到了一些小坑。现在详细说下实现方式。

第一步。是要加密的明文。没有用到过GUID的小伙伴,可能会困惑。其实只是一个随机字符串。生成方式如下:

    public function createGuid() {
        $charid = md5(uniqid(mt_rand(), true));
        $hyphen = chr(45);// "-"
        $uuid = substr($charid, 0, 8).$hyphen
        .substr($charid, 8, 4).$hyphen
        .substr($charid,12, 4).$hyphen
        .substr($charid,16, 4).$hyphen
        .substr($charid,20,12);
        return $uuid;
    }
View Code

第二步。关于这个byte数组让人很困惑,莫非要转成byte数组?但是在使用mcrypt加密的时候发现要求传入字符串,后来食药监局的技术人员耐心讲解道:加密的底层实现都是utf8的byte数组。所以只要传utf8的字符串就可以了,至于byte数组,应该是mcrypt内部实现的,不用管了。生成初始化向量和密匙的第三条里的填充用pcks7方式填充。关于pcks7填充,是怎么个填充,看代码,一目了然。

    function pkcs7padding($source){
        $source = trim($source);
        $block = mcrypt_get_block_size('rijndael-128', 'cbc');
        $pad = $block - (strlen($source) % $block);
        if ($pad <= $block) {
            $char = chr($pad);
            $source .= str_repeat($char, $pad);
        }
        return $source;
    }
View Code

到这里似乎该加密了。带着激动的心情发过去,不出意料的,不对。原来,这里所谓pcks7填充模式,不仅是填充初始化向量和密匙,加密的明文也要填充滴。

下面是整体的代码:

class AesCrypterClass {
  const URL="http://spzf.smda.gov.cn/";
  static $companyInfo = array('id'=>'xx',
    'name'=>'xx',
    'website'=>'www.xx.cn',
    'password'=>'xx'
  ); 

  private $algorithm;
  private $mode;
  public function __construct($algorithm = MCRYPT_RIJNDAEL_128,$mode = MCRYPT_MODE_CBC) {
    $this->algorithm = $algorithm;
    $this->mode = $mode;
  }
  /**
  *生成validate验证字符串
  *AES加密,加密模式:CBC;填充模式:PKCS7。
  * @since 2015-11-16
  * @param array $orig_data 参数说明
  * string id 第三方标识
  * string name 第三方名称
  * string password 密码 
  * string website 第三方网站地址
  * @return string 
  */
  public function createValidate() {
    $orig_data = self::$companyInfo;
    $iv = $this->getIvParameter(array('id'=>$orig_data['id'],'name'=>$orig_data['name']));
    $key = $this->getKey(array('password'=>$orig_data['password'],'website'=>$orig_data['website']));
    $guid = $this->createGuid();
    $data = array('id'=>$orig_data['id'],
    'name'=>$orig_data['name'],
    'website'=>$orig_data['website'],
    'gkey'=>$guid
    );
    $jsonData = json_encode($data, JSON_UNESCAPED_UNICODE);
    $jsonData = $this->pkcs7padding($jsonData);
    $encrypted = mcrypt_encrypt($this->algorithm, $key, $jsonData, $this->mode, $iv);
    $encryptText = urlencode(base64_encode($encrypted));
    return $encryptText;
  }
  /**
  *pkcs7填充
  * @since 2015-11-16
  */
  function pkcs7padding($source){
    $source = trim($source);
    $block = mcrypt_get_block_size('rijndael-128', 'cbc');
    $pad = $block - (strlen($source) % $block);
    if ($pad <= $block) {
      $char = chr($pad);
      $source .= str_repeat($char, $pad);
    }
    return $source;
  }
  /**
  *字符串转换为utf8编码方式
  * @since 2015-11-16
  */
  public function utf8str($str)
  { 
    $encode = mb_detect_encoding( $str, array('ASCII','UTF-8','GB2312','GBK'));
    if ( $encode !='UTF-8' ){
      $str = iconv($encode,'UTF-8',$str);
    }
    return $str;
  }
    /**
    *生成初始化向量IV
    * @since 2015-11-16
    * @param array $args 参数说明
    * string id 第三方标识
    * string name 第三方名称
    * @return string 
    */
    public function getIvParameter($args){
        $str = $args['id'].$args['name'];
        $utf8str = $this->utf8str($str);
        if(strlen($utf8str)<16){
            $iv = $this->pkcs7padding($utf8str, 16);
        }else{
            $iv = substr($utf8str, 0, 16);
        }
        return $iv;
    }
    /**
    *生成密匙
    * @since 2015-11-16
    * @param array $args 参数说明
    * string password 密码 
    * string website 第三方网站地址
    * @return string 
    */
    public function getKey($args){
        $str = $args['password'].$args['website'].date('Ymd');
        $utf8str = $this->utf8str($str);
        if(strlen($utf8str)<32){
        $key = $this->pkcs7padding($utf8str, 32);
        }else{
            $key = substr($utf8str, 0, 32);
        }
        return $key;
    }
    /**
    *生成随机数GUID
    * @since 2015-11-16
    */
    public function createGuid() {
        $charid = md5(uniqid(mt_rand(), true));
        $hyphen = chr(45);// "-"
        $uuid = substr($charid, 0, 8).$hyphen
        .substr($charid, 8, 4).$hyphen
        .substr($charid,12, 4).$hyphen
        .substr($charid,16, 4).$hyphen
        .substr($charid,20,12);
        return $uuid;
    }
    /**
    *获取Token
    * @since 2015-11-16
    * @param array $args 参数说明
    * string id 第三方标识 
    * string validate 验证字符串
    * @return string 
    */
    public function getToken($args){
        $url = self::URL.'api/user/authenticate?        id='.$args['id'].'&validate='.$args['validate'];
        $ch = curl_init ();
        curl_setopt ( $ch, CURLOPT_URL, $url);
        curl_setopt ( $ch, CURLOPT_HEADER, 0 );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
        $return = curl_exec ( $ch );
        $httpCode= curl_getinfo($ch,CURLINFO_HTTP_CODE);
        //403错误可能由于8小时内重新拉取token导致,加reCheck参数重新获取
        if($httpCode=='403'){
            $url = $url.'&reCheck=1';
            curl_setopt ( $ch, CURLOPT_URL, $url);
            $return = curl_exec ( $ch );
            $httpCode= curl_getinfo($ch,CURLINFO_HTTP_CODE);
        }
        curl_close ( $ch ); 
        return array('httpCode'=>$httpCode,'token'=>$return);
    }
    /**
    *获取餐厅分级信息
    * @since 2015-11-16
    * @param array $args 参数说明
    * string id 餐厅许可证号 
    * string token 验证token
    * @return array
    * License string 许可证号
    * OrgName string 单位名称
    * CheckDate date 检查日期
    * CheckResult string 检查结果,为良好、一般、较差
    * Signage string 店招,会有无效或无意义的值,仅供参考
    * Address string 地址
    */
    public function getRestaurantGradeInfo($args){
        $url = self::URL.'api/supervise/'.$args['id'];
        $headerArr = array("HOST:{$args['host']}","Authorization:FY     {$args['token']}");
        $ch = curl_init ();
        curl_setopt ( $ch, CURLOPT_URL, $url);
        curl_setopt ( $ch, CURLOPT_HEADER, 0 );
        curl_setopt ( $ch, CURLOPT_HTTPHEADER, $headerArr);
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
        $return = curl_exec ( $ch );
        $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        curl_close ( $ch ); 
        return array('httpCode'=>$httpCode,'gradeItem'=>$return);
    }

}                                                                                                         
View Code
原文地址:https://www.cnblogs.com/tomorrowdemo/p/5001312.html