小程序API加密 PHP版本

小程序API加密 PHP版本

在小程序管理后台开启api加密后,开发者需要对原API的请求内容加密与签名,同时API的回包内容需要开发者验签与解密。
https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html

第一步:生成获取所需密钥

  • 登录小程序后台

  • 在左侧菜单点击 开发 > 开发管理 进入开发设置 TAB,在下面找到 API安全一栏,扫码进入生成操作。

  • 对称与非对称加密都点击随机生成,并且在生成后点击校验,并且下载相应的私钥。此处分别可以获取到 AES256_SNAES256_KEYRSA_SNRSA_PUBLIC_KEYRSA_PRIVATE_KEY。(24小时只能修改一次,记得在第一次就保存好 RSA_PRIVATE_KEY再进入的话保存的是公钥)。

  • 点击保存,在此时这一栏可以获取到 CERT_SNCERT_KEY

如此我们就获取到了所有需要的密钥了。

第二步:写入相应的加密类

这里放上我所使用的代码,整理于微信社区:

<?php

/**
 * 封装微信api签名安全类
 */

namespace app\namespace;

use ErrorException;

// composer require phpseclib/phpseclib=2.0.3
use phpseclib\Crypt\RSA;

class ApiAuth
{
    private string $appId;
    private ?string $accessToken;
    private array $aes;
    private array $rsa;
    private array $cert;
    private string $url;


    public function __construct(
        $appid,
        $accessToken,
        $AES256_SN,
        $AES256_KEY,
        $RSA_PRIVATE_SN,
        $RSA_PUBLIC_KEY,
        $RSA_PRIVATE_KEY,
        $CERT_SN,
        $CERT_KEY
    )
    {
        $this->appId = $appid;
        $this->accessToken = $accessToken;
        $this->aes['sn'] = $AES256_SN;
        $this->aes['key'] = $AES256_KEY;
        $this->rsa['sn'] = $RSA_PRIVATE_SN;
        $this->rsa['rsa-public-key'] = $RSA_PUBLIC_KEY;
        $this->rsa['rsa-private-key'] = $RSA_PRIVATE_KEY;
        $this->cert['sn'] = $CERT_SN;
        $this->cert['cert-key'] = $CERT_KEY;
    }

    /**
     * Name:对外方法用于所有微信api的请求方法
     * User: zcw
     * Date: 2023/7/14
     * Time: 9:51
     * @param $url
     * @param $req
     * @return mixed|null
     * @throws \ErrorException
     * @throws \Exception
     */
    public function request($url, $req)
    {
        $accessToken = $this->accessToken;
        $this->url = $url;
        $urls = $url . "?access_token=" . $accessToken;
        //1.数据加密
        $newRe = $this->getRequestParam($url, $req);
        //2.获取签名
        $signature = $this->getSignature($newRe);
        //本地验签 非必需
        $checkLocalSig = $this->checkLocalSignature($newRe, $signature);
        if (!$checkLocalSig) {
            throw new ErrorException('本地验签错误');
        }
        $appId = $this->appId;
        $headerArray = ['Wechatmp-Appid:' . $appId, 'Wechatmp-TimeStamp:' . $newRe['ts'], 'Wechatmp-Signature:' . $signature];
        $data = $this->curlPost($urls, $newRe['reqData'], $headerArray);
        $headers = $this->httpParseHeaders($data['header']);
        $body = json_decode($data['body'], true);
        //请求平台报错
        if (isset($body['errcode'])) {
            throw new ErrorException($body['errmsg']);
        }
        // 3.响应参数验签 目前存在问题
        $verify = $this->verifyResponse($data);
        //4.参数解密
        return $this->jM($headers['Wechatmp-TimeStamp'], $body);
    }

