Wow, Mangos登录时的SRP6认证

简介: 这个次序应该无关紧要. 这样做的原因是服务器要先发送N, g到客户端, 这样可少一次消息交互.客户端计算公钥A时要用到N, g: A = g^a (隐含对N取模).

以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 )
RFC2954中文翻译 ( http://www.cnpaf.net/rfc/rfc2945.txt )
SRP是什么意思?_百度知道 ( http://zhidao.baidu.com/question/59783252.html )
源码 mangos/src/realmd/AuthSocket.cpp

== 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;        
    }
;
目录
相关文章
|
消息中间件 安全 API
C#实现操作Windows窗口句柄:SendMessage/PostMessage发送系统消息、事件和数据【窗口句柄总结之二】
SendMessage/PostMessage API 可以实现发送系统消息,这些消息可以定义为常见的鼠标或键盘事件、数据的发送等各种系统操作......
8612 1
C#实现操作Windows窗口句柄:SendMessage/PostMessage发送系统消息、事件和数据【窗口句柄总结之二】
|
关系型数据库 MySQL Docker
MySQL 5.7 timestamp类型设置default value为'0000-00-00 00:00:00'报错的解决方法
MySQL 5.7 timestamp类型设置default value为'0000-00-00 00:00:00'报错的解决方法
684 0
|
12月前
|
安全 JavaScript 前端开发
HarmonyOS NEXT~HarmonyOS 语言仓颉:下一代分布式开发语言的技术解析与应用实践
HarmonyOS语言仓颉是华为专为HarmonyOS生态系统设计的新型编程语言,旨在解决分布式环境下的开发挑战。它以“编码创造”为理念,具备分布式原生、高性能与高效率、安全可靠三大核心特性。仓颉语言通过内置分布式能力简化跨设备开发,提供统一的编程模型和开发体验。文章从语言基础、关键特性、开发实践及未来展望四个方面剖析其技术优势,助力开发者掌握这一新兴工具,构建全场景分布式应用。
1038 35
|
负载均衡 数据可视化 AI芯片
DeepSeek模型MOE结构代码详解
其实在DeepSeek-R1爆火之前,DeepSeek V2在我们行业就已经妇孺皆知了,它独特的MOE结构值得研究一下。这篇文章是基于 @ZOMI酱 的2个视频写的,这2个视频讲的很好,建议大家都学习一下:《MOE终于迎来可视化解读!傻瓜都能看懂MoE核心原理!》和《使用昇腾NPU手撕MoE单机版代码!没想到如此简单!》。
620 5
|
小程序
移动医保支付
移动医保支付
1226 0
|
自然语言处理
IEC 61131-3标准编程入门
IEC 61131-3标准编程入门
|
安全 Java 应用服务中间件
基于CAS,实现SSO单点登录,很细
基于CAS,实现SSO单点登录,很细
2271 2
基于CAS,实现SSO单点登录,很细
|
前端开发 JavaScript UED
如何防止接口重复提交?
本文讨论了前端如何防止接口重复提交的问题。主要方法包括:1) 禁用提交按钮,用户点击后立即禁用并显示加载状态,请求完成后恢复;2) 使用防抖或节流技术限制请求发送的频率;3) 生成请求标识符,后端检查已处理过的请求;4) 利用状态管理库(如Redux, Vuex)跟踪请求状态,避免重复提交;5) 接口锁定,通过变量记录请求状态,防止并发请求。这些策略可单独或组合使用,以确保请求的准确性和系统稳定性。
|
C++
[C++] 强制类型转换(dynamic_cast和dynamic_Pointer_cast)
[C++] 强制类型转换(dynamic_cast和dynamic_Pointer_cast)
502 1
|
前端开发 Oracle Java
Bean Searcher v4.3.0 重大更新!
Bean Searcher 是一款专注高级查询的只读 ORM 开源项目。本次更新带来了大家期待已久的功能 ...
662 0