The authentication protocol for authenticating with a World of Warcraft (WoW) server is widely available but not very well documented. Most documentation is spread across forums or just directly links to RFC2945, the 8 page, 20 year old, very broad and unspecific RFC that describes SRP6 in relatively academic terms.

The goal of this article is to be an approachable, complete guide to implementing the protocol specifically for WoW 1.12 without requiring external references.

The article starts out by giving a high level overview of the protocol and the specifics of the WoW implementation before giving a function-by-function implementation guide, including files for verification and examples of implementation in Rust, C++ and Elixir.

Important terms have been highlighted with **bold**. The exact definitions of these will come later in the article.

This article will only cover the SRP6 protocol, not the network aspects of authenticating with a client.

**This is not intended to be a guide for production usage. I have no cryptographic experience and essentially stumbled upon the solutions by accident.
SRP6 is also not very widely used, possibly for a reason.
Use this only for interacting with legacy World of Warcraft game clients.**

## Ensure that you actually need to implement this from scratch

There is little reason to implement this from scratch if you can just use an existing library for it.

These are the libraries that I know of, if you want yours added to the list send me an email. There are more implementations linked later in the article, but they are part of applications and can’t be used as libraries.

Name and link | Author | Language | Comment |
---|---|---|---|

wow_srp | Gtker | Rust | |

wow_srp_python | Gtker | Python | Python bindings to wow_srp. |

wow_srp_csharp | Gtker | C# | Native .NET Standard 2.1 compatible. |

wow_srp_c | Gtker | C/C++ | C/C++ bindings to wow_srp. |

wow_srp6 | oiramario | Python | Native Python implementation. |

go-wow-srp6 | Kangaroux | Go | Native Go implementation. |

## SRP6 Overview

SRP6 is a protocol for proving that a client and server both know the same specific password without having to send it over the network. It does this via various mathematical tricks, none of which are important to us.

When a new account is added, the server generates a random **salt** value and calculates
a **password verifier** before saving the **username**, **salt** and **password verifier** to the database.

When authenticating, the protocol works in 5 different stages:

- The client sends their
**username**to the server. - The server retrieves the
**password verifier**and**salt**from the database, then it randomly generates a**private key**which it uses to calculate a**public key**. It then sends the**public key**and**salt**to the client, as well as a**large safe prime**and a**generator**. - The client generates a
**private key**and calculates a**public key**. Then it calculates a**session key**and**client proof**. It then sends both the**public key**and**client proof**to the server. - The server calculates a
**session key**, then calculates the**client proof**on its own and compares it to the one the client sent. If they are identical the client and server have used the same password for their calculations. The server then sends a**server proof**to the client to prove that it also knows the correct password. - If the client gets an identical
**server proof**it is now authenticated and will send the “Send Realmlist” packet to ask the server to send a realmlist. - If the client loses connection, it can reconnect by proving that it knows the original session key. More on this in the Reconnecting section.

The above description can also be seen as a sequence diagram in figure 1.

There are some important conceptual points to remember:

- The
**private keys**are never sent over the network. Only the client will know what the**client private key**is, and only the server will know what the**server private key**is. Both values are chosen completely at random and are not identical. - The
**public keys**are sent over the network and are not identical. - The
**session keys**are calculated by both the client and the server based on different variables. These keys are identical and the**client proof**and**server proof**prove to the other part that they both have the same**session key**without saying what the key is. - The
**client proof**and**server proof**are calculated using the public information and their respective**session keys**. This means that the client calculates a**client proof**, then the server calculates a**client proof**using the same algorithm and if the**client proofs**match the**session keys**used are identical. The same happens for the**server proofs**.

Reconnecting and packet header encryption are described in their own sections.

## Important Knowledge Before Starting

Before we dive into the implementation itself, there are a few things you should know that would ease your implementation.

### Integers

You will need to convert an array of bytes to a number, this is often called a `BigInt`

, `BigInteger`

or arbitrary size integer.
You might need a third party library for this although some languages do have standard library support for this.

The integers should be **signed and positive**.

#### Modulo vs Remainder

It’s important that your `BigInteger`

implementation uses modulo rather than the remainder for `mod_pow`

.
With remainder, `(-2)^3 % 9 = -8`

while with modulo `(-2)^3 % 9 = 1`

.
Test your `BigInteger`

implementation and if it uses remainder, add the **large safe prime** to the result to get the correct result.
For example:

```
large_safe_prime = 9
a = -2
b = 3
result = a.mod_pow(b, large_safe_prime)
# if result == 1 you don't need to do anything else
# if result == -8 you need to create a wrapper around your mod_pow like so:
def real_mod_pow(a, b, large_safe_prime):
c = a.mod_pow(b, large_safe_prime)
if c < 0:
c += large_safe_prime
return c
result = real_mod_pow(a, b, large_safe_prime)
# result is now 1
```

#### Convertion of hexadecimal strings to byte arrays

When using the provided examples you will need to convert a hexadecimal string to arrays of bytes. It’s important to understand the difference between a byte array of a hexadecimal integer, and a byte array of a string.

Hexadecimal strings can be thought of as containing a byte every 2 characters.
So the string `FF`

would be an array with a single element containing the value `255`

(`0xFF`

), while the string `FFEE`

would be an array with two elements containg the value `255`