    /**
     * Name:post请求
     * User: zcw
     * Date: 2023/7/14
     * Time: 9:19
     * @param $url
     * @param $field
     * @param $header
     * @return array
     */
    public function curlPost($url, $field, $header): array
    {
        $headerArray = array("Content-type:application/json;charset=utf-8", "Accept:application/json");
        $headerArray = array_merge($headerArray, $header);
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headerArray);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $field);
        //输出响应头部
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, true);
        $str = curl_exec($curl);
        $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $headers = substr($str, 0, $headerSize);
        $body = substr($str, $headerSize);
        curl_close($curl);
        return ['body' => $body, 'header' => $headers];
    }

    /**
     * Name:对外方法用于所有微信api的请求通道
     * User: zcw
     * Date: 2023/7/14
     * Time: 9:21
     * @param $url
     * @param $req
     * @return array
     * @throws \Exception
     */
    public function getRequestParam($url, $req): array
    {
        $key = base64_decode($this->aes['key']);
        $sn = $this->aes['sn'];
        $appId = $this->appId;
        $time = time();
        //16位随机字符
        $nonce = rtrim(base64_encode(random_bytes(16)), '=');
        $addReq = ["_n" => $nonce, "_appid" => $appId, "_timestamp" => $time];
        $realReq = array_merge($addReq, $req);
        $realReq = json_encode($realReq);
        //额外参数
        $aad = $url . "|" . $appId . "|" . $time . "|" . $sn;
        //12位随机字符
        $iv = random_bytes(12);
        $cipher = openssl_encrypt($realReq, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag, $aad);
        $iv = base64_encode($iv);
        $data = base64_encode($cipher);
        $authTag = base64_encode($tag);
        $reqData = ["iv" => $iv, "data" => $data, "authtag" => $authTag];
        //校验本地加密是否正确 非必须
        // $checkParam = $this->checkParam($key, $authTag, $iv, $data, $aad);
        return ['ts' => $time, 'reqData' => json_encode($reqData)];
    }


    /**
     * Name:请求前本地验签
     * User: zcw
     * Date: 2023/7/14
     * Time: 9:57
     * @param $key
     * @param $authTag
     * @param $iv
     * @param $data
     * @param $aad
     * @return false|string
     */
    private function checkParam($key, $authTag, $iv, $data, $aad)
    {
        $iv = base64_decode($iv);
        $data = base64_decode($data);
        $authTag = base64_decode($authTag);
        return openssl_decrypt($data, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad);
    }

    /**
     * Name:获取签名
     * User: zcw
     * Date: 2023/7/14
     * Time: 10:03
     * @param array $newRe
     * @return string
     */
    private function getSignature(array $newRe): string
    {
        $time = $newRe['ts'];
        $key = $this->rsa['rsa-private-key'];
        $url = $this->url;
        $appId = $this->appId;
        $reqData = $newRe['reqData'];
        $payload = "$url\n$appId\n$time\n$reqData";
        $rsa = new RSA();
        $rsa->loadKey($key);
        $rsa->setHash("sha256");
        $rsa->setMGFHash("sha256");
        $signature = $rsa->sign($payload);
        return base64_encode($signature);
    }

    /**
     * Name:请求前本地验签
     * User: zcw
     * Date: 2023/7/14
     * Time: 10:11
     * @param array $newRe
     * @param string $signature
     * @return bool|string
     */
    private function checkLocalSignature(array $newRe, string $signature)
    {
        $signature = base64_decode($signature);
        $rsaPubKey = $this->rsa['rsa-public-key'];
        $appId = $this->appId;
        $url = $this->url;
        $time = $newRe['ts'];
        $reqData = $newRe['reqData'];
        $payload = "$url\n$appId\n$time\n$reqData";
        $payload = utf8_encode($payload);
        $rsa = new RSA();
        $rsa->loadKey($rsaPubKey);
        $rsa->setHash("sha256");
        $rsa->setMGFHash("sha256");
        return $rsa->verify($payload, $signature);
    }

    /**
     * 从证书中提取公钥
     * @param string $cert 证书内容
     * @return string
     */
    private function getPublicKey(string $cert): string
    {
        $pkey = openssl_pkey_get_public($cert);
        $keyData = openssl_pkey_get_details($pkey);
        $public_key = str_replace('-----BEGIN PUBLIC KEY-----', '', $keyData['key']);
        return trim(str_replace('-----END PUBLIC KEY-----', '', $public_key));
    }

    /**
     * Name:解析头部信息
     * User: zcw
     * Date: 2023/7/14
     * Time: 10:28
     * @param $headerString
     * @return array
     */
    private function httpParseHeaders($headerString): array
    {
        $headers = [];
        $lines = explode("\r\n", $headerString);
        foreach ($lines as $line) {
            $line = trim($line);
            if (!empty($line)) {
                $parts = explode(':', $line, 2);
                $key = trim($parts[0]);
                $value = isset($parts[1]) ? trim($parts[1]) : '';
                $headers[$key] = $value;
            }
        }
        return $headers;
    }

    /**
     * Name:解密参数
     * User: zcw
     * Date: 2023/7/14
     * Time: 10:31
     * @param $ts
     * @param $body
     * @return mixed|null
     * @throws ErrorException
     */
    private function jM($ts, $body)
    {
        $url = $this->url;
        $appId = $this->appId;
        $sn = $this->aes['sn'];
        $aad = $url . '|' . $appId . '|' . $ts . '|' . $sn;
        $key = $this->aes['key'];
        $key = base64_decode($key);
        $iv = base64_decode($body['iv']);
        $data = base64_decode($body['data']);
        $authTag = base64_decode($body['authtag']);
        $result = openssl_decrypt($data, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad);
        if (!$result) {
            throw new ErrorException();
        }
        return json_decode($result, true);

    }

    /**
     * Name:验证响应值
     * User: zcw
     * Date: 2023/7/14
     * Time: 11:16
     * @param $data
     * @return bool|string
     * @throws \ErrorException
     */
    private function verifyResponse($data)
    {
        $headers = $this->httpParseHeaders($data['header']);
        $nowTime = time();
        $reTime = $headers['Wechatmp-TimeStamp'];
        $appId = $this->appId;
        $cert = $this->cert;
        $sn = $cert['sn'];
        $key = $cert['cert-key'];
        $url = $this->url;
        if ($appId != $headers['Wechatmp-Appid'] || $nowTime - $reTime > 300) {
            throw new ErrorException('返回值安全字段校验失败');
        }
        if ($sn == $headers['Wechatmp-Serial']) {
            $signature = $headers['Wechatmp-Signature'];
        } elseif ($sn == $headers['Wechatmp-Serial-Deprecated']) {
            $signature = $headers['Wechatmp-Signature-Deprecated'];
        } else {
            throw new ErrorException('返回值sn不匹配');
        }
        $reData = $data['body'];
        $payload = "$url\n$appId\n$reTime\n$reData";
        $payload = utf8_encode($payload);
        $signature = base64_decode($signature);
        $publicKey = $this->getPublicKey($key);
        $rsa = new RSA();
        $rsa->loadKey($publicKey);
        $rsa->setHash("sha256");
        $rsa->setMGFHash("sha256");
        return $rsa->verify($payload, $signature);
    }

}

