微信第三方平台授权获取component_verify_ticket加解密(laravel+php7)

一、获取component_verify_ticket密文

微信第三方平台在创建审核通过后,第一步就是获取component_verify_ticket。出于安全考虑,微信服务器每隔10分钟会向你的服务器消息接收地址推送一次component_verify_ticket加密数据。该加密数据分为两部分接收,在laravel中,需要使用$request->all()接收解密参数(json字符串),file_get_contents(‘php://input’)接收加密xml字符串。获取这两部分数据后,我们就可以开始解密了。

二、解密component_verify_ticket密文

微信第三方平台官方平台已给出了加解密的demo,但版本太旧,加解密函数在php7中已弃用,其demo(仅支持php7以下)并不能,比较头大。只能结合demo中的加解密算法,重写个支持php7以上的demo(包含加密),其中ErrorCode因项目中不需要,未加入,有需要的可以对照官方demo自行添加。全部代码现分享出来,欢迎吐槽!

<?php

namespace App\Services\WeiXinXmlService;

class WeiXinXmlService
{
    private $token;
    private $encodingAesKey;
    private $appId;
    private $key;

    public function __construct()
    {
        $this->token = 'dup';
        $this->encodingAesKey = 'daeskeyaeskeyaeskeyaeskeyaeskeyaeskeyaeskey';
        $this->appId = 'third_app_id';
        $this->key = base64_decode($this->encodingAesKey . '=');
    }

    //xml格式化
    private function xmlFormat($xml)
    {
        $xmlTree = new \DOMDocument();
        $xmlTree->loadXML($xml);
        $encrypt = trim($xmlTree->getElementsByTagName('Encrypt')->item(0)->nodeValue);
        $format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%s]]></Encrypt></xml>";
        return sprintf($format, $encrypt);
    }

    //xml提取
    private function xmlExtract($xmlText)
    {
        try {
            $xml = new \DOMDocument();
            $xml->loadXML($xmlText);
            return trim($xml->getElementsByTagName('Encrypt')->item(0)->nodeValue);
        } catch (\Exception $e) {
            return false;
        }
    }

    //xml生成
    private function xmlGenerate($encrypt, $signature, $timestamp, $nonce)
    {
        $format = "<xml><Encrypt><![CDATA[%s]]></Encrypt><MsgSignature><![CDATA[%s]]></MsgSignature><TimeStamp>%s</TimeStamp><Nonce><![CDATA[%s]]></Nonce></xml>";
        return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
    }

    //获取随机16位字符串
    private function getRandomStr()
    {
        $str = "";
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max = strlen($strPol) - 1;
        for ($i = 0; $i < 16; $i++) {
            $str .= $strPol[mt_rand(0, $max)];
        }
        return $str;
    }

    //SHA1算法
    private function getSHA1($token, $timestamp, $nonce, $encryptMsg)
    {
        try {
            $array = array($encryptMsg, $token, $timestamp, $nonce);
            sort($array, SORT_STRING);
            $str = implode($array);
            return sha1($str);
        } catch (\Exception $e) {
            return false;
        }
    }

    //解密文本
    private function decodeText($text)
    {
        $pad = ord(substr($text, -1));
        if ($pad < 1 || $pad > 32) {
            $pad = 0;
        }
        return substr($text, 0, (strlen($text) - $pad));
    }

    //加密文本
    private function encodeText($text)
    {
        $blockSize = 32;
        $textLength = strlen($text);
        //计算需要填充的位数
        $amountToPad = $blockSize - ($textLength % $blockSize);
        if ($amountToPad == 0) {
            $amountToPad = $blockSize;
        }
        //获得补位所用的字符
        $padChr = chr($amountToPad);
        $tmp = "";
        for ($index = 0; $index < $amountToPad; $index++) {
            $tmp .= $padChr;
        }
        return $text . $tmp;
    }

    //解密字符串
    private function decryptString($encrypted, $appId)
    {
        try {
            //使用BASE64对需要解密的字符串进行解码
            $ciphertextDec = base64_decode($encrypted);
            $iv = substr($this->key, 0, 16);
            $decrypted = openssl_decrypt($ciphertextDec, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
        } catch (\Exception $e) {
            return false;
        }
        try {
            //去除补位字符
            $result = $this->decodeText($decrypted);
            //去除16位随机字符串,网络字节序和AppId
            if (strlen($result) < 16)
                return '';
            $content = substr($result, 16, strlen($result));
            $lenList = unpack('N', substr($content, 0, 4));
            $xmlLen = $lenList[1];
            $xmlContent = substr($content, 4, $xmlLen);
            $fromAppId = substr($content, $xmlLen + 4);
        } catch (\Exception $e) {
            return false;
        }
        if ($fromAppId != $appId)
            return false;
        return $xmlContent;
    }

    //加密字符串
    public function encryptString($text, $appid)
    {
        try {
            //获得16位随机字符串,填充到明文之前
            $random = $this->getRandomStr();
            $text = $random . pack("N", strlen($text)) . $text . $appid;
            // 网络字节序
            $iv = substr($this->key, 0, 16);
            //使用自定义的填充方式对明文进行补位填充
            $text = $this->encodeText($text);
            $encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
            //使用BASE64对加密后的字符串进行编码
            return base64_encode($encrypted);
        } catch (\Exception $e) {
            return false;
        }
    }

    //解密消息
    private function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData)
    {
        if (strlen($this->encodingAesKey) != 43) {
            return false;
        }
        //提取密文
        $encrypt = $this->xmlExtract($postData);
        if (!$encrypt) {
            return false;
        }
        if ($timestamp == null) {
            $timestamp = time();
        }
        //验证安全签名
        $signature = $this->getSHA1($this->token, $timestamp, $nonce, $encrypt);
        if (!$signature) {
            return false;
        }
        if ($signature != $msgSignature) {
            return false;
        }
        return $this->decryptString($encrypt, $this->appId);
    }

    //加密消息
    private function encryptMsg($replyMsg, $timeStamp, $nonce)
    {
        //加密
        $encrypt = $this->encryptString($replyMsg, $this->appId);
        if (!$encrypt) {
            return false;
        }
        if ($timeStamp == null) {
            $timeStamp = time();
        }
        //生成安全签名
        $signature = $this->getSHA1($this->token, $timeStamp, $nonce, $encrypt);
        if (!$signature) {
            return false;
        }
        //生成发送的xml
        return $this->xmlGenerate($encrypt, $signature, $timeStamp, $nonce);
    }

    //解密消息示例
    public function aesDecode()
    {
        $encryptMsg = '<xml>
	<Encrypt>
		<![CDATA[zThwtRtKUf7GXFmbme724p9xKhxS+VwJJS+JvITuO1z6nCew4tGCvfFYxrIJmnRQF27Cra/mIWspUHNIbBoUJC8ueggAwbF5GCJZMCtZk/v2SspyVzaRExYLzciYi3SjI9JrBSf/rv5igZ8+xIMcS+/ssRYwWXmzYB4KXnIgStYRDbxAQFwWBtNSWQMyqgVhR605JhOl/7sfPj/uHrMv9MvTE/TBeMO3b24+ZHVHaznHv1HzalkpeBViL8t+cEHbHbiGJDwNUG6e7Nd+NTqHM8cs1ENg4cjdem+rXqVV8M2g1xIagfn2DOVJVOc37dP/FpcWG450fiPW7LpJ89vJehQTV6SJNQb3GkJDFW8ECkBph0mqfMxIO3F9OKmna7zJ]]>
	</Encrypt>
	<MsgSignature>
		<![CDATA[82b8e04a5c5a908ff4c228d97344d96e6bbd017c]]>
	</MsgSignature>
	<TimeStamp>1626140909</TimeStamp>
	<Nonce>
		<![CDATA[987390923]]>
	</Nonce>
</xml>';
        $timeStamp = '1626140909';
        $nonce = '987390923';
        $msgSign = '82b8e04a5c5a908ff4c228d97344d96e6bbd017c';
        $fromXml = $this->xmlFormat($encryptMsg);
        return $this->decryptMsg($msgSign, $timeStamp, $nonce, $fromXml);
    }

    //加密消息示例
    public function aesEncode()
    {
        $timeStamp = '1626140909';
        $nonce = '987390923';
        $text = '<xml>
	<AppId>
		<![CDATA[third_app_id]]>
	</AppId>
	<CreateTime>
	    1626140909
	</CreateTime>
	<InfoType>
		<![CDATA[component_verify_ticket]]>
	</InfoType>
	<ComponentVerifyTicket>
		<![CDATA[ticket@@@ticket]]>
	</ComponentVerifyTicket>
</xml>';
        return $this->encryptMsg($text, $timeStamp, $nonce);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值