(`0xFF`

) and the value `238`

(`0xEE`

).

A byte array of a string are the literal values that represent the characters in the string, while a byte of array of an integer are the individual components of the integer.
So the byte array of the hexadecimal string `FF`

would contain two elements with the value `70`

(0x46) and the value `70`

, since these are the ASCII/UTF-8 values of the `F`

character.

Consider two the functions `HexToBytes`

and `StringToBytes`

.
`HexToBytes`

converts a hexadecimal string to an array of the byte values while `StringToBytes`

converts any string to an array of the character values.

If you read one of the examples and get the hexadecimal string `ABCDEF`

you can test whether you have a byte array of the string or the byte values by checking the length of the returned by array.

The array returned by `HexToBytes`

for the string `ABCDEF`

would contain 3 elements (`[ 0xAB, 0xCD, 0xEF ]`

), while the array returned by `StringToBytes`

for the string `ABCDEF`

would contain 6 elements (`[ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 ]`

).

### Endianness

All operations are performed on **little endian** arrays.
This means that if your arbitrary size integer has generic
`as_bytes()`

, `to_bytes()`

or `from_bytes()`

functions, you will need to investigate which endianness it expects and outputs.

To visualize endianness consider the hexadecimal (base/radix 16) number `0xABCD`

(43981 in decimal (base/radix 10)).

Most hexadecimal string parsers would consider the number to be in big endian representation with the most significant digit `A`

to the far left representing the digit that would affect the number most if changed, in the same way that `1`

is the most significant digit in `100`

(one hundred).

If you convert the number to big endian bytes the most significant byte, `AB`

, is in the first position and the rest follow along.
In little endian bytes the least significant byte, `CD`

, is the in the first position followed by the second least significant byte, `AB`

in this case.

See the psuedo code below to build a mental model of endianness.

```
BigInt value = 0xABCD
byte[] big_endian = { 0xAB, 0xCD }
byte[] little_endian = { 0xCD, 0xAB }
```

To test your `BigInt`

implementation, try to load up the hexadecimal number `ABCD`

and get it as bytes.
The Rust `num_bigint`

library has both `to_bytes_le()`

and `to_bytes_be()`

so there’s no confusion.
Make a note of whether the bytes you get are little endian or big endian.

```
use num_bigint; // 0.4.0
use num_traits::Num;
fn main() {
let number = num_bigint::BigInt::from_str_radix("ABCD", 16).unwrap();
dbg!(&number.to_bytes_be()); // Outputs [ 171, 205 ] which is [ 0xAB, 0xCD ] in hex
dbg!(&number.to_bytes_le()); // Outputs [ 205, 171 ] which is [ 0xCD, 0xAB ] in hex
}
```

Remember that there’s a different between the string representation and the literal bytes representation. A hexadecimal string is just a human readable representation of the value, the value of the characters that make up the string is not the same.

### Padding

All operations are performed on padded arrays, the sizes for different types can be seen below. Do not worry about understanding why the different sizes are chosen, most are simply the result of the client not being able to accept higher values or having fixed sized fields.

Type | Size (bytes) | Reason |
---|---|---|

Session Key | 40 | Two SHA-1 hashes appended are always 40 bytes. |

Server and Client Proof | 20 | Result of SHA-1 hash is always 20 bytes. |

Public Key | 32 | Packet field is always 32 bytes. |

Private Key | 32 | Arbitrarily chosen. |

Salt | 32 | Packet field is always 32 bytes. |

Password Verifier | 32 | Result of modulus large safe prime is always 32 bytes or less. |

Large Safe Prime | 32 | Largest value client will accept. Any larger can result in public key not fitting into packet field. |

S Key | 32 | Result of modulus large safe prime is always 32 bytes or less. |

Reconnect Seed Data | 16 | Packet field is always 16 bytes for both client and server. |

Generator | 1 | There is no reason to have a generator value above 255. |

### Hashing

All operations use SHA-1 which produces an output of 20 bytes.

To test that your SHA-1 implementation works correctly, ensure that hashing the string `test`

leads to the output of `a94a8fe5ccb19ba61c4c0873d391e987982fbbd3`

, and that hashing the bytes `0x53 0x51`

leads to `0c3d7a19ac7c627290bf031ec3df76277b0f7f75`

.

### Constants

The **large safe prime** should always be `0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7`

(big endian string).
The **large safe prime** can be expressed as a **little endian** Rust array like

```
pub const LARGE_SAFE_PRIME_LITTLE_ENDIAN: [u8; 32] = [
0xb7, 0x9b, 0x3e, 0x2a, 0x87, 0x82, 0x3c, 0xab,
0x8f, 0x5e, 0xbf, 0xbf, 0x8e, 0xb1, 0x01, 0x08,
0x53, 0x50, 0x06, 0x29, 0x8b, 0x5b, 0xad, 0xbd,
0x5b, 0x53, 0xe1, 0x89, 0x5e, 0x64, 0x4b, 0x89
];
```

or as a **big endian** Rust array like

```
pub const LARGE_SAFE_PRIME_BIG_ENDIAN: [u8; 32] = [
0x89, 0x4b, 0x64, 0x5e, 0x89, 0xe1, 0x53, 0x5b,
0xbd, 0xad, 0x5b, 0x8b, 0x29, 0x06, 0x50, 0x53,
0x08, 0x01, 0xb1, 0x8e, 0xbf, 0xbf, 0x5e, 0x8f,
0xab, 0x3c, 0x82, 0x87, 0x2a, 0x3e, 0x9b, 0xb7,
];
```

