收藏
回答

开发者服务器接受消息推送回包报错?

 $reply = generate_reply_message("success", $msg); // 生成回复消息
            $encryptMsg = '';
            $errCode = $pc->encryptMsg($reply, $nonce, $encryptMsg, $timeStamp); // 加密并生成 XML


    
 // 生成回复消息
function generate_reply_message($content, $receivedMsg) {
    $xml = simplexml_load_string($receivedMsg, 'SimpleXMLElement', LIBXML_NOCDATA);
    $toUserName = $xml->FromUserName;
    $fromUserName = $xml->ToUserName;
    $content = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); // 转义内容


    return "<xml>
        <ToUserName><![CDATA[$toUserName]]></ToUserName>
        <FromUserName><![CDATA[$fromUserName]]></FromUserName>
        <CreateTime>" . time() . "</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[$content]]></Content>
    </xml>";
}
    public function encryptMsg($replyMsg, $nonce, &$encryptMsg, $timeStamp)
    {
        $logFilePath = __DIR__ . ''; // 日志文件路径
        //error_log("Starting message encryption...\n", 3, $logFilePath);
    
        // 记录加密前的关键参数
        //error_log("Using EncodingAESKey: " . substr($this->encodingAesKey, 0, 10) . "...\n", 3, $logFilePath);
        //error_log("Using AppId: $this->appId\n", 3,$logFilePath);
        //error_log("Reply message to encrypt: $replyMsg\n", 3, $logFilePath);


        $pc = new Prpcrypt($this->encodingAesKey);
    
        // 调用加密函数
        $array = $pc->encrypt($replyMsg, $this->appId);
        $ret = $array[0];
        if ($ret != 0) {
            error_log("Encryption failed. Error Code: $ret\n", 3, $logFilePath);
            return $ret;
        }
    
        // 获取加密后的密文
        $encrypt = base64_encode($array[1]);
        //error_log("Encrypted message: $encrypt\n", 3, $logFilePath);      
        // 加密完成后处理 Encrypt
        //$encrypt = str_replace(['+', '/'], ['-', '_'], $encrypt);
        //error_log("Encrypted message (URL Safe): $encrypt\n", 3, $logFilePath);


    
        // 如果时间戳为 null,则生成当前时间戳
        if ($timeStamp == null) {
            $timeStamp = time();
        }
        //error_log("TimeStamp used for signing: $timeStamp\n", 3, $logFilePath);
    
        // 生成安全签名
        $sha1 = new SHA1;
    
        // 记录参与签名的参数
        //error_log("Signing parameters:\n", 3, $logFilePath);
        //error_log("Token: $this->token\n", 3, $logFilePath);
        //error_log("TimeStamp: $timeStamp\n", 3, $logFilePath);
        //error_log("Nonce: $nonce\n", 3, $logFilePath);
        //error_log("Encrypt: $encrypt\n", 3, $logFilePath);
    
        // 调用签名生成
        $array = $sha1->getSHA1($this->token, $timeStamp, $nonce, $encrypt);
        $ret = $array[0];
        if ($ret != 0) {
            error_log("Signature generation failed. Error Code: $ret\n", 3, $logFilePath);
            return $ret;
        }
    
        $signature = $array[1];
        //error_log("Generated signature: $signature\n", 3, $logFilePath);



    
        // 生成发送的 XML
        $xmlparse = new XMLParse;


        $encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce);
        //error_log("Generated reply XML: $encryptMsg\n", 3, $logFilePath);
        //error_log(str_repeat("*", 20) . "\n", 3, $logFilePath);
    
        return ErrorCode::$OK;
    }

