| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- <?php
- /*
- * This file is part of the overtrue/wechat.
- *
- * (c) overtrue <i@overtrue.me>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- /**
- * Encryptor.php.
- *
- * @author overtrue <i@overtrue.me>
- * @copyright 2015 overtrue <i@overtrue.me>
- *
- * @see https://github.com/overtrue
- * @see http://overtrue.me
- */
- namespace EasyWeChat\Encryption;
- use EasyWeChat\Core\Exceptions\InvalidConfigException;
- use EasyWeChat\Support\XML;
- use Exception as BaseException;
- /**
- * Class Encryptor.
- */
- class Encryptor
- {
- /**
- * App id.
- *
- * @var string
- */
- protected $appId;
- /**
- * App token.
- *
- * @var string
- */
- protected $token;
- /**
- * AES key.
- *
- * @var string
- */
- protected $AESKey;
- /**
- * Block size.
- *
- * @var int
- */
- protected $blockSize;
- /**
- * Constructor.
- *
- * @param string $appId
- * @param string $token
- * @param string $AESKey
- */
- public function __construct($appId, $token, $AESKey)
- {
- $this->appId = $appId;
- $this->token = $token;
- $this->AESKey = $AESKey;
- $this->blockSize = 32;
- }
- /**
- * Encrypt the message and return XML.
- *
- * @param string $xml
- * @param string $nonce
- * @param int $timestamp
- *
- * @return string
- */
- public function encryptMsg($xml, $nonce = null, $timestamp = null)
- {
- $encrypt = $this->encrypt($xml, $this->appId);
- !is_null($nonce) || $nonce = substr($this->appId, 0, 10);
- !is_null($timestamp) || $timestamp = time();
- //生成安全签名
- $signature = $this->getSHA1($this->token, $timestamp, $nonce, $encrypt);
- $response = [
- 'Encrypt' => $encrypt,
- 'MsgSignature' => $signature,
- 'TimeStamp' => $timestamp,
- 'Nonce' => $nonce,
- ];
- //生成响应xml
- return XML::build($response);
- }
- /**
- * Decrypt message.
- *
- * @param string $msgSignature
- * @param string $nonce
- * @param string $timestamp
- * @param string $postXML
- *
- * @return array
- *
- * @throws EncryptionException
- */
- public function decryptMsg($msgSignature, $nonce, $timestamp, $postXML)
- {
- try {
- $array = XML::parse($postXML);
- } catch (BaseException $e) {
- throw new EncryptionException('Invalid xml.', EncryptionException::ERROR_PARSE_XML);
- }
- $encrypted = $array['Encrypt'];
- $signature = $this->getSHA1($this->token, $timestamp, $nonce, $encrypted);
- if ($signature !== $msgSignature) {
- throw new EncryptionException('Invalid Signature.', EncryptionException::ERROR_INVALID_SIGNATURE);
- }
- return XML::parse($this->decrypt($encrypted, $this->appId));
- }
- /**
- * Get SHA1.
- *
- * @return string
- *
- * @throws EncryptionException
- */
- public function getSHA1()
- {
- try {
- $array = func_get_args();
- sort($array, SORT_STRING);
- return sha1(implode($array));
- } catch (BaseException $e) {
- throw new EncryptionException($e->getMessage(), EncryptionException::ERROR_CALC_SIGNATURE);
- }
- }
- /**
- * Encode string.
- *
- * @param string $text
- *
- * @return string
- */
- public function encode($text)
- {
- $padAmount = $this->blockSize - (strlen($text) % $this->blockSize);
- $padAmount = 0 !== $padAmount ? $padAmount : $this->blockSize;
- $padChr = chr($padAmount);
- $tmp = '';
- for ($index = 0; $index < $padAmount; ++$index) {
- $tmp .= $padChr;
- }
- return $text.$tmp;
- }
- /**
- * Decode string.
- *
- * @param string $decrypted
- *
- * @return string
- */
- public function decode($decrypted)
- {
- $pad = ord(substr($decrypted, -1));
- if ($pad < 1 || $pad > $this->blockSize) {
- $pad = 0;
- }
- return substr($decrypted, 0, (strlen($decrypted) - $pad));
- }
- /**
- * Return AESKey.
- *
- * @return string
- *
- * @throws InvalidConfigException
- */
- protected function getAESKey()
- {
- if (empty($this->AESKey)) {
- throw new InvalidConfigException("Configuration mission, 'aes_key' is required.");
- }
- if (43 !== strlen($this->AESKey)) {
- throw new InvalidConfigException("The length of 'aes_key' must be 43.");
- }
- return base64_decode($this->AESKey.'=', true);
- }
- /**
- * Encrypt string.
- *
- * @param string $text
- * @param string $appId
- *
- * @return string
- *
- * @throws EncryptionException
- */
- private function encrypt($text, $appId)
- {
- try {
- $key = $this->getAESKey();
- $random = $this->getRandomStr();
- $text = $this->encode($random.pack('N', strlen($text)).$text.$appId);
- $iv = substr($key, 0, 16);
- $encrypted = openssl_encrypt($text, 'aes-256-cbc', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
- return base64_encode($encrypted);
- } catch (BaseException $e) {
- throw new EncryptionException($e->getMessage(), EncryptionException::ERROR_ENCRYPT_AES);
- }
- }
- /**
- * Decrypt message.
- *
- * @param string $encrypted
- * @param string $appId
- *
- * @return string
- *
- * @throws EncryptionException
- */
- private function decrypt($encrypted, $appId)
- {
- try {
- $key = $this->getAESKey();
- $ciphertext = base64_decode($encrypted, true);
- $iv = substr($key, 0, 16);
- $decrypted = openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
- } catch (BaseException $e) {
- throw new EncryptionException($e->getMessage(), EncryptionException::ERROR_DECRYPT_AES);
- }
- try {
- $result = $this->decode($decrypted);
- if (strlen($result) < 16) {
- return '';
- }
- $content = substr($result, 16, strlen($result));
- $listLen = unpack('N', substr($content, 0, 4));
- $xmlLen = $listLen[1];
- $xml = substr($content, 4, $xmlLen);
- $fromAppId = trim(substr($content, $xmlLen + 4));
- } catch (BaseException $e) {
- throw new EncryptionException($e->getMessage(), EncryptionException::ERROR_INVALID_XML);
- }
- if ($fromAppId !== $appId) {
- throw new EncryptionException('Invalid appId.', EncryptionException::ERROR_INVALID_APPID);
- }
- $dataSet = json_decode($xml, true);
- if ($dataSet && (JSON_ERROR_NONE === json_last_error())) {
- // For mini-program JSON formats.
- // Convert to XML if the given string can be decode into a data array.
- $xml = XML::build($dataSet);
- }
- return $xml;
- }
- /**
- * Generate random string.
- *
- * @return string
- */
- private function getRandomStr()
- {
- return substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'), 0, 16);
- }
- }
|