The **generator** should always be `7`

.

The **k** value should always be `3`

.

The **XOR Hash** with these values will always be `0xA7C27B6C96CA6F505A7C98031173AC383AB07BDD`

(big endian string).
The **XOR Hash** can be expressed as a **little endian** Rust array like

```
const PRECALCULATED_XOR_HASH: [u8; 20] = [
0xdd, 0x7b, 0xb0, 0x3a, 0x38, 0xac, 0x73, 0x11, 0x3, 0x98,
0x7c, 0x5a, 0x50, 0x6f, 0xca, 0x96, 0x6c, 0x7b, 0xc2, 0xa7,
];
```

### Aliases

Previously terms like **client public key** and **large safe prime** have been used instead of the single letters `A`

and `N`

which are used in RFC2945.

The one letter abbreviations are in my opinion difficult to remember and internalize unless you already know what they mean beforehand. The long forms will therefore be used, and the table below will serve as a translation between the long and short forms. Not all terms have a long term, since some terms only exist as intermediate results.

Short Form | Long Form |
---|---|

`A` | Client public key |

`a` | Client private key |

`B` | Server public key |

`b` | Server private key |

`N` | Large safe prime |

`g` | Generator |

`k` | K value |

`s` | Salt |

`U` | Username |

`p` | Password |

`v` | Password verifier |

`M1` | Client proof (proof first sent by client, calculated by both) |

`M2` | Server proof (proof first sent by server, calculated by both) |

`M` | (Client or server) proof |

`S` | S key |

`K` | Session key |

## Implementation Details

This section describes how to implement the algorithm on a function-by-function basis. A completely functional program will only be possible once everything except for the Reconnect proof has been implemented. Test against the provided text files for every function to ensure that no mistakes occur.

The wow_srp Rust crate implements the full algorithm, with examples for both client and server located in the examples/ directory. WowSrp has a C# version. Ember has a C++ version. Shadowburn has an Elixir version. wow_srp6 is a Python version.

The various Mangos forks and derivatives also have SRP6 implementations but these are of very low quality and some will produce incorrect results in as often as 1/1000 cases.

### Password Verifier

The password verifier, `v`

, is calculated as `v = g^x % N`

, where:

`g`

is the static**generator**equal to 7 defined in Constants.`x`

is discussed below.`%`

is the modulo operator.`N`

is the static**large safe prime**defined in Constants.

Calculating the password verifier in pseudo code would look like

```
function calculate_password_verifier
argument username: string
argument password: string
argument salt: little endian array of 32 bytes
returns little endian array of 32 bytes
x = calculate_x(username, password, salt)
// modpow is a power of and modulo operation in the same function
return g.modpow(x, large_safe_prime)
```

##### Implementations

- Rust (wow_srp)
- C# (WowSrp
- C++ (Ember). Slightly confusing way of doing it. Callsite is here, then here and then here.
- Elixir (Shadowburn)
- Python (wow_srp6)
- Go (go-wow-srp6)

##### Verification values

- calculate_v_values.txt contains the columns:

Username | Password | Salt | Expected |
---|---|---|---|

String | String | Big Endian Hex | Big Endian Hex |

#### Calculating `x`

The `x`

value is calculated as `x = SHA1( s | SHA1( U | : | p ))`

, where:

`U`

is the uppercased**username**of the user. See this section for notes.`p`

is the uppercased**password**of the user. See this section for notes.`s`

is the randomly generated**salt**value for the user.`|`

is concatenating the values.`SHA1`

is SHA-1 hashing the values.`:`

is the literal character`:`

.

Calculating `x`

in pseudo code would look like

```
function calculate_x
argument username: string
argument password: string
argument salt: little endian array of 32 bytes
returns little endian array of 20 bytes
interim = SHA1( username + ":" + password )
// Array concatenation
return SHA1( salt + interim )
```

##### Implementations

##### Verification values

- calculate_x_salt_values.txt assumes a static
**username**of`USERNAME123`

and a static**password**of`PASSWORD123`

. It contains the columns:

Salt | Expected |
---|---|

Big Endian Hex | Big Endian Hex |

- calculate_x_values.txt assumes a static
**salt**of`0xCAC94AF32D817BA64B13F18FDEDEF92AD4ED7EF7AB0E19E9F2AE13C828AEAF57`

(big endian). It contains the columns:

Username | Password | Expected |
---|---|---|

String | String | Big Endian Hex |

### Server public key

The **server public key**, `B`

, is calculated as `B = (k * v + (g^b % N)) % N`

, where:

`k`

is a**k value**statically defined in Constants.`v`

is the**password verifier**for the user.`g`

is a**generator**statically defined in Constants.`b`

is the**server private key**.`%`

is the modulo operator.`N`

is a**large safe prime**statically defined in Constants.

Calculating the **server public key** in pseudo code would look like:

```
function calculate_server_public_key
argument password_verifier: BigInteger type
argument server_private_key: BigInteger type
returns little endian array of 32 bytes
// modpow is a power of and modulo operation in the same function
interim = k_value * password_verifier
+ generator.modpow(server_private_key, large_safe_prime)
return interim % large_safe_prime
```

