posts - 311, comments - 0, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理
在MaNGOS-Zero中认证登录服务器是已独立的进程存在的,名字叫realmd,这样命名可能是和游戏client端根目录下的realmlist.wtf文件相对应。realmd的主要工作是:检查登录用户的合法性,并在合法的情况下完成通信密钥的交换,最后把游戏逻辑服务器的地址信息列表传给client端。

realmd认证的基本流程如下:

图中的(1)~(7)详细过程如下:

(1)Client启动后立即尝试连接realmlist.wtf文件中指定的认证服务器,就发送LogonChallenge给认证服务器realmd。协议结构如下:

   1: typedef struct AUTH_LOGON_CHALLENGE_C
   2: {
   3:     uint8   cmd;                //Cmd is the command/operation code of the packet. Always 0 for this message
   4:     uint8   error;
   5:     uint16  size;            //Size of the remaining part of the message
   6:     uint8   gamename[4];    //4 byte C-String, containing the String "WoW\0"
   7:     uint8   version1;            //Major version number of the client ( 1 for 1.12.1 )
   8:     uint8   version2;            //Minor version number of the client ( 12 for 1.12.1 )
   9:     uint8   version3;            //Patchlevel version number of the client ( 1 for 1.12.1 )
  10:     uint16  build;            //Build number of the client. ( 5875 for 1.12.1 )
  11:     uint8   platform[4];        //Platform the client is running on, reversed C-String ( "68x\0" for x86 )
  12:     uint8   os[4];            //OS the client is running on, reversed C-String ( "niW\0" for Windows )
  13:     uint8   country[4];        //Locale of the client, reversed C-String ( "SUne" for enUS )
  14:     uint32  timezone_bias;
  15:     uint32  ip;                //IP address of the client in binary format
  16:     uint8   I_len;            //Length of the Identity ( user name ) in characters
  17:     uint8   I[1];                //The Identity string ( user name )
  18: } sAuthLogonChallenge_C;

uint8 I[1]是一个unsigned char的变长数组的头指针,保存user name,服务器端保存的时候要把\0后面的东西去掉,防止SQL injection。(1.1)执行“SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '%s'”语句从数据库中读取数据。

realmd收到sAuthLogonChallenge_C协议后处理步骤如下:

1) 检查该ip是否被封禁,如果是发送相应的错误

2) 查询数据库中是否有该账户,如果没有返回相应的错误

3) 查看最后一次登录ip与账户是否绑定,如果绑定对比当前ip与last_ip是否一致

4) 检查该账号是否被封禁,如果是发送相应的错误信息

5) 获取用户名密码,开始SRP6计算,参见本文最下方的附录《MaNGOS所使用的SRP6算法》

6) _accountSecurityLevel,保存用户的权限等级,普通用户、GM、admin等等

7) 本地化:根据_localizationName的名字找对应的.mpq文件所在的位置比如enUS,zhTW,zhCN

 

(2) realmd向client发送challenge,协议如下:

   1: typedef struct
   2: {
   3:     uint8   cmd;            //Cmd is the command/operation code of the packet. Always 0 for this message.
   4:     uint8   error;
   5:     uint8   unk2;        //random value
   6:     uint8   B[32];        //B is an SRP6 value. It is the server's public value.
   7:     uint8   g_len;        //Length of the SRP6 g value we send the client in bytes. Always 1
   8:     uint8   g[1];            //The SRP6 g value we send the client. Always 7
   9:     uint8   N_len;        //Lenght of the SRP6 N value we send the client. Always 32
  10:     uint8   N[32];        //The SRP6 N value we send the client. 
  11:     uint8   s[32];        //The SRP6 s value
  12:     uint8   unk3[16];        //a randomly generated 16 byte value.
  13:     uint8   unk4;        //a single byte 0.
  14: } sAuthLogonChallenge_S;

 

 

(3)客户端的Handshaking阶段,client为了回应realmd的challenge消息,证明client的密码和server上保存的密码一致,需要计算M1值,并发送给realmd,发送的协议如下:

   1: typedef struct AUTH_LOGON_PROOF_C
   2: {
   3:     uint8   cmd;                    //Cmd is the command/operation code of the packet. Always 1 for this message
   4:     uint8   A[32];                //The client SRP6 A value ( public client value ).
   5:     uint8   M1[20];                //The client's SRP6 M value.待比较的最终值
   6:     uint8   crc_hash[20];            //doesn't seem to be used
   7:     uint8   number_of_keys;        //It also seems to be always 0
   8:     uint8   securityFlags;                 // 0x00-0x04
   9: } sAuthLogonProof_C;

计算公式如下,各个变量的含义请查看本文最下方的附录《MaNGOS所使用的SRP6算法》

A = g^a mod N //a为19个字节的随机数

B = (kv + g^b) mod N

u = H(A, B)

x = H(s, p)

S = (B - kg^x) ^ (a + ux)

K = H(S)

M = H(H(N) xor H(g), H(I), s, A, B, K)

