以Mangos代碼為參考, 解析SRP6的原理和實(shí)現(xiàn).
(轉(zhuǎn)載請(qǐng)注明來(lái)源于金慶的專欄)
SRP全稱Secure Remote Password(安全遠(yuǎn)程密碼),是一個(gè)開源認(rèn)證協(xié)議。
SRP簡(jiǎn)化后的原理是:
1. 服務(wù)器不保存密碼或密碼的散列值, 防止字典攻擊.
而只是保存驗(yàn)證因子(verifier).
2. 客戶端和服務(wù)器可以各自計(jì)算出一個(gè)會(huì)話秘鑰(session key), 其值相同. 防止竊聽.
參考:
Wow 服務(wù)器解析 ( http://www.shnenglu.com/Jedimaster/archive/2006/10/14/13674.aspx )
SRP Protocol Design ( http://srp.stanford.edu/design.html )
魔獸世界服務(wù)器端編寫參考資料 ( 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認(rèn)證過(guò)程 ==
1. 客戶端發(fā)送用戶名和版本信息
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];
};
大部份信息用來(lái)決定是否封阻該用戶登錄.
SRP6相關(guān)的只有I, 為用戶名.
SRP6相關(guān)的字段都是按協(xié)議中的符號(hào)定義的.
1.1 _SetVSFields(rI)設(shè)置v, s字段
從數(shù)據(jù)庫(kù)中獲取密碼散列值rI(字段名sha_pass_hash), 應(yīng)該是密碼p,
x = H(s, p)
v = g^x (密碼學(xué)中的計(jì)算一般都是在最后對(duì)大質(zhì)數(shù)N取模: v = g.ModExp(x, N);)
這個(gè)應(yīng)該是驗(yàn)證因子v.
然后v, s存入數(shù)據(jù)庫(kù). x為臨時(shí)值, 用后丟棄.
salt值s是在連接時(shí)設(shè)置的隨機(jī)值.

/**//// Accept the connection and set the s random value for SRP6
void AuthSocket::OnAccept()


{
s.SetRand(s_BYTE_SIZE * 8);
}
s是32字節(jié)長(zhǎng), s_BYTE_SIZE = 32.
安全大質(zhì)數(shù)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可以是一個(gè)安全的素?cái)?shù)
(也就是,一個(gè)類似于N=2q + 1形式的數(shù),同時(shí)q是個(gè)素?cái)?shù))。
而且,g將是一個(gè)以N為模的生成元,
意味著,對(duì)任何X,有0 < X < N,存在一個(gè)值x,使得g^x % N == X。
Mangos保存了密碼p, 是錯(cuò)誤的. 服務(wù)器不應(yīng)該保存密碼或其散列值.
應(yīng)該在創(chuàng)建用戶時(shí), 由客戶端取s值, 計(jì)算v, 將{I, s, v}傳輸?shù)椒?wù)器并保存.
登錄時(shí), 特定用戶的s, v應(yīng)該是固定的, 從數(shù)據(jù)庫(kù)讀取, 而不是每次登錄時(shí)隨機(jī).
1.2 取b值, 計(jì)算B
b.SetRand(19 * 8);
BigNumber gmod=g.ModExp(b, N);
B = ((v * 3) + gmod) % N;
b為19字節(jié)長(zhǎng)的隨機(jī)數(shù). 不知為何是19字節(jié)長(zhǎng), 不是16或32?
b是服務(wù)器的臨時(shí)秘鑰, B為臨時(shí)公鑰.
B = kv + g^b
在SRP6中k=3, 而在最新的SRP6a中, k=H(N, g).
1.3 服務(wù)端返回 CMD_AUTH_LOGON_CHALLENGE 數(shù)據(jù)包
返回的數(shù)據(jù)結(jié)構(gòu)沒(méi)有用struct定義, 只是用ByteBuffer依次填入數(shù)據(jù).
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 是服務(wù)器發(fā)給客戶端的SRP6參數(shù).
unk3是個(gè)16字節(jié)長(zhǎng)的隨機(jī)數(shù), 不知道干什么用的. (unknown3?)
按SRP6的協(xié)議, 應(yīng)該是客戶端先發(fā)送自己的用戶名和公鑰(I, A), 但在Mangos中,
是服務(wù)器在沒(méi)有收到A時(shí)就發(fā)送鹽值和自己的公鑰(s, B).
這個(gè)次序應(yīng)該無(wú)關(guān)緊要. 這樣做的原因是服務(wù)器要先發(fā)送N, g到客戶端, 這樣可少一次消息交互.
客戶端計(jì)算公鑰A時(shí)要用到N, g: A = g^a (隱含對(duì)N取模).
2. 客戶端發(fā)送 CMD_AUTH_LOGON_PROOF 數(shù)據(jù)包請(qǐng)求驗(yàn)證
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 計(jì)算u, S, K
u = sha(A, B);
S = (A * (v.ModExp(u, N))).ModExp(b, N);
K = H(S);
其中K分奇偶位分別計(jì)算, 應(yīng)該不是SRP的方法, 不知是否會(huì)降低散列效果.
2.2 計(jì)算M并與M1比較驗(yàn)證
M = sha(sha(N) xor sha(g), sha(I), s, A, B, K)
2.3 M1驗(yàn)證通過(guò)后計(jì)算M2, 用于客戶端驗(yàn)證
M2 = sha(A, M, K)
2.4 服務(wù)端發(fā)回 CMD_AUTH_LOGON_PROOF
包含了 SRP6 驗(yàn)證的結(jié)果 M2
struct AUTH_LOGON_PROOF_S

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