##### Implementations

##### Verification values

- calculate_B_values.txt contains the columns:

Password Verifier | Server Private Key | Expected |
---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex |

### Session key

The **session key**, `K`

, is calculated differently depending on whether it is the client or server doing the calculation because they only have access to their own **private keys**.

Both ways calculate an interim **S key** value that is finally **interleaved** in order to get the common **session key**.

For completeness, both the server and client **session key**, `K`

, is calculated as `K = interleaved(S)`

, where `interleaved`

is a function described below.
The calculation of the client and server **S keys** are below.

#### Client S Key

The **client S Key**, `S`

, is calculated as `S = (B - (k * (g^x % N)))^(a + u * x) % N`

, where:

`g`

is a**generator**statically defined in Constants.`%`

is the modulo operator.`N`

is a**large safe prime**statically defined in Constants.`k`

is a**k value**statically defined in Constants.`a`

is the**client private key**.`B`

is the**server public key**.`x`

is an interim value described under the Password Verifier section.`u`

is an interim value described below.

Calculating the **client S key** in pseudo code would look like:

```
function calculate_client_S_key
argument client_private_key: little endian array of 32 bytes
argument server_public_key: little endian array of 32 bytes
argument x: little endian array of 32 bytes
argument u: little endian array of 32 bytes
returns little endian array of 32 bytes
S = (server_public_key - (k * g.modpow(x, large_safe_prime))).modpow(client_private_key + u * x, large_safe_prime)
return S
```

##### Implementations

##### Verification values

If you’re getting an error on the 6th line of this file, your `BigInteger`

implementation is likely using remainder instead of modulo.

- calculate_client_S_values.txt expects a static
**generator**and**large safe prime**from Constants, and contains the columns:

Server Public Key | Client Private Key | x | u | Expected |
---|---|---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

#### Server S Key

The **server S key**, `S`

, is calculated as `S = (A * (v^u % N))^b % N`

, where:

`A`

is the**client public key**.`v`

is the**password verifier**for the user.`%`

is the modulo operator.`N`

is a**large safe prime**statically defined in Constants.`b`

is the**server private key**.`u`

is an interim value described below.

Calculating the **server S key** in pseudo code would look like:

```
function calculate_server_S_key
argument client_public_key: BigInteger type
argument password_verifier: BigInteger type
argument u: BigInteger type
argument server_private_key: BigInteger type
returns little endian array of 32 bytes
S = (client_public_key * password_verifier.modpow(u, large_safe_prime))
.modpow(server_private_key, large_safe_prime)
return S
```

##### Implementations

##### Verification values

- calculate_S_values.txt expects a static
**generator**and**large safe prime**from Constants, and contains the columns:

Client Public Key | Password Verifier | u | Server Private Key | Expected |
---|---|---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

#### u

The `u`

value is calculated as `u = SHA1( A | B )`

, where:

`A`

is the client public key.`B`

is the server public key.`SHA1`

is SHA-1 hashing.`|`

is array concatenation.

Calculating the **server S key** in pseudo code would look like:

```
function calculate_u
argument client_public_key: little endian array of 32 bytes
argument server_public_key: little endian array of 32 bytes
returns little endian array of 20 bytes
return SHA1( A + B )
```

##### Implementations

- Rust (wow_srp)
- C# (WowSrp
- C++ (Ember)
- Elixir (Shadowburn) where it’s called the scrambler.
- Python (wow_srp6)
- Go (go-wow-srp6)

##### Verification values

- calculate_u_values.txt contains the columns:

Client Public Key | Server Public Key | Expected |
---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex |

#### Interleaved

The **interleaved** function converts the **S key** to the **session key**.
It is calculated as `K = SHA_Interleave(S)`

, where:

`K`

is the**session key**.`S`

is the**S key**.`SHA_Interleave()`

is a function described below:

If the least significant byte is 0, the **two** least significant bytes are removed.
If the next least significant byte is also 0, the two new least significant bytes are again removed.
This process continues until the least significant byte is not equal to 0.
The even elements of the split **S key** are then hashed, as are the odd elements.
The two hashes are then zipped together, with the even elements in the first position.

The `SHA_Interleave()`

function in pseudo code would look like:

```
function split_s_key
argument s_key: little endian array of 32 bytes
returns little endian array of 32 bytes or fewer
// While the _least_ significant byte is 0,
// remove the 2 least significant bytes.
// Always keep the length even without
// trailing zero elements
while s_key[0] == 0
// In the pseudo code the s_key variable is modified in place
// so the first iteration `s_key` has length 32, in the second
// it has length 30, and so on.
s_key = s_key[2:]
return s_key
function SHA_Interleave
argument s_key: little endian array of 32 bytes
returns array of 40 bytes
split_s_key = split_s_key(s_key)
E = dynamic vector of bytes
// Only add the even elements
for i in split_s_key.even_elements
E.push(i)
G = SHA1(E)
F = dynamic vector of bytes
for i in split_s_key.odd_elements
F.push(i)
H = SHA1(F)
session_key = little endian array of 40 bytes
for i = 0, while i < 20, i += 2
session_key[i * 2] = G[i]
session_key[(i * 2) + 1] = H[i]
return session_key
```

##### Implementations

