一、获取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 . '=');
}
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);
}
private function xmlExtract($xmlText)
{
try {
$xml = new \DOMDocument();
$xml->loadXML($xmlText);
return trim($xml->getElementsByTagName('Encrypt')->item(0)->nodeValue);
} catch (\Exception $e) {
return false;
}
}
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);
}
private function getRandomStr()
{
$str = "";
$strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
$max = strlen($strPol) - 1;
for ($i = 0; $i < 16; $i++) {
$str .= $strPol[mt_rand(0, $max)];
}
return $str;
}
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 {
$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);
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 {
$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);
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;
}
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);
}
}
|