微信公众号开发——消息体加解密

上篇文章中,我们提到微信消息加解密方式有三种,分别是明文模式、兼容模式和安全模式,选择兼容模式和安全模式,需要在公众号后台配置消息加解密秘钥EncodingAESKey。这篇文章我们来具体说一说在安全模式下微信消息加解密过程。

当用户向公众号发送消息,微信公众账号将会在URL中带上signature、timestamp、nonce、openid、encrypt_type、msg_signature等参数发送到用户服务器,例如:

http://wechat.acier.cn/index.php/Home/Index/index?signature=4fa4a9b8f704650c8b4b7b9b3ed840730fe7a6e5&timestamp=1556418922&nonce=716667816&openid=ou7rj1bzsmvE8SdHhVG0v6bt5m-M&encrypt_type=aes&msg_signature=f7c0ad5059052243822c2916c7aa0e978472f97e

encrypt_type:加密类型。没有该参数或参数为raw时,表示不加密。参数为aes时,表示aes加密。

msg_signature:对消息体的签名。

同时推送如下XML消息,即一个已加密的消息:

<xml>
    <ToUserName><![CDATA[gh_a1535e2cabb5]]></ToUserName>
    <Encrypt><![CDATA[jAU5g+GnBRz7bgWBonVMdibqGwOntnh+s4MXGWVB3jzMUxvkZ1LKOx6XaHEguUGNZLIYBGU4vWFWQ0vsxuJEEd14IcUN1yuUFSQDUE76BldOeTH8dzZ6lUJwWAWCvAfy/YpuNFibR06R7/jnH97WZVa8/kHgFhw6n08SNeEh6v+EzjlNZWSvNTMkbkZZOohpsCEqmvpzjmCC/kwgnZIm2sATDaZIEHZpXcLgpMCBwk3y+NNyIjDzJThXov5hYGIafTjRjYHYolLh+LIXFR9FICu8Qbo2ax+mfdWXeC0bpVWikZk4cIn2j0uRih5rAuoqKOUOhDVvy2XbDEJpslom0z9IcQVxFhn+DlRc0UkhnmAVRoSP8ZTCnZrH4G2QKx5eTP9e3RiVeK4HUR4Gqd6f9H8tYsbuITt34gCEiZxaxkY=]]></Encrypt>
</xml>

在服务器PHP代码中,我们需要获取到timestamp、nonce、encrypt_type和msg_signature,这些参数将用于对消息的加解密。

程序收到微信服务器发送过来的消息后,将进行解密。对AES对称加解密的算法,微信提供了示例代码,点此下载。这里,我将下载的示例代码放在新建的WeChat目录下,并将WeChat目录放进thinkphp的Vender目录里面,然后调用进行解密,解密之后,根据业务流程,要回复给微信服务器一个消息,这时又需要对将要回复的消息进行加密,然后发送给微信服务器。具体流程代码如下:

class IndexController extends ComController
{
    //入口
    public function index()
    {
        define("TOKEN", "acier");
        define("AppID", "wx4abf8a686084d2fd");
        define("EncodingAESKey", "cMhGxj3Bq1Y9vcG93ef1pCWH4AUh2E3zXpLNHiF74CI");
//        $this -> traceHttp();
        $this->logger(' http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].(empty($_SERVER['QUERY_STRING'])?"":("?".$_SERVER['QUERY_STRING'])));
        if (!isset($_GET['echostr'])) {
            $this->responseMsg();
        }else{
            $this->valid();
        }
    }

    //验证签名
    public function valid()
    {
        $echoStr = $_GET["echostr"];
        $signature = $_GET["signature"];
        $timestamp = $_GET["timestamp"];
        $nonce = $_GET["nonce"];
        $token = TOKEN;
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);
        if($tmpStr == $signature){
            echo $echoStr;
            exit;
        }
    }

    //响应消息
    public function responseMsg()
    {
        $timestamp  = $_GET['timestamp'];
        $nonce = $_GET['nonce'];
        $msg_signature  = $_GET['msg_signature'];
        $encrypt_type = (isset($_GET['encrypt_type']) && ($_GET['encrypt_type'] == 'aes')) ? "aes" : "raw";
        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
//        $postStr = file_get_contents("php://input");   //如果是PHP7,则使用本行,注释上面一行
        $this->logger(" postStr \r\n".$postStr);
        if (!empty($postStr)){
            //解密
            if ($encrypt_type == 'aes'){
                Vendor('WeChat.wxBizMsgCrypt');
                $pc = new \WXBizMsgCrypt(TOKEN, EncodingAESKey, AppID);
                $decryptMsg = "";  //解密后的明文
                $errCode = $pc->decryptMsg($msg_signature, $timestamp, $nonce, $postStr, $decryptMsg);
                $postStr = $decryptMsg;
            }
            $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
            $RX_TYPE = trim($postObj->MsgType);

            //消息类型分离
            switch ($RX_TYPE)
            {
                case "event":
                    $result = $this->receiveEvent($postObj);
                    break;
                case "text":
                    if (strstr($postObj->Content, "第三方")){
                        $result = $this->relayPart3("http://discuz.comli.com/test.php".'?'.$_SERVER['QUERY_STRING'], $postStr);
                    }else{
                        $result = $this->receiveText($postObj);
                    }
                    break;
                case "image":
                    $result = $this->receiveImage($postObj);
                    break;
                case "location":
                    $result = $this->receiveLocation($postObj);
                    break;
                case "voice":
                    $result = $this->receiveVoice($postObj);
                    break;
                case "video":
                    $result = $this->receiveVideo($postObj);
                    break;
                case "link":
                    $result = $this->receiveLink($postObj);
                    break;
                default:
                    $result = "unknown msg type: ".$RX_TYPE;
                    break;
            }
            //加密
            if ($encrypt_type == 'aes'){
                $encryptMsg = ''; //加密后的密文
                $errCode = $pc->encryptMsg($result, $timestamp, $nonce, $encryptMsg);
                $result = $encryptMsg;
            }
            echo $result;
        }else {
            echo "";
            exit;
        }
    }

    //日志记录
    private function logger($log_content)
    {
        if(isset($_SERVER['HTTP_APPNAME'])){   //SAE
            sae_set_display_errors(false);
            sae_debug($log_content);
            sae_set_display_errors(true);
        }else if($_SERVER['REMOTE_ADDR'] != "127.0.0.1"){ //LOCAL
            $max_size = 500000;
            $log_filename = "log.xml";
            if(file_exists($log_filename) and (abs(filesize($log_filename)) > $max_size)){unlink($log_filename);}
            file_put_contents($log_filename, date('Y-m-d H:i:s').$log_content."\r\n", FILE_APPEND);
        }
    }
}

以上就是对消息体加解密的演示流程。

发表评论

发表回复

沙发空缺中,还不快抢~