【原】微信企业号加密类库详解(下)
in 备忘 with 0 comment

【原】微信企业号加密类库详解(下)

in 备忘 with 0 comment

人生的意志和劳动将创造奇迹般的奇迹。 --涅克拉索夫


继续昨天,将微信加密类库剩下的两个类分析完。

Prpcrypt.php

这个文件主要功能为:根据给出的AESKey,和公司应用ID,对明文数据加密,对密文数据解密。这儿需要注意的是,想要使用微信的加密类库,那么PHP的 Mcrypt 扩展是必不可少的。这个类,核心函数就两个 加密解密。所以就不整个代码复制了,直接按函数分析好了。

函数一:encrypt,很明显,这是一个加密函数。接下来,我们从函数中不难看出,微信在对数据进行了多次扰码添加,多次加入杂项,进一步加强数据安全性。本类大量使用到了Mcrypt的函数,想要明白具体实现还是需要去看函数的具体定义http://php.net/manual/zh/ref.mcrypt.php

public function encrypt($text, $corpid){
    try {
        //获得16位随机字符串,填充到明文之前
        $random = $this->getRandomStr();
        //这里核心需要理解的是pack函数,Pack函数是PHP中一个将指定数据[strlen($text)]按照特定的格式[N]打包成二进制数据的函数。
        $text = $random . pack("N", strlen($text)) . $text . $corpid;
        //获取加密算法“MCRYPT_RIJNDAEL_128”的分组大小
        $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128);
        //用“CBC”模式,“MCRYPT_RIJNDAEL_128”算法开启一个模块
        $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
        $iv = substr($this->key, 0, 16);
        //使用自定义的填充方式对明文进行补位填充
        $pkc_encoder = new Pkcs7Encoder();
        $text = $pkc_encoder->encode($text);
        //初始化加密所需的缓冲区
        mcrypt_generic_init($module, $this->key, $iv);
        //加密
        $encrypted = mcrypt_generic($module, $text);
        mcrypt_generic_deinit($module);
        mcrypt_module_close($module);
        
        //使用BASE64对加密后的字符串进行编码
        return array(ErrorCode::OK, base64_encode($encrypted));
    } catch (\Exception $e) {
        print $e;
        return array(ErrorCode::EncryptAESError, null);
    }
}

函数二:decrypt,解密函数,这个函数正好是和加密函数是反着运行的。同样的需要对二进制和Mcypt函数有一定的认识。

public function decrypt($encrypted, $corpid){
    try {
        //使用BASE64对需要解密的字符串进行解码
        $ciphertext_dec = base64_decode($encrypted);
        $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
        $iv = substr($this->key, 0, 16);
        mcrypt_generic_init($module, $this->key, $iv);
        //解密
        $decrypted = mdecrypt_generic($module, $ciphertext_dec);
        mcrypt_generic_deinit($module);
        mcrypt_module_close($module);
    } catch (\Exception $e) {
        return array(ErrorCode::DecryptAESError, null);
    }
    try {
        //去除补位字符
        $pkc_encoder = new Pkcs7Encoder();
        $result = $pkc_encoder->decode($decrypted);
        //去除16位随机字符串,网络字节序和AppId
        if (strlen($result) < 16)
            return "";
        $content = substr($result, 16, strlen($result));
        $len_list = unpack("N", substr($content, 0, 4));
        $xml_len = $len_list[1];
        $xml_content = substr($content, 4, $xml_len);
        $from_corpid = substr($content, $xml_len + 4);
    } catch (\Exception $e) {
        print $e;
        return array(ErrorCode::IllegalBuffer, null);
    }
    if ($from_corpid != $corpid)
        return array(ErrorCode::ValidateCorpidError, null);
    return array(0, $xml_content);

}

WxCrypt.php

这个文件主要是各个功能的封装,将昨天说及的各个类库给联系起来,用以实现各个具体的场景。由于类太长,这里将以函数为最小单元进行剖析。

