WARNING: Wiki content is an archive, no promise about quality!
The Battle.net SRP is a variation the standard SRP protocol, with a few minor changes. There are several important packets I'll go over, and then I'll discuss each variable and how we come up with it, including a sample implementation in Java. As far as I know, me, Maddox, and TheMinistered are the first (and only) to reverse this publicly, so enjoy!
Packets
SID_AUTH_ACCOUNTCREATE (C -> S)
SID_AUTH_ACCOUNTLOGON (C -> S)
SID_AUTH_ACCOUNTLOGON (C <- S)
SID_AUTH_ACCOUNTLOGONPROOF (C -> S)
- (byte[20]) M1
SID_AUTH_ACCOUNTLOGONPROOF (C <- S)
- (byte[20]) M2
SID_AUTH_ACCOUNTCHANGE (C -> S)
- (byte[32]) A (for old password)
SID_AUTH_ACCOUNTCHANGE (C <- S)
SID_AUTH_ACCOUNTCHANGEPROOF (C -> S)
SID_AUTH_ACCOUNTCHANGEPROOF (C <- S)
- (byte[20]) M2 (for old password)
SID_AUTH_ACCOUNTUPGRADE (C -> S)
- [blank]
SID_AUTH_ACCOUNTUPGRADE (S -> C)
- (dword) status
- (dword) server token
SID_AUTH_ACCOUNTUPGRADEPROOF (C -> S)
SID_AUTH_ACCOUNTCHANGEPROOF (S -> C)
- (dword) status
- (byte[20]) M2
Functions
H()
Standard SHA-1
%
Modulo Division
*
Multiplication
-
Subtraction
+
Addition
Variables
C
Your username in upper case
P
Your password in upper case
N
N IS THE "modulus". It is a large 32-byte unsigned integer, and all calculations are done modulus N. That means no value in SRP will ever go over N. Its value is:
Decimal: 112624315653284427036559548610503669920632123929604336254260115573677366691719
Hex: 0xF8FF1A8B619918032186B68CA092B5557E976C78C73212D91216F6658523C787\
g
g is the "generator" variable. It is used to generate public keys, based on private keys. The value is "47" in decimal, or 0x2F in hex.\
I
I is not in the original SRP. I actually invented it to optimize Battle.net SRP, since it was being calculated. The way to calculate it, if you need to, is H(g) xor H(N). What this means is that you calculate the SHA-1 values of both g and N, then xor each byte of them together. The value you come out with is:
Decimal: 1415864289515498529999010855430909456942718455404
Hex: F8018CF0A425BA8BEB8958B1AB6BF90AED970E6C\
a
a is a sessional private key. It is a random integer lower than N, and is regenerated for each log in.
B
B is the server's temporary public key, derived from b (which I won't bother showing here). It is sent to the client in SID_AUTH_ACCOUNTLOGON.
s
s is the "salt" value. You choose it randomly when you create your account, and then it never changes. Every time you log into Battle.net, it's sent back to you (in SID_AUTH_ACCOUNTLOGON), and is used to help scramble the password.
x
x is a private key that is derived from s, C, P. Note that in standard SRP, it's only derived from the s and P. The formula is:
x = H(s, H(C, ":", P));
Which means that you hash the salt along with the hash of the username, a colon, and the password. Here is a sample implementation of it:
MessageDigest mdx = getSHA1();
mdx.update(username.getBytes());
mdx.update(":".getBytes());
mdx.update(password.getBytes());
byte []hash = mdx.digest();
mdx = getSHA1();
mdx.update(salt);
mdx.update(hash);
hash = mdx.digest();
v
v is the "Password Verifier". It is basically a private key, which is derived from g, x, and is modulo N:
v = g^x^ % N
A sample implementation of this might be:
g.modPow(x, N);
A
A is a public key that exists only for a single login session. It is derived from g, a, and of course is modulo N:
A = g^a^ % N
A sample implementation for this might be:
g.modPow(a, N);
u
u is used to help "scramble" the private key. In regular SRP, it's generated by the server and sent to the client along with B. However, in Battle.net SRP, it is actually equal to the first 4 bytes of H(B). Here is a sample implementation, which is, in Java, pretty yucky:
byte []hash = getSHA1().digest(B); // Get the SHA-1 digest of B
byte []u = new byte[4]; // Allocate 4 bytes for U
u[0] = hash[3];
u[1] = hash[2];
u[2] = hash[1];
u[3] = hash[0];
S
S is where a lot of the magic happens. It is generated by both the client and the server, using different values and a different formula, and it ends up as the same value. On the client, it's derived from B, v, a, u, x, and is, of course, modulo N. On the server side, it's derived from A, v, u, and B. The respective formulas are:
(client) S = ((N + B - v) % N)^(a\ +\ ux)^ % N
(server) S = (A * (v^u^ % N))^b^ % N
If you really enjoy math, you can go ahead and figure out how these work out to the same value. It's actually a pretty interesting equation. Here is my Java implementation:
S_base = N.add(B).subtract(v).mod(N);
S_exp = a.add(get_u(B).multiply(x));
S = S_base.modPow(S_exp, N);
K
K is a value that is based on S, and is generated by both the client and the server as proof that they actually know the value of S. In standard SRP, it's just H(S); however, in Battle.net SRP, it's fairly complicated:\
- 2 buffers are created; one is the even bytes of S, and the other is the odd bytes of S.
- Each buffer is hashed with SHA-1.
- The even bytes of K are the even bytes of S, and the odd bytes of K are the odd bytes of S.
Here is my Java implementation:
byte []K = new byte[40]; // Create the buffer for K
byte []hbuf1 = new byte[16]; // Create the 2 buffers to each hold half of S
byte []hbuf2 = new byte[16];
for(int i = 0; i < hbuf1.length; i++) // Loop through S
{
hbuf1[i] = S[i * 2];
hbuf2[i] = S[(i * 2) + 1];
}
byte []hout1 = getSHA1().digest(hbuf1); // Hash the values
byte []hout2 = getSHA1().digest(hbuf2);
for(int i = 0; i < hout1.length; i++)
{
K[i * 2] = hout1[i]; // Put them into K
K[(i * 2) + 1] = hout2[i];
}
Pretty stupid, if you ask me, but that's life.
M1
M1 (or M[1] is the proof that you actually know your own password. In standard SRP, it's derived from A, B, and K. Of course, that's too simple for Blizzard, so Battle.net SRP is derived from I, H(C), s, A, B, and K. Since I is constant, and C and s are sent across the network, I'm pretty sure that the change adds no security, but as long as it makes them feel better. The formula is actually very simple, at least:
M1 = H(I, H(C), s, A, B, K)
My Java implementation looks like this:
MessageDigest totalCtx = getSHA1();
totalCtx.update(I);
totalCtx.update(getSHA1().digest(username.getBytes()));
totalCtx.update(s);
totalCtx.update(A);
totalCtx.update(B);
totalCtx.update(K);
M1 = totalCtx.digest();
M2
M2 (or M[2]) is the proof that the server actually knows your password. It is derived by hashing the values of A, M1, and K. When the server sends it back, you should verify it to make sure that the server actually knows your password (if it doesn't, then there's likely an attack going on). The formula is:
M2 = H(A, M1, K)
My Java implementation looks like this:
MessageDigest shaM2 = getSHA1();
shaM2.update(A);
shaM2.update(M);
shaM2.update(K);
M2 = shaM2.digest();
Conclusion
I've went over all the packets that I use in SRP, so there should be more than enough there to get you going. Good luck!
-Ron
Credits
I have to thank the following people:
* Maddox, SneakCharm -- Reverse engineering the code with me
* Arta -- For convincing me to figure out what it did, and get it on BNetDocs
* Cloaked - For actually reading it and pointing out mistakes :)\
Legal stuff
All information on this page is public domain. If for any reason you want to copy/use this, feel free and have fun. All software and source directly distributed by me is public domain, and may be used in any way. Any copyrights I use (Particularely Starcraft, Brood War, Diablo, Warcraft, and Blizzard) are copyrights of their respective owners (in this case, Blizzard). Please respect all copyrights, and enjoy any public domain source code and software.