123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- <?php
- namespace App\Service;
- class DingTalkCrypto
- {
- protected string $token;
- protected string $encodingAesKey; // URL safe base64 字符串,长度 43
- public function __construct(string $token, string $encodingAesKey)
- {
- $this->token = $token;
- $this->encodingAesKey = $encodingAesKey;
- }
- /**
- * 解密钉钉事件回调消息
- *
- * @param string $signature URL参数 signature
- * @param string $timestamp URL参数 timestamp
- * @param string $nonce URL参数 nonce
- * @param string $encrypt 消息体 encrypt
- * @return array|false
- */
- public function decryptMsg(string $signature, string $timestamp, string $nonce, string $encrypt)
- {
- // 1. 验签(官方:token + timestamp + nonce + encrypt)
- $tmpStr = $this->token . $timestamp . $nonce . $encrypt;
- $hash = sha1($tmpStr);
- if ($hash !== $signature) {
- return false;
- }
- // 2. 解密 AES-256-CBC
- $aesKey = base64_decode($this->encodingAesKey); // 官方不加 "="
- $iv = substr($aesKey, 0, 16);
- $cipherText = base64_decode($encrypt);
- $decrypted = openssl_decrypt($cipherText, 'AES-256-CBC', $aesKey, OPENSSL_ZERO_PADDING, $iv);
- if ($decrypted === false) return false;
- // 3. 去 padding
- $decrypted = $this->pkcs7Unpad($decrypted);
- // 4. 去掉前 16 位随机字符串
- $content = substr($decrypted, 16);
- // 5. 读取消息长度
- $lenList = unpack("N", substr($content, 0, 4));
- $jsonLen = $lenList[1];
- // 6. 获取消息 JSON
- $json = substr($content, 4, $jsonLen);
- return json_decode($json, true);
- }
- /**
- * 加密返回给钉钉的消息
- *
- * @param string $replyMsg 明文消息
- * @param string $timestamp
- * @param string $nonce
- * @return array
- */
- public function encryptMsg(string $replyMsg, string $timestamp, string $nonce): array
- {
- $random16 = $this->getRandomStr(16);
- $msgLenBin = pack("N", strlen($replyMsg));
- $corpId = ''; // 不需要corpId
- $plain = $random16 . $msgLenBin . $replyMsg . $corpId;
- // PKCS#7 padding
- $blockSize = 32;
- $pad = $blockSize - (strlen($plain) % $blockSize);
- $pad = $pad === 0 ? $blockSize : $pad;
- $plainPadded = $plain . str_repeat(chr($pad), $pad);
- // AES 加密
- $aesKey = base64_decode($this->encodingAesKey);
- $iv = substr($aesKey, 0, 16);
- $encrypted = openssl_encrypt($plainPadded, 'AES-256-CBC', $aesKey, OPENSSL_ZERO_PADDING, $iv);
- $encryptBase64 = base64_encode($encrypted);
- // 计算 msg_signature
- $signatureStr = $this->token . $timestamp . $nonce . $encryptBase64;
- $msgSignature = sha1($signatureStr);
- return [
- 'msg_signature' => $msgSignature,
- 'encrypt' => $encryptBase64
- ];
- }
- private function getRandomStr(int $length = 16): string
- {
- $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- $str = '';
- for ($i = 0; $i < $length; $i++) {
- $str .= $chars[random_int(0, strlen($chars) - 1)];
- }
- return $str;
- }
- private function pkcs7Unpad(string $text)
- {
- $pad = ord(substr($text, -1));
- if ($pad < 1 || $pad > 32) return false;
- return substr($text, 0, -$pad);
- }
- }
|