DingCallbackCrypto.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <?php
  2. namespace App\Service;
  3. /**
  4. * PHP7.1及其之上版本的回调加解密类库
  5. * 该版本依赖openssl_encrypt方法加解密,注意版本依赖 (PHP 5 >= 5.3.0, PHP 7)
  6. */
  7. class DingCallbackCrypto
  8. {
  9. /**
  10. * @param token 钉钉开放平台上,开发者设置的token
  11. * @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey
  12. * @param corpId 企业自建应用-事件订阅, 使用appKey
  13. * 企业自建应用-注册回调地址, 使用corpId
  14. * 第三方企业应用, 使用suiteKey
  15. */
  16. private $m_token;
  17. private $m_encodingAesKey;
  18. private $m_corpId;
  19. //注意这里修改为构造函数
  20. function __construct($token, $encodingAesKey, $ownerKey)
  21. {
  22. $this->m_token = $token;
  23. $this->m_encodingAesKey = $encodingAesKey;
  24. $this->m_corpId = $ownerKey;
  25. }
  26. public function getEncryptedMap($plain){
  27. $timeStamp = time();
  28. $pc = new Prpcrypt($this->m_encodingAesKey);
  29. $nonce= $pc->getRandomStr();
  30. return $this->getEncryptedMapDetail($plain, $timeStamp, $nonce);
  31. }
  32. /**
  33. * 加密回调信息
  34. */
  35. public function getEncryptedMapDetail($plain, $timeStamp, $nonce)
  36. {
  37. $pc = new Prpcrypt($this->m_encodingAesKey);
  38. $array = $pc->encrypt($plain, $this->m_corpId);
  39. $ret = $array[0];
  40. if ($ret != 0) {
  41. //return $ret;
  42. // return ['ErrorCode'=>$ret, 'data' => ''];
  43. throw new \Exception('AES加密错误',ErrorCode::$EncryptAESError);
  44. }
  45. if ($timeStamp == null) {
  46. $timeStamp = time();
  47. }
  48. $encrypt = $array[1];
  49. $sha1 = new SHA1;
  50. $array = $sha1->getSHA1($this->m_token, $timeStamp, $nonce, $encrypt);
  51. $ret = $array[0];
  52. if ($ret != 0) {
  53. //return $ret;
  54. throw new \Exception('ComputeSignatureError',ErrorCode::$ComputeSignatureError);
  55. }
  56. $signature = $array[1];
  57. $encryptMsg = json_encode(array(
  58. "msg_signature" => $signature,
  59. "encrypt" => $encrypt,
  60. "timeStamp" => $timeStamp,
  61. "nonce" => $nonce
  62. ));
  63. return $encryptMsg;
  64. }
  65. /**
  66. * 解密回调信息
  67. */
  68. public function getDecryptMsg($signature, $timeStamp = null, $nonce, $encrypt)
  69. {
  70. if (strlen($this->m_encodingAesKey) != 43) {
  71. //return ErrorCode::$IllegalAesKey;
  72. // return ['ErrorCode'=>ErrorCode::$IllegalAesKey, 'data' => ''];
  73. throw new \Exception('IllegalAesKey',ErrorCode::$IllegalAesKey);
  74. }
  75. $pc = new Prpcrypt($this->m_encodingAesKey);
  76. if ($timeStamp == null) {
  77. $timeStamp = time();
  78. }
  79. $sha1 = new SHA1;
  80. $array = $sha1->getSHA1($this->m_token, $timeStamp, $nonce, $encrypt);
  81. $ret = $array[0];
  82. if ($ret != 0) {
  83. //return $ret;
  84. // return ['ErrorCode'=>$ret, 'data' => ''];
  85. throw new \Exception('ComputeSignatureError',ErrorCode::$ComputeSignatureError);
  86. }
  87. $verifySignature = $array[1];
  88. if ($verifySignature != $signature) {
  89. //return ErrorCode::$ValidateSignatureError;
  90. //return ['ErrorCode'=>ErrorCode::$ValidateSignatureError, 'data' => ''];
  91. throw new \Exception('ValidateSignatureError',ErrorCode::$ValidateSignatureError);
  92. }
  93. $result = $pc->decrypt($encrypt, $this->m_corpId);
  94. if ($result[0] != 0) {
  95. //return $result[0];
  96. // return ['ErrorCode'=>$result[0], 'data' => ''];
  97. throw new \Exception('DecryptAESError',ErrorCode::$DecryptAESError);
  98. }
  99. $decryptMsg = $result[1];
  100. //return ErrorCode::$OK;
  101. return $decryptMsg;
  102. }
  103. }
  104. class SHA1
  105. {
  106. public function getSHA1($token, $timestamp, $nonce, $encrypt_msg)
  107. {
  108. try {
  109. $array = array($encrypt_msg, $token, $timestamp, $nonce);
  110. sort($array, SORT_STRING);
  111. $str = implode($array);
  112. return array(ErrorCode::$OK, sha1($str));
  113. } catch (\Exception $e) {
  114. print $e . "\n";
  115. return array(ErrorCode::$ComputeSignatureError, null);
  116. }
  117. }
  118. }
  119. /**
  120. * error code 说明.
  121. * <ul>
  122. * <li>-900004: encodingAesKey 非法</li>
  123. * <li>-900005: 签名验证错误</li>
  124. * <li>-900006: sha加密生成签名失败</li>
  125. * <li>-900007: aes 加密失败</li>
  126. * <li>-900008: aes 解密失败</li>
  127. * <li>-900010: suiteKey 校验错误</li>
  128. * </ul>
  129. */
  130. class ErrorCode
  131. {
  132. public static $OK = 0;
  133. public static $IllegalAesKey = 900004;
  134. public static $ValidateSignatureError = 900005;
  135. public static $ComputeSignatureError = 900006;
  136. public static $EncryptAESError = 900007;
  137. public static $DecryptAESError = 900008;
  138. public static $ValidateSuiteKeyError = 900010;
  139. }
  140. class PKCS7Encoder
  141. {
  142. public static $block_size = 32;
  143. function encode($text)
  144. {
  145. $block_size = PKCS7Encoder::$block_size;
  146. $text_length = strlen($text);
  147. $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
  148. if ($amount_to_pad == 0) {
  149. $amount_to_pad = PKCS7Encoder::$block_size;
  150. }
  151. $pad_chr = chr($amount_to_pad);
  152. $tmp = "";
  153. for ($index = 0; $index < $amount_to_pad; $index++) {
  154. $tmp .= $pad_chr;
  155. }
  156. return $text . $tmp;
  157. }
  158. function decode($text)
  159. {
  160. $pad = ord(substr($text, -1));
  161. if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
  162. $pad = 0;
  163. }
  164. return substr($text, 0, (strlen($text) - $pad));
  165. }
  166. }
  167. class Prpcrypt
  168. {
  169. public $key;
  170. function __construct($k)
  171. {
  172. $this->key = base64_decode($k . "=");
  173. }
  174. public function encrypt($text, $corpid)
  175. {
  176. try {
  177. //获得16位随机字符串,填充到明文之前
  178. $random = $this->getRandomStr();
  179. $text = $random . pack("N", strlen($text)) . $text . $corpid;
  180. $iv = substr($this->key, 0, 16);
  181. // 网络字节序
  182. // $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
  183. // $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
  184. //使用自定义的填充方式对明文进行补位填充
  185. $pkc_encoder = new PKCS7Encoder;
  186. $text = $pkc_encoder->encode($text);
  187. // mcrypt_generic_init($module, $this->key, $iv);
  188. // //加密
  189. // $encrypted = mcrypt_generic($module, $text);
  190. // mcrypt_generic_deinit($module);
  191. // mcrypt_module_close($module);
  192. $encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv );
  193. //print(base64_encode($encrypted));
  194. //使用BASE64对加密后的字符串进行编码
  195. return array(ErrorCode::$OK, base64_encode($encrypted));
  196. } catch (\Exception $e) {
  197. print $e;
  198. return array(ErrorCode::$EncryptAESError, null);
  199. }
  200. }
  201. public function decrypt($encrypted, $corpid)
  202. {
  203. try {
  204. $ciphertext_dec = base64_decode($encrypted);
  205. // $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
  206. $iv = substr($this->key, 0, 16);
  207. // mcrypt_generic_init($module, $this->key, $iv);
  208. // $decrypted = mdecrypt_generic($module, $ciphertext_dec);
  209. // mcrypt_generic_deinit($module);
  210. // mcrypt_module_close($module);
  211. $decrypted = openssl_decrypt ( $ciphertext_dec, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv );
  212. // return $decrypted;
  213. } catch (\Exception $e) {
  214. return array(ErrorCode::$DecryptAESError, null);
  215. }
  216. try {
  217. //去除补位字符
  218. $pkc_encoder = new PKCS7Encoder;
  219. $result = $pkc_encoder->decode($decrypted);
  220. //去除16位随机字符串,网络字节序和AppId
  221. if (strlen($result) < 16)
  222. return "";
  223. $content = substr($result, 16, strlen($result));
  224. $len_list = unpack("N", substr($content, 0, 4));
  225. $xml_len = $len_list[1];
  226. $xml_content = substr($content, 4, $xml_len);
  227. $from_corpid = substr($content, $xml_len + 4);
  228. } catch (\Exception $e) {
  229. print $e;
  230. return array(ErrorCode::$DecryptAESError, null);
  231. }
  232. if ($from_corpid != $corpid)
  233. return array(ErrorCode::$ValidateSuiteKeyError, null);
  234. return array(0, $xml_content);
  235. }
  236. function getRandomStr()
  237. {
  238. $str = "";
  239. $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
  240. $max = strlen($str_pol) - 1;
  241. for ($i = 0; $i < 16; $i++) {
  242. $str .= $str_pol[mt_rand(0, $max)];
  243. }
  244. return $str;
  245. }
  246. }
  247. ///**
  248. // * 以下为开发者需要自行构造的接口(也就是回调url),接口内调用demo类中的解密和加密方法即可
  249. // */
  250. //
  251. //function test_demo(){
  252. // // 构造加解密方法
  253. // $crypt = new DingCallbackCrypto("token", "aes_key", "ownerKey");
  254. // // 解密方法; data为钉钉服务器请求开发者该接口时携带的参数(msg_signature,timeStamp和nonce在request中,encrypt在body中)
  255. // $text = $crypt->getDecryptMsg($data->msg_signature, $data->timeStamp, $data->nonce, $data->encrypt);
  256. // var_dump($text);
  257. // // 加密返回;参数固定传success字符串,该方法得到的信息直接返回给钉钉服务器即可
  258. // $res = $crypt->getEncryptedMap("success");
  259. // var_dump($res);
  260. //}
  261. //
  262. //test_demo();