phpMyAdmin 启用 mcrypt #1045 无法登录 MySQL 服务器 Cannot log in to the MySQL server

2017-10-31 14:29:08 旧日重来

安装好了 phpMyAdmin,运行正常,但登录之后提示建议启用 mcrypt。按照提示一路 yum install php-mcrypt,启用后却发现无法登录了,提示 #1045 Cannot log in to the MySQL server#1045 无法登录 MySQL 服务器)。简单排查未发现明显问题,只好通过 debug 源码解决,记录在这里

出现这个错误一般有以下几类原因:

  1. MySQL 服务未启动或异常
  2. php-mysql/php-mysqli/php-mysqlnd 库和配置文件不匹配
  3. 连接配置有误,如 ip 地址/Linux socket地址不匹配。特别的,如果是支持 IPv6的机器(新版系统一般都支持),则监听地址配了 127.0.0.1 而 phpMyAdmin 配的是 localhost 则可能无法连接

由于安装 php-mcrypt 之前是可以登录的,所以肯定不是上面几个常见原因。而 phpMyAdmin 又没有打印有用的出错日志,一时没有头绪。只好从源代码入手开始 debug 了。

phpMyAdmin 的代码很复杂,为快速入手,首先在所有源代码中搜索出错信息的字符串,很快就在 ./libraries/plugins/auth/AuthenticationCookie.class.php 里发现了:

public function authFails():
   // Other stuff
   $conn_error = '#' . $GLOBALS['errno'] . ' '
      . __('Cannot log in to the MySQL server');

authFails() 函数本身是一个出错时打印信息的回调,而非真正出错的地方,所以并没有提供什么有用的信息。不过从这里入手可以进一步定位,php 内部提供了获取函数 callstack (调用栈) 的功能,所有只要在 authFails() 内部查看 callstack,就可以进一步找到出错的地点了。简单起见,这里直接调用 debug_print_backtrace() 来实现:

public function authFails():
   // Other stuff
   debug_print_backtrace();    // 打印 callstack,added by Aulddays
   $conn_error = '#' . $GLOBALS['errno'] . ' '
      . __('Cannot log in to the MySQL server');
注意,不要在生产环境中直接调用 debug_print_backtrace(),它把信息直接打印到网页上,可能会暴露内部信息。生产环境的话可以考虑用 debug_backtrace() 获取再通过 error_log() 之类打印到仅内部可见的地点

callstack 如下:

#0 AuthenticationCookie->authFails() called at
          [/live.aulddays.com/pma/libraries/dbi/mysql.dbi.lib.php:179]
#1 PMA_DBI_connect(, testp, ) called at
          [/live.aulddays.com/pma/libraries/common.inc.php:978]
#2 require_once(/live.aulddays.com/pma/libraries/common.inc.php) called at
          [/live.aulddays.com/pma/index.php:11]

可以看见,实际的出错地点是 PMA_DBI_connect() 函数。再仔细观察,此函数的原型是:

function PMA_DBI_connect($user, $password, ...)

但 callstack 中实际参数只有 $password,而 $user 是空串,说明用户名在某个地方丢失了。接着查找,发现用户名是在下面这个函数中从浏览器 cookie 解析的:

public function cookieDecrypt($encdata, $secret)

通过 firefox 的 developer tool 查看,站点 cookie 中 pmaUser-1 确实为空,而 pmaAuth-1 是有内容的:

phpmyadmin mcrypt 1045 cookie

说明在创建 cookie 时用户名就已经丢失。跟着上面的 Decrypt() 函数顺藤摸瓜,不难找到对应的 Encrypt() 函数:

public function cookieEncrypt($data, $secret)

该函数功能很简单,代码就不贴了,具体就是看一下是否安装了 mcrypt,如果是就用 mcrypt_encrypt() 来加密,否则调用 phpMyAdmin 自带的加密函数来实现。查看文档,mcrypt_encrypt() 函数成功返回加密结果,失败返回 false。而 phpMyAdmin 在这里并未检查返回值,而 php 内部把 false 转为了空串造成问题。下面就继续看一下为什么出错。这里把 cookieEncrypt() 的相关内容单独提出来测试一下,创建 test.php,内容如下:

srand((double) microtime() * 1000000);
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
if ($td === false) {
   PMA_fatalError(__('Failed to use Blowfish from mcrypt!'));
}
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
 
$result = mcrypt_encrypt(MCRYPT_BLOWFISH,
   "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdsadfasdf",
   "test", MCRYPT_MODE_CBC, $iv);

运行发现报错如下:

PHP Warning:  mcrypt_encrypt():
       Size of key is too large for this algorithm in /live.aulddays.com/test.php on line 8
PHP Catchable fatal error:
       mcrypt_encrypt(): Mcrypt initialisation failed in /live.aulddays.com/test.php on line 8

加密的 key 太长导致加密失败。而这里的 key 就是 config.ini.php 中配置的 $cfg['blowfish_secret']。原来 Aulddays 把这个 key 写的太长,造成 mcrypt 运行出错,改短之后就正常了。经过进一步测试,mcrypt 可接受的最大长度是 56 字节

有趣的是,phpMyAdmin 对这个字符串有最短长度的检查,而未作最大长度检查。另一方面,phpMyAdmin 内部实现的加密代码在长度超长时仍能正常运行。

查看:原文地址;来源:live.aulddays.com


注意:本站所有文章除特别说明外均为原创,版权所有,转载请务必以超链接方式注明作者出处,并禁止用作商业用途