public function encrypt($text, $appid)
     {
         $logFilePath = __DIR__ . ''; // 日志文件路径
         try {
             //error_log("Encrypt function called.\n", 3, $logFilePath);


            
             // 检查 AppId 是否为空
             if (empty($appid)) {
                 error_log("AppId is empty. Please check the configuration or input.\n", 3, $logFilePath);
                 throw new Exception("AppId cannot be empty.");
             }
             //error_log("AppId for Encryption: $appid\n", 3, $logFilePath);
     
             // 1. 生成 16 字节随机字符串,填充到明文之前
             $random = openssl_random_pseudo_bytes(16);
             $text = $random . pack("N", strlen($text)) . $text . $appid;
             error_log("FullStr: " . bin2hex($text) . "\n", 3, $logFilePath);
     
             // 2. 验证 AES 密钥长度
             if (strlen($this->key) != 32) {
                 error_log("Invalid AES Key: Length is not 32.\n", 3, $logFilePath);
                 throw new Exception("Invalid AES Key: Length must be 32 bytes.");
             }      
                 
             // 3. 使用自定义的填充方式对明文进行补位填充
             $pkc_encoder = new PKCS7Encoder();
             $text = $pkc_encoder->encode($text);
             //error_log("Padded text length: " . strlen($text) . "\n", 3, $logFilePath);
     
             // 4. 使用密钥的前 16 字节作为 IV
             //$iv = substr($this->key, 0, 16);
             $iv = $random; //openssl_random_pseudo_bytes(16);
             //error_log("Generated IV(Hex): " . bin2hex($iv) . "\n", 3, $logFilePath);
     
             // 5. 使用 openssl_encrypt 进行 AES 加密
             $encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
             // 记录加密前的关键参数
             //error_log("AESKey for Encrytion: " . bin2hex(substr($this->key, 0, 16)) . " Length: " . strlen($this->key) . "\n",  3, $this->logFilePath);
             
             if ($encrypted === false) {
                 error_log("Error: openssl_encrypt failed.\n", 3, $logFilePath);
                 throw new Exception("Encryption failed");
             }
     
             // 6. 输出日志
             //error_log("Final Encrypted Text: " . base64_encode($iv . $encrypted) . "\n", 3, $logFilePath);
     
             // 7. 返回加密结果(密文和 IV)
             return array(ErrorCode::$OK, $iv . $encrypted);


         } catch (Exception $e) {
             // 捕获异常并记录日志
             error_log("Encryption Exception: " . $e->getMessage() . "\n", 3, $logFilePath);
             return array(ErrorCode::$EncryptAESError, null);
         }
     }

    public function generate($encrypt, $signature, $timestamp, $nonce)
    {
        // 使用 htmlspecialchars 进行 XML 转义
        $format = "<xml>
<Encrypt><![CDATA[%s]]></Encrypt>
<MsgSignature><![CDATA[%s]]></MsgSignature>
<TimeStamp>%s</TimeStamp>
<Nonce><![CDATA[%s]]></Nonce>
</xml>";
        return sprintf(
            $format,
            htmlspecialchars($encrypt, ENT_QUOTES, 'UTF-8'),
            htmlspecialchars($signature, ENT_QUOTES, 'UTF-8'),
            $timestamp,
            htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8')
        );


        // 记录日志
        //error_log("XMLParse::generate called. Resulting XML:\n$result\n", 3, __DIR__ . '/wechat_received.log');
    }
}
    public function getSHA1($token, $timestamp, $nonce, $encrypt_msg)
    {
        $logFilePath = __DIR__ . ''; // 日志文件路径
    
        // 参数校验
        if (empty($token) || empty($timestamp) || empty($nonce) || empty($encrypt_msg)) {
            error_log("Error: Invalid parameters for SHA1 generation.\n", 3, $logFilePath);
            return array(ErrorCode::$InvalidParameter, null);
        }
    
        try {    
            
            // 强制转为字符串(即使输入是数字)
            $timestamp = (string)$timestamp;
            $nonce = (string)$nonce;
            $token = (string)$token;
            $encrypt_msg = (string)$encrypt_msg;
            
            // 排序
            //error_log("Signature parameters: \n Token: $token, TimeStamp: $timestamp, Nonce: $nonce, Encrypt: $encrypt_msg\n", 3, $logFilePath);
            $array = [$token, $timestamp, $nonce, $encrypt_msg];
            sort($array, SORT_STRING);
            //error_log("Sorted Parameters: " . print_r($array, true), 3, $logFilePath);
    
            // 拼接字符串
            $str = implode('', $array);
            //error_log("String for SHA1: " . $str . "\n", 3, $logFilePath);
    
            // 计算 SHA1
            $signature = sha1($str);
            //error_log("Expected Signature: 097a5aa62b20586aa87daecd54e5659c477c2232", 3, $logFilePath);
            //error_log("Actual Signature: $signature" . "\n", 3, $logFilePath);    
    
            return array(ErrorCode::$OK, $signature);
        } catch (Exception $e) {
            // 异常处理
            error_log("SHA1 generation failed: " . $e->getMessage() . "\n", 3, $logFilePath);
            return array(ErrorCode::$ComputeSignatureError, null);
        }
    }