- Rust (wow_srp) (
`split_s_key`

) - Rust (wow_srp)
- C# (WowSrp (
`SplitSKey`

) - C# (WowSrp
- C++ (Ember)
- Elixir (Shadowburn)
- Python (wow_srp6)
- Go (go-wow-srp6)

##### Verification values

- calculate_interleaved_values.txt contains the columns:

S Key | Expected |
---|---|

Big Endian Hex | Big Endian Hex |

- calculate_split_s_key.txt contains the columns:

S Key | Expected |
---|---|

Big Endian Hex | Big Endian Hex |

#### Server Session Key

The server session key combines all the previous functions:

```
function calculate_server_session_key:
argument client_public_key: array of 32 bytes
argument server_public_key: array of 32 bytes
argument password_verifier: array of 32 bytes
argument server_private_key: array of 32 bytes
returns array of 40 bytes
u = calculate_u(client_public_key, server_public_key)
S = calculate_S(client_public_key, password_verifier, u, server_private_key)
return calculate_interleaved(S)
}
```

##### Implementations

##### Verification values

You will need to calculate the **server public key** using the supplied **password verifier** and **server private key** in order to test this.

- calculate_server_session_key.txt contains the columns:

Client Public Key | Password Verifier | Server Private Key | Expected |
---|---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

#### Client Session Key

The client session key combines all the previous functions:

```
function calculate_client_session_key:
argument username: string
argument password: string
argument server_public_key: array of 32 bytes
argument client_private_key: array of 32 bytes
argument generator: unsigned integer
argument large_safe_prime: array of 32 bytes
argument client_public_key: array of 32 bytes
argument salt: array of 32 bytes
returns array of 40 bytes
x = calculate_x(username, password, salt)
u = calculate_u(client_public_key, server_public_key)
S = calculate_client_S(
server_public_key,
x,
client_private_key,
u,
generator,
large_safe_prime,
)
return calculate_interleaved(S)
}
```

##### Implementations

##### Verification values

- calculate_client_session_key.txt contains the columns:

Username | Password | Server Public Key | Client Private Key | Generator | Large Safe Prime | Client Public Key | Salt | Expected |
---|---|---|---|---|---|---|---|---|

String | String | Big Endian Hex | Big Endian Hex | Integer | Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

### Server proof

The **server proof**, `M2`

or sometimes just `M`

, is calculated as `M2 = SHA1(A | M1 | K)`

, where:

`A`

is the**client public key**.`M1`

is the**client proof**. Remember that`M1`

**must**be calculated and verified on the server before this calculation takes place.`K`

is the**session key**.`|`

is concatenation.

Calculating the **server proof** in pseudo code would look like:

```
function calculate_server_proof
argument client_public_key: little endian array of 32 bytes
argument client_proof: little endian array of 20 bytes
argument session_key: little endian array of 40 bytes
returns little endian array of 20 bytes (SHA1 hash)
// Array concatenation
return SHA1(client_public_key + client_proof + session_key)
```

##### Implementations

- Rust (wow_srp)
- C# (WowSrp
- C++ (Ember) calls into here.
- Elixir (Shadowburn)
- Python (wow_srp6)
- Go (go-wow-srp6)

##### Verification values

- calculate_M2_values.txt contains the columns:

Client Public Key | Client Proof | Session Key | Expected |
---|---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

### Client proof

The **client proof**, `M1`

or sometimes just `M`

, is calculated as `M1 = SHA1( X | SHA1(U) | s | A | B | K )`

, where:

`X`

is the`XOR Hash`

, described below. It requires the**large safe prime**and the**generator**. Can be precalculated if both are known.`SHA1(U)`

is a SHA-1 hash of the uppercased**username**.`s`

is the**salt**.`A`

is the**client public key**.`B`

is the**server public key**.`K`

is the**session key**.

Calculating the **client proof** in pseudo code would look like:

```
function calculate_client_proof
argument xor_hash: little endian array of 20 bytes
argument username: string
argument session_key: little endian array of 40 bytes
argument client_public_key: little endian array of 32 bytes
argument server_public_key: little endian array of 32 bytes
argument salt: little endian array of 32 bytes
hashed_username = SHA1( username )
// | is array concatenation
return SHA1( xor_hash
| hashed_username
| salt
| client_public_key
| server_public_key
| session_key )
```

##### Implementations

##### Verification values

- calculate_M1_values.txt contains the columns:

Username | Session Key | Client Public Key | Server Public Key | Salt | Expected |
---|---|---|---|---|---|

String | Big endian hex | Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

#### XOR Hash

This value can be precalculated on the server and you can avoid the calculation. See the verification values for the value.

The `xor_hash`

is calculated as `SHA1(N) XOR SHA1(g)`

, where:

`SHA1(g)`

is a SHA-1 hash of the**generator**statically defined in Constants on the server and variable on the client.`SHA1(N)`

is a SHA-1 hash of the**large safe prime**statically defined in Constants on the server and variable on the client.`XOR`

uses the XOR operator on every element of the two arrays.

Calculating the XOR Hash in pseudo code would look like:

```
function calculate_xor_hash
argument large_safe_prime: little endian array of 32 bytes
argument generator: byte value
returns little endian array of 20 bytes (SHA-1 hash)
hashed_generator = SHA1( generator )
hashed_large_safe_prime = SHA1( large_safe_prime )
result is a 20 byte array
for index in hashed_generator
result[index] = hashed_generator[index] ^ hashed_large_safe_prime[index]
end for
return result
```

