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.**

## 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**.

### 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`

.

### 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. 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++ (Ember). Slightly confusing way of doing it. Callsite is here, then here and then here.
- Elixir (Shadowburn)
- Python (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: little endian array of 32 bytes
argument server_private_key: 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
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

- 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: little endian array of 32 bytes
argument password_verifier: little endian array of 32 bytes
argument u: little endian array of 32 bytes
argument server_private_key: little endian array of 32 bytes
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++ (Ember)
- Elixir (Shadowburn) where it’s called the scrambler.
- Python (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
// 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
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

##### Verification values

- calculate_interleaved_values.txt contains the columns:

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

Little Endian Hex | Little 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

##### Verification values

- calculate_M2_values.txt contains the columns:

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

Big Endian Hex | Big Endian Hex | Little 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 | Little 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

I do not have verification values, although the precalculated hash with the Constant values in Rust is:

```
const PRECALCULATED_XOR_HASH: [u8; 20] = [
221, 123, 176, 58, 56, 172, 115, 17, 3, 152, 124,
90, 80, 111, 202, 150, 108, 123, 194, 167,
];
```

### 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 |
---|---|---|---|

String | Little Endian Hex | Little Endian Hex | Little Endian Hex |

## World Packet 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) |
---|---|---|

Little Endian Hex | Little Endian Hex | Little Endian Hex |

### 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) |
---|---|---|

Little Endian Hex | Little Endian Hex | Little Endian Hex |

### World Server Proof

After receiving the CMSG_AUTH_SESSION 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 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

#### Verification values

- calculate_world_server_proof.txt contains the columns:

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

String | Big Endian Hex | Little Endian Hex | Little Endian Hex | Little 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.