钉钉官方的php demo文档是几年前的了,其中aes加密这块用的是老版本的函数,会报错Fatal error: Call to undefined function mcrypt_get_block_size()错误 ,其他几个mcrypt_开头函数也会报错。最直观的后果就是返回 { [“errcode”]=> int(71009) [“errmsg”]=> string(22) “返回文本非success” }
mcrypt函数适用于php4、php5版本?php7.0及之前使用该函数只要屏蔽掉报错信息在用到mcrypt函数语句的地方前面加@屏蔽就可以正常使用。但是7.1及以上版本就完全弃用了,这时候可以使用openssl函数来操作。只要是用到AES的地方都需要注意匹配你的php版本哦!下面直接将官方demo包的pkcs7Encoder.php内容替换成我的就行了(根据获取的php版本号自动调用AES加解密函数,因为这个问题测试了无数次,请忽略php文件中的杂乱信息)
<?phpinclude_once "errorCode.php";class PKCS7Encoder{public static $block_size = 32;function encode($text){$block_size = PKCS7Encoder::$block_size;$text_length = strlen($text);$amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);if ($amount_to_pad == 0) {$amount_to_pad = PKCS7Encoder::block_size;}$pad_chr = chr($amount_to_pad);$tmp = "";for ($index = 0; $index < $amount_to_pad; $index++) {$tmp .= $pad_chr;}return $text . $tmp;}function decode($text){$pad = ord(substr($text, -1));if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {$pad = 0;}return substr($text, 0, (strlen($text) - $pad));}}class Prpcrypt{public $key;function __construct($k){$this->key = base64_decode($k . "="); //为变量赋值}/* function Prpcrypt($k){$this->key = base64_decode($k . "=");}*/public function encrypt($text, $corpid){try {//获得16位随机字符串,填充到明文之前$random = $this->getRandomStr();$text = $random . pack("N", strlen($text)) . $text . $corpid;// 网络字节序$version=substr(PHP_VERSION,0,3);if($version<'7.1'){//(PHP 4, PHP 5 正常使用) (php6-php7.0 可能会warning提示但是屏蔽错误后可继续使用)(php7.1+ 由openssl_encrypt替代)// mcrypt 函数在php7.1 以上版本被遗弃,可使用替代函数。。。 这里以@屏蔽错误, @的使用会影响效率@$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);//mcrypt_get_block_size() 用来获取 cipher (其中包括了加密模式) 加密算法分组大小。@$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);//mcrypt_generic_init — 初始化加密所需的缓冲区 在每次调用 mcrypt_generic() 或 mdecrypt_generic() 函数之前必须调用本函数。//加密@$encrypted = mcrypt_generic($module, $text);//本函数用来加密数据。 传入数据长度必须是 n * 分组大小,否则需要后补 "\0"。 本函数返回加密后的数据。 注意,根据数据补齐不同, 返回的数据可能比输入的数据长度有所增加。//$moudle为加密描述符,$text为加密前的数据,数据加密函数返回加密后的字符串@mcrypt_generic_deinit($module);//mcrypt_generic_deinit — 对加密模块进行清理工作 它会清理缓冲区,但是并不关闭模块。 要想关闭加密模块, 你需要自行调用 mcrypt_module_close() 函数。@mcrypt_module_close($module);//print(base64_encode($encrypted));//使用BASE64对加密后的字符串进行编码}else{//php 7.1 //echo base64_encode(openssl_encrypt($str, 'aes-128-cbc', $key, true, $iv));// $data = openssl_encrypt($text, 'aes-128-cbc',$this->key,true,$iv);// $encrypted = $data;$iv = substr($this->key, 0, 16);$aes_encrypt=openssl_encrypt($text, 'AES-256-CBC',$this->key,OPENSSL_RAW_DATA, $iv);// $msg_encrypt =base64_encode($aes_encrypt);$encrypted =$aes_encrypt;//return $data;//php 7.1}return array(ErrorCode::$OK, base64_encode($encrypted));} catch (Exception $e) {print $e;return array(ErrorCode::$EncryptAESError, null);}}public function decrypt($encrypted, $corpid){try {$ciphertext_dec = base64_decode($encrypted);$iv = substr($this->key, 0, 16);$version=substr(PHP_VERSION,0,3);if($version<'7.1'){@$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');@mcrypt_generic_init($module, $this->key, $iv);@$decrypted = mdecrypt_generic($module, $ciphertext_dec);@mcrypt_generic_deinit($module);@mcrypt_module_close($module);}else{///php7.1 jiemi//echo openssl_decrypt(base64_decode($strEncode), 'aes-128-cbc', $key, true, $iv);//$decrypted = openssl_decrypt($ciphertext_dec, 'AES-256-CBC',$this->key, OPENSSL_RAW_DATA, $iv);//使用OPENSSL_RAW_DATA不行$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC',$this->key,OPENSSL_ZERO_PADDING, $iv);//注意openssl_decrypt第一个参数是base64加密过的字符串,第四个参数是OPENSSL_ZERO_PADDING不是上面加密时的 OPENSSL_RAW_DATA// $decrypted .='xx';///php7.1 jiemi}} catch (Exception $e) {return array(ErrorCode::$DecryptAESError, null);}try {//去除补位字符$pkc_encoder = new PKCS7Encoder;$result = $pkc_encoder->decode($decrypted);//去除16位随机字符串,网络字节序和AppIdif (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::$DecryptAESError, null);}if ($from_corpid != $corpid)return array(ErrorCode::$ValidateSuiteKeyError, null);return array(0, $xml_content);}function getRandomStr(){$str = "";$str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";$max = strlen($str_pol) - 1;for ($i = 0; $i < 16; $i++) {$str .= $str_pol[mt_rand(0, $max)];}return $str;}}?>
php下使用钉钉回调的方法参考我之前的文章: https://ranjuan.cn/php7-0%E4%B8%8B%E9%92%89%E9%92%89%E5%9B%9E%E8%B0%83%E6%8E%A5%E5%8F%A3%E6%B3%A8%E5%86%8C/
出现“ 返回文本非success ”第一步 ,先把所有错误显示 打印出来,解决掉再去排查其他原因。第二步,直接访问回调url地址看有没有除规定返回的json字符串外有其他输出没有(右键–查看网页源代码 里面找有没有 多余的换行、空格等)。第三步、测试加解密函数。 钉钉的说明文档有点反人类,等哪天有空了写篇文章把钉钉的aes加解密单独分析一下。
2019.11.01提示://注意openssl_decrypt第一个参数是base64加密过的字符串,第四个参数是OPENSSL_ZERO_PADDING不是上面加密时的 OPENSSL_RAW_DATA
2 条评论
感谢感谢!太有用了,直接替换就能用!全网就此一篇解决这个问题,太难得了!感谢大神分享!!!
另外我遇到的问题稍微有点不同,PHP版本都是7.3,但是在一台win7上使用官方文件可以正常注册回调,在其他电脑win10就提示错误,不知道是什么问题。而且作为入门小白,想不到怎么去测试回调,都不知道问题在哪里
如果你之前已经成功注册过一个回调地址,后面就不能继续注册了(有且仅能注册一个回调地址),此时你需要使用回调接口的更新接口或删除接口才行!见官方文档:https://ding-doc.dingtalk.com/doc#/serverapi2/pwz3r5