cqp 1 هفته پیش
والد
کامیت
39c9a225ed
1فایلهای تغییر یافته به همراه42 افزوده شده و 63 حذف شده
  1. 42 63
      app/Service/DingTalkCrypto.php

+ 42 - 63
app/Service/DingTalkCrypto.php

@@ -4,9 +4,8 @@ namespace App\Service;
 
 class DingTalkCrypto
 {
-    protected $token;
-    protected $encodingAesKey;  // URL safe base64 字符串,长度 43
-    // protected $corpid; // 如果需要
+    protected string $token;
+    protected string $encodingAesKey; // URL safe base64 字符串,长度 43
 
     public function __construct(string $token, string $encodingAesKey)
     {
@@ -15,118 +14,98 @@ class DingTalkCrypto
     }
 
     /**
-     * 验签 + 解密消息
+     * 解密钉钉事件回调消息
      *
-     * @param string $signature 从请求 query 参数 signature
-     * @param string $timestamp 从请求参数 timestamp
-     * @param string $nonce 从请求参数 nonce
-     * @param string $encrypt 消息体中的 encrypt 字段
-     * @return array|false 解密后的数组,失败返回 false
+     * @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. 验签
-        $tmpArr = [$this->token, $timestamp, $nonce, $encrypt];
-        sort($tmpArr, SORT_STRING);
-        $tmpStr = implode($tmpArr);
+        // 1. 验签(官方:token + timestamp + nonce + encrypt)
+        $tmpStr = $this->token . $timestamp . $nonce . $encrypt;
         $hash = sha1($tmpStr);
         if ($hash !== $signature) {
             return false;
         }
 
-        // 2. 解密
-        $aesKey = base64_decode($this->encodingAesKey . '=');  // 注意要加一个 '=' 补足 padding
+        // 2. 解密 AES-256-CBC
+        $aesKey = base64_decode($this->encodingAesKey); // 官方不加 "="
         $iv = substr($aesKey, 0, 16);
-
-        // 解密,使用 AES-256-CBC
         $cipherText = base64_decode($encrypt);
+
         $decrypted = openssl_decrypt($cipherText, 'AES-256-CBC', $aesKey, OPENSSL_ZERO_PADDING, $iv);
-        if ($decrypted === false) {
-            return false;
-        }
+        if ($decrypted === false) return false;
 
-        // 去 padding, PKCS#7
+        // 3. 去 padding
         $decrypted = $this->pkcs7Unpad($decrypted);
 
-        // 解出: 16 位随机字符串 + 4 字节内容长度 network-order + 内容 + corpId(如果有的话)
-        // 跳过前 16 位
+        // 4. 去掉前 16 位随机字符串
         $content = substr($decrypted, 16);
-        // 读长度
+
+        // 5. 读取消息长度
         $lenList = unpack("N", substr($content, 0, 4));
-        $xmlLen = $lenList[1];
-        $xmlContent = substr($content, 4, $xmlLen);
-        // 如果有 corpId 在 xmlContent 后还有
-        // $fromCorpId = substr($content, 4 + $xmlLen);
-
-        $json = $xmlContent;  // 钉钉回调 body 是 JSON 字符串
-        // 解析
-        $arr = json_decode($json, true);
-        return $arr;
+        $jsonLen = $lenList[1];
+
+        // 6. 获取消息 JSON
+        $json = substr($content, 4, $jsonLen);
+        return json_decode($json, true);
     }
 
     /**
-     * 加密回复消息,比如返回 “success”
+     * 加密返回给钉钉的消息
      *
-     * @param string $replyMsg 明文消息,比如 "success"
+     * @param string $replyMsg 明文消息
      * @param string $timestamp
      * @param string $nonce
-     * @return array 包含加密后json结构
+     * @return array
      */
-    public function encryptMsg(string $replyMsg, string $timestamp, string $nonce)
+    public function encryptMsg(string $replyMsg, string $timestamp, string $nonce): array
     {
-        // 1. 构造消息内容
         $random16 = $this->getRandomStr(16);
-        $replyMsgXml = $replyMsg;  // 钉钉回调用 JSON 格式内容
-        $msgLen = strlen($replyMsgXml);
-        $msgLenBin = pack("N", $msgLen);
-        $corpId = '';  // 如果不需要 corpId,可以空
+        $msgLenBin = pack("N", strlen($replyMsg));
+        $corpId = ''; // 不需要corpId
 
-        $plain = $random16 . $msgLenBin . $replyMsgXml . $corpId;
+        $plain = $random16 . $msgLenBin . $replyMsg . $corpId;
 
-        // 2. PKCS#7 padding
+        // PKCS#7 padding
         $blockSize = 32;
         $pad = $blockSize - (strlen($plain) % $blockSize);
-        if ($pad == 0) {
-            $pad = $blockSize;
-        }
+        $pad = $pad === 0 ? $blockSize : $pad;
         $plainPadded = $plain . str_repeat(chr($pad), $pad);
 
-        // 3. AES 加密
-        $aesKey = base64_decode($this->encodingAesKey . '=');
+        // 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);
 
-        // 4. 签名
-        $signatureArr = [$this->token, $timestamp, $nonce, $encryptBase64];
-        sort($signatureArr, SORT_STRING);
-        $signature = sha1(implode($signatureArr));
+        // 计算 msg_signature
+        $signatureStr = $this->token . $timestamp . $nonce . $encryptBase64;
+        $msgSignature = sha1($signatureStr);
 
-        // 5. 返回结构
         return [
-            'msg_signature' => $signature,
+            'msg_signature' => $msgSignature,
             'encrypt'       => $encryptBase64
         ];
     }
 
-    private function getRandomStr($length = 16)
+    private function getRandomStr(int $length = 16): string
     {
         $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
         $str = '';
         for ($i = 0; $i < $length; $i++) {
-            $str .= $chars[ord(bin2hex(random_bytes(1))) % strlen($chars)];
+            $str .= $chars[random_int(0, strlen($chars) - 1)];
         }
         return $str;
     }
 
-    private function pkcs7Unpad($text)
+    private function pkcs7Unpad(string $text)
     {
-        // 去掉结尾的 padding
         $pad = ord(substr($text, -1));
-        if ($pad < 1 || $pad > 32) {
-            // padding 有问题
-            return false;
-        }
+        if ($pad < 1 || $pad > 32) return false;
         return substr($text, 0, -$pad);
     }
 }