//以上是关键代码,基于官方文档修改,部分方法配合PHP版本有升级。接口测试时显示解码错误。根据官方文档推测服务器解码方式,用以下python模拟测试可以正确解码,但服务器上不能通过,因为无法知晓微信服务器的解码机制,所以实在看不出从哪里修改。

import base64
import hashlib
from Crypto.Cipher import AES
import struct


def verify_signature(token, encrypt, timestamp, nonce, msg_signature):
    """
    验证消息签名是否正确
    """
    # 按字典序排序参数值
    params = sorted([encrypt, nonce, timestamp, token])
    # 拼接字符串
    sorted_str = ''.join(params)
    # 计算SHA1
    sha1 = hashlib.sha1()
    sha1.update(sorted_str.encode('utf-8'))


    calculated_signature = sha1.hexdigest()
    
    print(f"[签名验证] 计算签名: {calculated_signature}")
    print(f"[签名验证] 收到签名: {msg_signature}")
    return calculated_signature == msg_signature


def decrypt_message(encoding_aes_key, encrypt):
    """
    执行AES解密并解析明文
    """
    try:
        # 1. 补全并解码AESKey
        aes_key = base64.b64decode(encoding_aes_key + "==")
        if len(aes_key) != 32:
            print(f"[错误] AESKey长度不正确: {len(aes_key)}字节,应为32字节")
            return None
            
        # 2. Base64解码加密数据(处理URL安全字符)
        encrypt = encrypt.replace('-', '+').replace('_', '/')
        encrypted_data = base64.b64decode(encrypt)
        print(f"[解密] Base64解码后数据长度: {len(encrypted_data)}字节")
        
        # 3. 提取IV(前16字节)
        iv = encrypted_data[:16]
        ciphertext = encrypted_data[16:]
        print(f"[解密] IV(HEX): {iv.hex()}")
        
        # 4. AES解密
        cipher = AES.new(aes_key, AES.MODE_CBC, iv=iv)
        decrypted = cipher.decrypt(ciphertext)
        
        # 5. 去除PKCS7填充
        pad = decrypted[-1]
        decrypted = decrypted[:-pad]
        print(f"[解密] 去除填充后长度: {len(decrypted)}字节")
        
        # 6. 解析明文结构
        random = decrypted[:16]
        msg_len = struct.unpack('>I', decrypted[16:20])[0]  # 大端序解包
        msg = decrypted[20:20+msg_len].decode('utf-8')
        appid = decrypted[20+msg_len:].decode('utf-8')
        
        print("\n======= 解密结果 =======")
        print(f"Random(HEX): {random.hex()}")
        print(f"消息长度: {msg_len}字节")
        print(f"消息内容: {msg}")
        print(f"AppID: {appid}")
        print("=======================")
        return msg
        
    except Exception as e:
        print(f"[错误] 解密过程中发生异常: {str(e)}")
        return None


# 测试用例(替换为你的实际数据)
if __name__ == "__main__":
    # 输入参数(从加密消息中获取)
    encrypt = ""
    timestamp = ""
    nonce = ""
    msg_signature = ""
    
    # 服务器存储的凭证
    token = ""
    encoding_aes_key = ""  # 你的EncodingAESKey
    
    print("===== 开始签名验证 =====")
    if verify_signature(token, encrypt, timestamp, nonce, msg_signature):
        print("\n===== 签名验证通过,开始解密 =====")
        decrypt_message(encoding_aes_key, encrypt)
    else:
        print("\n[错误] 签名验证失败,终止处理")

回答关注问题邀请回答
收藏

1 个回答

登录 后发表内容