##### Implementations

##### Verification values

- calculate_xor_hash.txt contains the columns:

Generator | Large Safe Prime | Client Public Key |
---|---|---|

Decimal Number | Big endian hex | Big Endian Hex |

### Client public key

Generating the **client public key** is not necessary if you’re only writing the server part.

The **client public key**, `A`

, is calculated as `A = g^a % N`

, where:

`g`

is a**generator**. Clients do not get to choose the**generator**, so they can not assume a static value is used.`a`

is the**client private key**.`%`

is the modulo operator.`N`

is a**large safe prime**. Clients do not get to choose the**large safe prime**, so they can not assume a static value is used.

Calculating the **client public key** in pseudo code would look like:

```
function calculate_client_public_key
argument client_private_key: little endian array of 32 bytes
argument generator: 1 byte integer
argument large_safe_prime: little endian array of 32 bytes
returns little endian array of 32 bytes
// modpow is a power of and modulo operation in the same function
return generator.modpow(client_private_key, large_safe_prime)
```

##### Implementations

##### Verification values

- calculate_A_values.txt expects a static
**generator**and**large safe prime**as defined under Constants. It contains the columns:

Client Private Key | Expected |
---|---|

Big Endian Hex | Big Endian Hex |

## Reconnecting

If a client loses connection with the logon server after getting a **session key** but before connecting to a realm, the client will attempt bypass the creation of a new **session key** by proving to the client that it knows the old **session key**.

When reconnecting, the protocol works in 6 steps:

- The client sends their
**username**to the server. - The server checks for an existing session with the
**username**and creates randomized**server data**that it sends to the client. - The client generates randomized
**client data**and uses the**server data**along with the**username**and**session key**to create a**reconnect proof**. It then sends the**client data**and**reconnect proof**to the server. - The server calculates their own
**reconnect proof**from the**username**,**server data**,**client data**, and**session key**to see if it matches the client**reconnect proof**. - The client is now authenticated again and will send the ‘Send Realmlist’ packet to as the server to send a realmlist.

The above description can also be seen as a sequence diagram in figure 2.

The only function needed for the above is calculating the **reconnect proof** and generating random 16 byte values.

### Reconnect proof

The **reconnect proof** is not part of SRP6 and does not have a short term. It is calculated the same for the client and server.
The **reconnect proof** is calculated as `SHA1( username | client_data | server_data | session_key )`

, where:

`username`

is the**username**of the user attempting to reconnect.`client_data`

is the randomized data sent by the client.`server_data`

is the randomized data sent by the server.`session_key`

is the**session key**from a previous successful authentication.`|`

is array concatenation. Strings are converted to UTF-8 bytes.

Calculating the **client public key** in pseudo code would look like:

```
function calculate_reconnect_proof
argument username: string
argument client_data: little endian array of 16 bytes
argument server_data: little endian array of 16 bytes
argument session_key: little endian array of 40 bytes
returns little endian array of 20 bytes
return SHA1(username + client_data + server_data + session_key)
```

#### Implementations

#### Verification values

- calculate_reconnection_values.txt contains the columns:

Username | Client Data | Server Data | Session Key | Expected |
---|---|---|---|---|

String | Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

## Vanilla World Message Header Encryption

World Packets encrypt their header using the **session key**.
None of the Login Packets require encryption, but this section is included because it can be tedious to implement and is necessary for getting to the character screen.

The sending party encrypts the header and the receiving party decrypts the header. Client and server headers have different length, but this does not matter since the algorithm works on any amount of bytes.

The “encryption” is not real encryption, since it’s very easily breakable and can leak the session key which is used for reconnection.

To be explicit, if you’re writing a server you will only ever **decrypt** headers received from the client and **encrypt** headers sent to the client.

Figure 3 shows both encryption and decryption visually. Individual images can be found at step 1, step 2, step 3, step 4 and step 5.

### Encryption

Encryption works by XORing the unencrypted value with a byte of the session key and adding the previous encrypted value. The index into the session key is then incremented. This is done for every element of the array to be encrypted.

The encryption is calculated as `E = (x ^ S) + L`

where:

`E`

is the encrypted value, what is sent over the wire.`x`

is the unencrypted value, a byte of the opcode or size.`S`

is a byte of the session key, incrementing after every encryption.`L`

is the last encrypted value. This is`E`

from the previous iteration.

In pseudo code this would look like:

```
function encrypt
argument data: little endian array of length AL
returns little endian array of length AL
static int index = 0 // Static variables keep their values between function calls
static int last_value = 0 // Last value starts at zero
// The session key exists somewhere else as `session_key`
return_array = {} // Encrypted values
for unencrypted in data {
encrypted = (unencrypted ^ session_key[index]) + last_value
index = (index + 1) % session_key.length // Use the session key as a circular buffer
return_array.append(encrypted)
last_value = encrypted
}
return return_array
```

#### Implementations

#### Verification values

- calculate_encrypt_values.txt contains the columns:

Session Key | Data (50 bytes) | Expected (50 bytes) |
---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex |

The following files can be used to verify utility methods that return a size and opcode. The data should be decrypted first using the side in the file name, then encrypted again in order to verify that the write also matches.