函数一:VerifyURL,从名字就可以看出这是一个验证URL的函数,也就是身份认证。

/**
 * @param string $sMsgSignature 签名串,对应URL参数的msg_signature
 * @param string $sTimeStamp 时间戳,对应URL参数的timestamp
 * @param string $sNonce 随机串,对应URL参数的nonce
 * @param string $sEchoStr 随机串,对应URL参数的echostr
 * @param string $sReplyEchoStr 解密之后的echostr,当return返回0时有效
 * @return int 成功0,失败返回对应的错误码
 */
public function VerifyURL($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr, &$sReplyEchoStr){
    //从微信文档上也可以看出来他的AESKey必须是一个长度为43的字符串。
    if (strlen($this->m_sEncodingAesKey) != 43) {
        return ErrorCode::IllegalAesKey;
    }
    //初始化类,传入关键Key
    $pc = new Prpcrypt($this->m_sEncodingAesKey);
    //根据接口传来的相关数据,本地计算生成
    $sha1 = new Sha1();
    $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $sEchoStr);
    $ret = $array[0];
    if ($ret != 0) {
        return $ret;
    }
    $signature = $array[1];
    //比较接口给出的令牌和计算出的令牌是否一致,不一致则为非法
    if ($signature != $sMsgSignature) {
        return ErrorCode::ValidateSignatureError;
    }
    //调用解密函数,解密数据。
    $result = $pc->decrypt($sEchoStr, $this->m_sCorpid);
    if ($result[0] != 0) {
        return $result[0];
    }
    $sReplyEchoStr = $result[1];
    return ErrorCode::OK;
}

函数二:EncryptMsg,这个函数主要是将用户拼接后的明文xml字符串,进行加密。

public function EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce, &$sEncryptMsg) {
    $pc = new Prpcrypt($this->m_sEncodingAesKey);

    //加密数据
    $array = $pc->encrypt($sReplyMsg, $this->m_sCorpid);
    $ret = $array[0];
    if ($ret != 0) {
        return $ret;
    }
    if ($sTimeStamp == null) {
        $sTimeStamp = time();
    }
    $encrypt = $array[1];

    //生成安全签名
    $sha1 = new Sha1();
    $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt);
    $ret = $array[0];
    if ($ret != 0) {
        return $ret;
    }
    $signature = $array[1];

    //生成发送的xml
    $sEncryptMsg = $this->generate($encrypt, $signature, $sTimeStamp, $sNonce);
    return ErrorCode::OK;
}

函数三:generate, 这个函数的主要作用就是,根据参数组合特定的XML格式字符串。

    public function generate($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);
    }

函数四:DecryptMsg,解密函数,这个函数主要是验证用户身份,同时解密微信服务器给出的密文数据。

public function DecryptMsg($sMsgSignature, $sTimeStamp = null, $sNonce, $sPostData, &$sMsg) {
    if (strlen($this->m_sEncodingAesKey) != 43) {
        return ErrorCode::IllegalAesKey;
    }
    $pc = new Prpcrypt($this->m_sEncodingAesKey);

    //提取密文
    if ($sTimeStamp == null) {
        $sTimeStamp = time();
    }
    $encrypt = $sPostData['Encrypt'];

    //验证安全签名
    $sha1 = new Sha1();
    $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt);
    $ret = $array[0];
    if ($ret != 0) {
        return $ret;
    }
    $signature = $array[1];
    if ($signature != $sMsgSignature) {
        return ErrorCode::ValidateSignatureError;
    }

    //消息解密
    $result = $pc->decrypt($encrypt, $this->m_sCorpid);
    if ($result[0] != 0) {
        return $result[0];
    }
    $sMsg = $result[1];
    return ErrorCode::OK;
}

最后说两句,我认为加密就是干扰和运算,我们从上面的加密就可以看出,其中大量使用的PHP的Mcrypt类库函数,以及进行二进制相关运算。在后面的博客中我将单独聊聊这一块。上面的加密重在会用,没有计算机基础很难理解!

Comments are closed.