上诉的哈希函数H都使用的是SHA1

 

(4)realmd收到client发来的Proof协议,(a)开始检查版本是否允许,(b) 使用SRP6计算M值,与client传过来的M1做对比,if (!memcmp(M.AsByteArray(), lp.M1, 20)) { } (c) 如果M值相等计算K —— 即sessionkey,并最终保存到数据库。

 

(5)验证成功,realmd向client发送server的logon proof

   1: typedef struct AUTH_LOGON_PROOF_S
   2: {
   3:     uint8   cmd;
   4:     uint8   error;
   5:     uint8   M2[20];            //The SRP6 M values,M = H(H(N) xor H(g), H(I), s, A, B, K)
   6:     uint32  unk1;            // AccountFlags (trial 0x08, ProPass 0x800000, gm 0x01)
   7:     uint32  unk2;            // SurveyId
   8:     uint16  unk3;            // some flags (AccountMsgAvailable = 0x01)
   9: } sAuthLogonProof_S;

 

 

(6)_HandleRealmList,查询realmcharacters表获得角色和realmid的对应关系,然后可以在RealmList类里通过realmid找到对应的表项,RealmList在初始化的时候再realmlist表里读取数据放在内存里使用:“SELECT id, name, address, port, icon, realmflags, timezone, allowedSecurityLevel, population, realmbuilds FROM realmlist WHERE (realmflags & 1) = 0 ORDER BY name ” 查询逻辑服务器。

 

(7)发送realmlist给客户端,客户端选择对应的服务器后点击enter world的时,会主动断开和realmd的连接。

 

总结:整个登录认证过程主要干了如下几件事

(a)使用公钥算法验证client的合法性。

(b)client合法的情况下,计算sessionkey并保存在MySQL里,以备后面的逻辑服务器使用。

(c)验证通过后,把逻辑服务器列表发给client。

其中最为重要的就是在验证合法性、交换密钥的过程中,如何在传递的协议里隐藏client的密钥,使其在被截获的情况下也无法被黑客解析,这就只能依仗密码学了。WOW所使用的加密算法是SRP6,详细的介绍如下:

 

 

 

附录:MaNGOS所使用的SRP6算法

SRP全称Secure Remote Password(安全远程密码).使用SRP的客户机和服务器不会在网络上传送明文密码,这样可以消除直接密码嗅探行为,另外使用高强度的算法保证了不可能使用字典法攻击嗅探到的数据[1]。SRP协议的描述见 http://srp.stanford.edu/design.html

SRP6名词解释:

N - A large safe prime (N = 2q+1, where q is prime) All arithmetic is done modulo N.

g - A generator modulo N

k - Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)

s - User's salt

I - Username

p - Cleartext Password

H() - One-way hash function

^ - (Modular) Exponentiation

u - Random scrambling parameter

a,b - Secret ephemeral values

A,B - Public ephemeral values

x - Private key (derived from p and s)

v - Password verifier

对应MaNGOS的计算方法和变量含义:

sha_pass_hash:使用这个函数进行Sha1Hash哈希std::string AccountMgr::CalculateShaPassHash(std::string& name, std::string& password)后得到的值。

N: A large safe prime (N = 2q+1, where q is prime), All arithmetic is done modulo N. 在MaNGOS里N被设成N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");

s: 32个字节的随机数,算过一次后保存在数据库里。

x: 计算v值使用到的x,即私钥是s与数据库里的sha_pass_hash的倒置的Sha1Hash

v: g^x mod N,验证密码时使用Password verifier,算过一次后保存在数据库里。

g: g = 7

b: 为19个字节的随机数,每次client连接认证的时候重新生成。

a: 19个字节的随机数,由客户端产生。

B: 公钥, gmod = g^b, N , B = ((v * 3) + gmod) % N

K: 认证通过后最终保存到数据库里的sessionkey,(1)计算A 客户端公钥A = g^a mod N a为19为随机数,(2)计算x,x = sha(s, I),I = sha(“username : password”); (3)计算u,u = sha(A, B) //(服务公钥,客户公钥);(4)计算S,S = (B - g^x*3)^(a+u*x); (5)计算K,S为32位,K为40位是 sha(s奇部分)20位, sha(s偶部分)20位的奇偶交错组合。

M: 20个字节的数,用于与client传过来的最终结果M1进行对比,如果M和M1每个字节都相等则验证通过。计算方法:t3 = sha(N)[i] ^ sha(g)[i],t4 = sha(username),M = sha(t3,t4,s,A,B,K)

k: k = 3

总结:服务端,客户端各自计算S的公式个不同,公钥部分服务端用A,b, 客户端用B,a 但其计算结果相同………私钥x被很好的隐藏了,因为想从M获得x是基本不可能的!基于数学难题基础上的加密算法确实很给力。

 

References

[1]http://blog.csdn.net/lfhfut/article/details/1124768

[2]http://www.arcemu.org/wiki/

[3]http://hi.baidu.com/zyy503950958/blog/item/3addce90a91eda81a877a463.html