```
session_key = [
0x2E, 0xFE, 0xE7, 0xB0, 0xC1, 0x77, 0xEB, 0xBD, 0xFF, 0x66, 0x76, 0xC5, 0x6E, 0xFC, 0x23,
0x39, 0xBE, 0x9C, 0xAD, 0x14, 0xBF, 0x8B, 0x54, 0xBB, 0x5A, 0x86, 0xFB, 0xF8, 0x1F, 0x6D,
0x42, 0x4A, 0xA2, 0x3C, 0xC9, 0xA3, 0x14, 0x9F, 0xB1, 0x75,
];
username = "A";
client_seed = 0xDEADBEEF;
server_seed = 0xDEADBEEF;
```

- vanilla_server contains the columns:

Data (8 bytes) | Size | Opcode |
---|---|---|

Big Endian Hex | Unsigned Integer | Unsigned |

- vanilla_client contains the columns:

Data (8 bytes) | Size | Opcode |
---|---|---|

Big Endian Hex | Unsigned Integer | Unsigned |

### Decryption

Decryption does the opposite of encryption. So first the old encrypted value is subtracted from the encrypted value, before it is XORed with the session key.

The decryption is calculated as `x = (E - L) ^ S`

where:

`x`

is the unencrypted value, a byte of the opcode or size.`E`

is the encrypted value, what is sent over the wire.`L`

is the last encrypted value. This is`E`

from the previous iteration.`S`

is a byte of the session key, incrementing after every encryption.

In pseudo code this would look like:

```
function decrypt
argument data: little endian array of length AL
returns little endian array of length AL
static int index = 0 // Static variables keep their values between function calls
static int last_value = 0 // Last value starts at zero
// The session key exists somewhere else as `session_key`
return_array = {} // Unencrypted values
for encrypted in data {
unencrypted = (encrypted - last_value) ^ session_key[index]
index = (index + 1) % session_key.length // Use the session key as a circular buffer
last_value = encrypted
return_array.append(unencrypted)
}
return return_array
```

#### Implementations

#### Verification values

- calculate_decrypt_values.txt contains the columns:

Session Key | Data (50 bytes) | Expected (50 bytes) |
---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex |

## TBC World Message Header Encryption

Has the same implementation for encryption/decryption as Vanilla, but it performs an HMAC-SHA1 on the **session key** along with a **seed** and iterates over that instead.
Verification values are provided for

Remember that the key for TBC is only 20 bytes, while it’s 40 for Vanilla so you will probably need different functions for each.

### Key Generation

`HMAC-SHA1`

is the HMAC-SHA1 function.

In pseudo code the generation looks like:

```
function create_tbc_key:
argument session_key: array of 40 bytes
returns array of 20 bytes
S = [0x38, 0xA7, 0x83, 0x15, 0xF8, 0x92, 0x25, 0x30, 0x71, 0x98, 0x67, 0xB1, 0x8C, 0x4, 0xE2,
0xAA]
hmac = HMAC-SHA1(S)
hmac.update(session_key)
return hmac.finalize()
```

#### Implementations

#### Verification values

- create_tbc_key.txt contains the columns:

Session Key | Expected |
---|---|

Big Endian Hex | Big Endian Hex |

### Encryption/Decryption

This is identical to the Vanilla version except the key is only 20 bytes instead of 40 bytes.

Encrypt this and verify that it matches `Expected Encrypt`

, then decrypt it and verify that it matches the original `Data`

.

#### Verification values

- calculate_tbc_encrypt_values.txt contains the columns:

Session Key | Data | Expected Encrypt |
---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex |

The following files can be used to verify utility methods that return a size and opcode. The data should be decrypted first using the side in the file name, then encrypted again in order to verify that the write also matches.

```
session_key = [
0x2E, 0xFE, 0xE7, 0xB0, 0xC1, 0x77, 0xEB, 0xBD, 0xFF, 0x66, 0x76, 0xC5, 0x6E, 0xFC, 0x23,
0x39, 0xBE, 0x9C, 0xAD, 0x14, 0xBF, 0x8B, 0x54, 0xBB, 0x5A, 0x86, 0xFB, 0xF8, 0x1F, 0x6D,
0x42, 0x4A, 0xA2, 0x3C, 0xC9, 0xA3, 0x14, 0x9F, 0xB1, 0x75,
];
username = "A";
client_seed = 0xDEADBEEF;
server_seed = 0xDEADBEEF;
```

- tbc_server contains the columns:

Data (8 bytes) | Size | Opcode |
---|---|---|

Big Endian Hex | Unsigned Integer | Unsigned |

- tbc_client contains the columns:

Data (8 bytes) | Size | Opcode |
---|---|---|

Big Endian Hex | Unsigned Integer | Unsigned |

## Wrath World Message Header Encryption

Wrath creates an intermediate key by running a fixed key and the **session key** through HMAC-SHA1, and then uses this to seed an RC4A cipher that encrypts the header.

The key generation function is essentially the same as the TBC generation, except TBC uses the same hardcoded seed for both client and server, while Wrath has separate keys.

The seed keys are:

