如何在mysql中间件层实现客户端连接的一次登入多次验证
很多情况下,mysql中间件程序会集成连接池功能,并且客户端的登录用户和实际操作后端数据库的用户为不同的用户。也就是说,中间件程序在客户端登录之前已经使用某个特定的用户名和密码建立了中间件到后端mysql的连接池;客户登入验证通过后就可以直接使用连接池中的连接。
这样设计的好处是可以多个用户共享一个连接池,即避免了每个客户重新创建连接的开销,也避免了为每个客户准备单独的连接池所带来的浪费。(当然多个用户共享一个连接池还需要有另外的控制机制保证它们的互不干扰。)
而这样设计也带来了客户端连接操作的权限认证问题。因为客户端登录之后使用的是另一个用户名去操作后端数据库,通常这个用户名拥有比客户端登录用户更大的权限,这就导致了权限被放大的问题。
简单的解决办法是为每个登录客户保留它们登录时的认证连接,每次操作之前使用该认证连接查看权限。
这个方法最直接也最简单,但要为每个登录客户端保留一个连接代价有点高,这限制了中间件程序的客户端并发数。并且作为中间件程序,我们可能不需要那么完整的权限控制;很多情况下业务是以库进行区分的,某些场景下只需要确定这个客户端连接是否有权限操作某个库就足够了。
另一个方法是在需要的时候模拟一次客户端登录重新创建一个认证连接。这样做的好处是按需使用连接,避免为每个客户端保留一个认证连接的高昂代价;如果中间件程序登录后的权限认证频率不高,这是非常推荐一种方法。当然它的难点是你没有客户端登录的密码,如何去模拟登录呢?
首先看下mysql客户端是如何登录的,如下是代码示例。代码的输入为客户端密码,服务端返回的随机加密串。
SHA1_CONTEXT sha1_context;
uint8_t hash_stage1[SHA1_HASH_SIZE];
uint8_t hash_stage2[SHA1_HASH_SIZE];
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, (uint8_t *) password, (unsigned int) strlen(password));
mysql_sha1_result(&sha1_context, hash_stage1);
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, hash_stage1, SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, hash_stage2);
;
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, (const uint8_t *) message, SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, (uint8_t *) to);
my_crypt(to, (const unsigned char *) to, hash_stage1, SCRAMBLE_LENGTH);
可以看到首先通过对客户端密码的hash获得 hash_stage1,然后通过对hash_stage1的再hash获得hash_stage2。然后通过将 hash_stage2和服务端随机串的hash结果与hash_stage1进行加密(异或操作)生产最终发给服务端的认证串。
所以,我们只需要能获取到hash_stage1和hash_stage2就可以不用客户端密码直接登录了。
hash_stage2其实就保存在服务端的mysql.user表的password字段中,当然需要进行一下简单的转换,将它转成2进制(参考mysql代码,使用函数hex2octet)。而hash_stage1,参考mysql服务端的认证代码则可以通过hash_stage2,服务端随机认证串以及对应的客户端最终发往服务端的认证串计算出来。
获取到hash_stage2和hash_stage1之后就可以根据新的服务端认证串进行登录了。
如下是示例代码,输入为
mysql.user表的password字段,代码中为 dbscale_password
客户端登录时获取的服务端随机认证串,代码中为 message
客户端登录时最终发往服务端的认证串,代码中为 to
这次模拟登录获取的服务端新随机认证串,代码中为 new_message
SHA1_CONTEXT sha1_context;
uint8_t hash_dbscale_s2[SHA1_HASH_SIZE];
// get hash_stage2 using the password in mysql.user
hex2octet(hash_dbscale_s2, dbscale_password, SHA1_HASH_SIZE);
uint8_t hash_dbscale_s1[SHA1_HASH_SIZE];
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, (const uint8_t *) message, SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_dbscale_s2, SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, hash_dbscale_s1);
my_crypt((char *) hash_dbscale_s1, (const unsigned char *)hash_dbscale_s1, (const unsigned char *)to, SCRAMBLE_LENGTH);
上面的代码首先通过mysql.user表的password字段算出hash_stage2,然后通过客户端登录时获取的服务端随机认证串 message 和 客户端登录时最终发往服务端的认证串 to, 计算出hash_stage1.
;
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, (const uint8_t *) new_message, SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_dbscale_s2, SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, (uint8_t *) to);
my_crypt(to, (const unsigned char *) to, (const unsigned char *)hash_dbscale_s1, SCRAMBLE_LENGTH);
上面的代码为通过hash_stage1和hash_stage2,安装新的服务端随机认证串,生产新的最终认证串,进行模拟登录。
转载请注明转自高孝鑫的博客
http://blog.sina.com.cn/s/blog_4673e6030101ii8a.html
Mysql Client链接Mysql Server的认证方式
参见:http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Password_functions
我意译一下,大致就是以下内容:
4.0版本之前
1、服务器发送随机字符串(scramble_buff)给客户端.
2、客户端把用户明文密码加密一下,然后将hash加上服务器的随机字符串加密一下变成新的scramble_buff。(参见sql/password.c:scramble()).
3、客户端将加密后的scramble_buff值发给服务端.
4、服务器将mysql.user.Password的值加上原始随机字符串进行加密.
5、服务器比对加密后的hash值和服务端送过来的加密后的scramble_buff.
6、如果一样,则验证成功.
基本就是一个挑战机制。但是注意一点:实质上真正意义上的密码是明文密码的加密hash值; 如果有人知道了这个用户的mysql.user.Password(而不用知道原始明文密码)他就能直接登录服务端.
4.1以后版本
4.1以后数据库保存的密码是用SHA1加密的:SHA1(SHA1(password))
1、服务器发送随机字符串(scramble)给客户端.
2、客户端作如下计算:
stage1_hash = SHA1(明文密码).
token = SHA1(scramble + SHA1(stage1_hash)) XOR stage1_hash
3、客户端将token发送给服务端
4、服务端作如下计算:
stage1_hash = token XOR SHA1(scramble + mysql.user.Password)
5、服务端比对SHA1(stage1_hash)和mysql.user.Password,如果匹配,则认证正确。
注意:SHA1(A+B)意思是SHA1(A字符串连接B字符串).
这次没上一个版本的缺陷了. 有了mysql.user.Password和scramble也不能获得token。因为他没法获得stage1_hash。
但是如果这人有这用户的 mysql.user.Password 及网络上截取的一次完整验证数据, 他也能根据这次截获的token和scramble反解出stage1_hash的值。而由于stage1_hash是不变的,因此下次连接,他获取了新的scramble后,自己加密一下token,送给服务端也能通过验证而连接到服务器.
最后放一个5.1的认证的抓包结果,注意标红的地方:
server > 127.0.0.1.49130: Handshake <proto 10 ver 5.1.41-3ubuntu12.6 thd 55 scramble1EGu9\Aq8_UnI_'@L<*Y >
127.0.0.1.49130 > server: Handshake (new auth) <user root db (null) token6d2c7025c412b997788525b19a5167c89dafcbe max pkt 16777216>
server > 127.0.0.1.49130: OK <fields 0 affected rows 0 insert id 0 warnings 0>
http://www.wofeiwo.com/post/2010-08-20/mysql-clientlian-jie-mysql-serverde-ren-zheng-fang-shi-.html




