Battle.net SRP (by iago)
The Battle.net SRP is a variation the standard SRP protocol, with a few minor and several useless changes. There are 5 important packets I'll go over, and then I'll discuss each variable and how we come up with it.
- SID_AUTH_ACCOUNTCREATE (C -> S)
- (byte) s
- (byte) v
- (byte) Username
- SID_AUTH_ACCOUNTLOGON (C -> S)
- (byte) A
- (ntstring) C
- SID_AUTH_ACCOUNTLOGON (C <- S)
- (dword) status
- (byte) s
- (byte) B
- SID_AUTH_ACCOUNTLOGONPROOF (C -> S)
- SID_AUTH_ACCOUNTLOGONPROOF (C <- S)
H() is standard SHA-1
% is Modulo Division
* is Multiplication
- is Subtraction
+ is Addition
Your username in upper case
Your password in upper case
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:
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 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:
a is a sessional private key. It is a random integer lower than N, and is regenerated for each log in.
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 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 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();
byte hash = mdx.digest();
mdx = getSHA1();
hash = mdx.digest();
v is the "Password Verifier". It is basically a private key, which is derived from g, x, and is modulo N:
v = gx % N
A sample implementation of this might be:
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 = ga % N
A sample implementation for this might be:
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; // Allocate 4 bytes for U
u = hash;
u = hash;
u = hash;
u = hash;
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 * (vu % 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 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:
Here is my Java implementation:
- 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.
byte K = new byte; // Create the buffer for K
byte hbuf1 = new byte; // Create the 2 buffers to each hold half of S
byte hbuf2 = new byte;
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.
M 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:
M = H(I, H(C), s, A, B, K)
My Java implementation looks like this:
MessageDigest totalCtx = getSHA1();
M1 = totalCtx.digest();
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!
I feel obligated to thank the following people:
- Maddix, UserLoser, 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 :)
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.