金庆的专栏

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  423 随笔 :: 0 文章 :: 454 评论 :: 0 Trackbacks
Wow, Mangos登录时的SRP6认证

以Mangos代码为参考, 解析SRP6的原理和实现.
(转载请注明来源于金庆的专栏)

SRP全称Secure Remote Password(安全远程密码),是一个开源认证协议。

SRP简化后的原理是:
1. 服务器不保存密码或密码的散列值, 防止字典攻击.
   而只是保存验证因子(verifier).
2. 客户端和服务器可以各自计算出一个会话秘钥(session key), 其值相同. 防止窃听.

参考:
Wow 服务器解析 ( http://www.cppblog.com/Jedimaster/archive/2006/10/14/13674.aspx )
SRP Protocol Design ( http://srp.stanford.edu/design.html )
魔兽世界服务器端编写参考资料 ( http://www.asstudio.de/wow/wow.htm )
源码 mangos/src/realmd/AuthSocket.cpp
RFC2954中文翻译 ( http://www.cnpaf.net/rfc/rfc2945.txt )
SRP是什么意思?_百度知道 ( http://zhidao.baidu.com/question/59783252.html )

== Mangos SRP6认证过程 ==


1. 客户端发送用户名和版本信息


    struct AUTH_LOGON_CHALLENGE_C
    {
        uint8   cmd;
        uint8   error;
        uint16  size;
        uint8   gamename[4];
        uint8   version1;
        uint8   version2;
        uint8   version3;
        uint16  build;
        uint8   platform[4];
        uint8   os[4];
        uint8   country[4];
        uint32  timezone_bias;
        uint32  ip;
        uint8   I_len;
        uint8   I[1];
    };

大部份信息用来决定是否封阻该用户登录.
SRP6相关的只有I, 为用户名.
SRP6相关的字段都是按协议中的符号定义的.

1.1 _SetVSFields(rI)设置v, s字段


从数据库中获取密码散列值rI(字段名sha_pass_hash), 应该是密码p,
x = H(s, p)
v = g^x (密码学中的计算一般都是在最后对大质数N取模: v = g.ModExp(x, N);)
这个应该是验证因子v.
然后v, s存入数据库. x为临时值, 用后丢弃.

salt值s是在连接时设置的随机值.
/// Accept the connection and set the s random value for SRP6
void AuthSocket::OnAccept()
{
    s.SetRand(s_BYTE_SIZE * 8);
}
s是32字节长, s_BYTE_SIZE = 32.

安全大质数N, 及其生成元g, 是固定的:
    N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
    g.SetDword(7);

RFC2945:
   For
   maximum security, N should be a safe prime (i.e. a number of the form
   N = 2q + 1, where q is also prime).  Also, g should be a generator
   modulo N (see [SRP] for details), which means that for any X where 0
   < X < N, there exists a value x for which g^x % N == X.

为了最大化安全性,N可以是一个安全的素数
(也就是,一个类似于N=2q + 1形式的数,同时q是个素数)。
而且,g将是一个以N为模的生成元,
意味着,对任何X,有0 < X < N,存在一个值x,使得g^x % N == X。

Mangos保存了密码p, 是错误的. 服务器不应该保存密码或其散列值.
应该在创建用户时, 由客户端取s值, 计算v, 将{I, s, v}传输到服务器并保存.
登录时, 特定用户的s, v应该是固定的, 从数据库读取, 而不是每次登录时随机.

1.2 取b值, 计算B

    b.SetRand(19 * 8);
    BigNumber gmod=g.ModExp(b, N);
    B = ((v * 3) + gmod) % N;

b为19字节长的随机数. 不知为何是19字节长, 不是16或32?
b是服务器的临时秘钥, B为临时公钥.
B = kv + g^b
在SRP6中k=3, 而在最新的SRP6a中, k=H(N, g).

1.3 服务端返回 CMD_AUTH_LOGON_CHALLENGE 数据包

返回的数据结构没有用struct定义, 只是用ByteBuffer依次填入数据.

    ByteBuffer pkt;
    
    pkt << (uint8) AUTH_LOGON_CHALLENGE;
    pkt << (uint8) 0x00;
    pkt << (uint8)REALM_AUTH_SUCCESS;
    pkt.append(B.AsByteArray(32), 32);   // 32 bytes
    pkt << (uint8)1;
    pkt.append(g.AsByteArray(), 1);
    pkt << (uint8)32;
    pkt.append(N.AsByteArray(), 32);
    pkt.append(s.AsByteArray(), s.GetNumBytes());   // 32 bytes
    pkt.append(unk3.AsByteArray(), 16);
    pkt << (uint8)0;                    // Added in 1.12.x client branch
   
    SendBuf((char const*)pkt.contents(), pkt.size());

B, g, N, s 是服务器发给客户端的SRP6参数.
unk3是个16字节长的随机数, 不知道干什么用的. (unknown3?)

按SRP6的协议, 应该是客户端先发送自己的用户名和公钥(I, A), 但在Mangos中,
是服务器在没有收到A时就发送盐值和自己的公钥(s, B).
这个次序应该无关紧要. 这样做的原因是服务器要先发送N, g到客户端, 这样可少一次消息交互.
客户端计算公钥A时要用到N, g: A = g^a (隐含对N取模).


2. 客户端发送 CMD_AUTH_LOGON_PROOF 数据包请求验证

    struct AUTH_LOGON_PROOF_C
    {
        uint8   cmd;
        uint8   A[32];
        uint8   M1[20];
        uint8   crc_hash[20];
        uint8   number_of_keys;
        uint8   unk;  // Added in 1.12.x client branch
    };

A, M1有用

2.1 计算u, S, K

    u = sha(A, B);
    S = (A * (v.ModExp(u, N))).ModExp(b, N);
    K = H(S);
其中K分奇偶位分别计算, 应该不是SRP的方法, 不知是否会降低散列效果.

2.2 计算M并与M1比较验证

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

2.3 M1验证通过后计算M2, 用于客户端验证

    M2 = sha(A, M, K)

2.4 服务端发回 CMD_AUTH_LOGON_PROOF

包含了 SRP6 验证的结果 M2

    struct AUTH_LOGON_PROOF_S
    {                       
        uint8   cmd;        
        uint8   error;      
        uint8   M2[20];     
        uint32  unk1;       
        uint32  unk2;       
        uint16  unk3;       
    };

 
posted on 2009-04-10 13:06 金庆 阅读(4060) 评论(0)  编辑 收藏 引用 所属分类: 1. C/C++2. 网游开发

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理