```
// Used for Client (Encryption) to Server (Decryption)
const S: [u8; 16] = [
0xC2, 0xB3, 0x72, 0x3C, 0xC6, 0xAE, 0xD9, 0xB5, 0x34, 0x3C, 0x53, 0xEE, 0x2F, 0x43, 0x67, 0xCE,
];
// Used for Server (Encryption) to Client (Decryption) messages
const R: [u8; 16] = [
0xCC, 0x98, 0xAE, 0x04, 0xE8, 0x97, 0xEA, 0xCA, 0x12, 0xDD, 0xC0, 0x93, 0x42, 0x91, 0x53, 0x57,
];
```

### Key Generation

`HMAC-SHA1`

is the HMAC-SHA1 function.

In pseudo code the generation looks like:

```
function create_wrath_key:
argument session_key: array of 40 bytes
argument key: array of 16 bytes
returns array of 20 bytes
hmac = HMAC-SHA1(key)
hmac.update(session_key)
return hmac.finalize()
```

#### Implementations

#### Verification values

- create_wrath_hmac_key.txt contains the columns:

Session Key | Key | Expected |
---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex |

### Encryption/Decryption

Initialize a RC4A with the previously calculated key. Then apply the keystream to 1024 bytes (since this is the drop1024 variant).

After this apply the keystream to the header to be encrypted.

```
function create_crypto:
argument session_key: array of 40 bytes
argument key: array of 16 bytes
returns RC4A in progress
S = create_wrath_key(session_key, key)
rc4 = RC4(S)
rc4.apply_keystream to 1024 0 bytes
retrurn rc4
function encrypt/decrypt:
argument rc4: RC4A in progress
argument data: array of bytes
# Modifies data in place
rc4.apply(data)
```

#### Implementations

#### Verification values

These verification values use the above keys. Ensure that you’re using the correct one for the correct direction and side.

- calculate_wrath_encrypt_values.txt contains the columns:

Session Key | Data | Expected Client Encrypt | Expected Server Encrypt | |
---|---|---|---|---|

Big Endian Hex | Big Endian Hex | Big Endian Hex | Big Endian Hex |

The following files can be used to verify utility methods that return a size and opcode. The data should be decrypted first using the side in the file name, then encrypted again in order to verify that the write also matches.

```
session_key = [
0x2E, 0xFE, 0xE7, 0xB0, 0xC1, 0x77, 0xEB, 0xBD, 0xFF, 0x66, 0x76, 0xC5, 0x6E, 0xFC, 0x23,
0x39, 0xBE, 0x9C, 0xAD, 0x14, 0xBF, 0x8B, 0x54, 0xBB, 0x5A, 0x86, 0xFB, 0xF8, 0x1F, 0x6D,
0x42, 0x4A, 0xA2, 0x3C, 0xC9, 0xA3, 0x14, 0x9F, 0xB1, 0x75,
];
username = "A";
client_seed = 0xDEADBEEF;
server_seed = 0xDEADBEEF;
```

- wrath_server contains the columns:

Data (8 bytes) | Size | Opcode |
---|---|---|

Big Endian Hex | Unsigned Integer | Unsigned |

- wrath_client contains the columns:

Data (8 bytes) | Size | Opcode |
---|---|---|

Big Endian Hex | Unsigned Integer | Unsigned |

## World Server Proof

After receiving the `CMSG_AUTH_SESSION`

^{wiki}
message the server will have to verify that the client does indeed know the correct session key. This is done by having both the server and client use 32 bit (not byte) values as seeds in a hash with the session key.

The world server proof works for all versions from Vanilla through to Wrath.

The proof is calculated as `SHA1( username | 0 | client_seed | server_seed | session_key )`

, where:

`username`

is the**username**of the user attempting to connect.`0`

is the literal value zero, taking up 4 bytes (32 bits, a standard`int`

).`client_seed`

is a random 4 byte value sent by the client.`server_seed`

is a random 4 byte value generated by the server.`session_key`

is the**session key**from the original authentication.

Calculating the world proof in pseudo code would look like:

```
function calculate_world_server_proof
argument username: string
argument client_seed: unsigned 4 byte value, converted to little endian bytes
argument server_seed: unsigned 4 byte value, converted to little endian bytes
argument session_key: little endian array of 40 bytes
returns little endian array of 20 bytes
zero = [0, 0, 0, 0] // Assume this is a 4 byte array of zeros
return SHA1(username + zero + client_seed + server_seed + session_key)
```

#### Implementations

- Rust (wow_srp)
- C# (WowSrp)
- C++ (Ember)
- C++ (Mangos)
- Elixir (Shadowburn)
- Python (wow_srp6)
- Go (go-wow-srp6)

#### Verification values

- calculate_world_server_proof.txt contains the columns:

Username | Session Key | Server Seed | Client Seed | Expected |
---|---|---|---|---|

String | Big Endian Hex | Number | Number | Big Endian Hex |

## Notes on uppercasing username and password

The username and password are UTF-8 strings that are automatically uppercased by the client before being sent over the network. This might present some issues with getting the correct name from the registration form and the client, more information is present at the wow_srp library documentation.

## External Resources

- The Shadowburn Project has a guide on authenticating that focuses more on the networking and specific packets.
- The WoWDev Wiki has an overview of packets.
- Wireshark at version 3.5 or greater has support for parsing 1.12 packets through the
`WOW`

and`WOWW`

protocols. You**will**need this if you’re trying to debug a networked implementation.