调用示例

$contents = (new ApiAuth(
        $appid,
        $accessToken,
        $AES256_SN,
        $AES256_KEY,
        $RSA_PRIVATE_SN,
        $RSA_PUBLIC_KEY,
        $RSA_PRIVATE_KEY,
        $CERT_SN,
        $CERT_KEY
    ))->request($api, $params);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 企业微信小程序是一款基于企业微信的应用程序,开发者可以使用php来编写企业微信小程序代码。 首先,为了使用php编写企业微信小程序代码,我们需要安装php环境,并确保其运行正常。然后,可以使用企业微信小程序开发文档中提供的接口,结合php语法编写代码。 例如,我们可以使用php发送请求到企业微信小程序的接口,获取用户信息或者发送消息。可以使用curl库来发送请求,设置请求的url地址、请求方式、请求头和请求参数等。 另外,我们还可以使用php的数据库操作函数来实现企业微信小程序的数据管理功能。例如,连接到企业微信小程序的数据库,执行查询、插入、更新等操作,获取或修改数据。 同时,在编写企业微信小程序代码时,需要注意安全性和用户体验。如对用户传入的参数进行合法性验证、对用户发送的消息进行处理和回复。 在开发过程中,可以参考企业微信小程序开发文档、php官方文档和相关教程,遇到问题可以查阅开发者社区或寻求帮助。 最后,完成代码编写后,可以使用企业微信小程序的开发工具进行调试和发布。可以上传代码包、配置小程序的基本信息、设置权限等等。 总之,使用php编写企业微信小程序代码,可以通过调用接口、操作数据库等实现各种功能,以满足企业微信小程序的需要。 ### 回答2: 企业微信小程序是企业微信提供的一种轻量级应用开发框架,可以基于企业微信的组织结构和权限体系,快速构建适合企业内部使用的小程序。 在企业微信小程序的代码编写中,可以使用 PHP语言来进行后端开发。PHP是一种广泛使用的服务器端脚本语言,特别适合用于Web开发。 在PHP中,可以使用特定的小程序开发框架进行企业微信小程序的代码编写。这些框架提供了各种功能和API接口,使开发者能够方便地与企业微信进行交互和数据处理。 企业微信小程序代码中,PHP可以用来处理用户请求、生成动态页面内容、与后台数据库进行交互等。通过PHP的特性,可以方便地进行数据存储和读取、用户身份验证、数据加密等操作,确保小程序的安全性和稳定性。 在企业微信小程序代码的编写过程中,开发者可以通过PHP语言来实现小程序的业务逻辑和功能,如员工信息查询、打卡记录查看、通知公告发布等。同时,也可以通过PHP框架提供的功能来快速搭建小程序的页面结构和样式,使小程序具有良好的用户体验。 总之,通过使用PHP语言编写企业微信小程序的代码,开发者可以充分利用PHP的优势和功能来构建高效、安全、稳定且具有丰富功能的企业微信小程序。 ### 回答3: 企业微信小程序代码可以使用PHP进行开发。PHP是一种广泛应用于Web开发的脚本语言,它具有简单易学、灵活开放、运行稳定的特点。 在企业微信小程序开发中,可以使用PHP进行后端接口的编写和数据的处理。PHP可以通过与企业微信小程序API进行交互,实现用户的登录验证、数据的获取和存储等功能。 具体来说,使用PHP开发企业微信小程序代码可以包括以下几个步骤: 1. 准备开发环境:安装PHP的开发环境,例如PHP解释器和相关的开发工具。 2. 设计数据库:根据小程序的需求,设计相应的数据库并创建相应的表结构。 3. 编写接口:使用PHP编写与企业微信小程序进行交互的接口,例如用户登录验证、数据的增删改查等。 4. 数据处理:通过PHP的数据库操作函数,对数据库中的数据进行增删改查操作,以满足小程序业务逻辑的需求。 5. 接口测试:使用工具或者调用接口进行测试,确保接口的正确性和稳定性。 总的来说,使用PHP进行企业微信小程序代码的开发可以实现后端逻辑的处理和数据的交互,实现了小程序与服务器之间的数据传输和交互,提供了更丰富的功能和更好的用户体验。同时,PHP具有较低的学习曲线和广泛的应用,使得开发过程更加简单高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值