World of Warcraft versions after `2.0.1.6180`

supports using **Matrix Cards**/**Grid Cards** as multi factor authentication during login.
**Matrix Cards** are a form of Pre-shared Key where the client is tested on partial elements of the shared key on every login.

This post details how the implementation works and how to use it.

# Background

**Matrix Cards** are a relatively niche form of two factor authentication where the client is tested on partial elements of a Pre-shared Key.
The client is first given a **Matrix Card** in the form of either a document or a physical card akin to the one shown in figure 1.

The Chinese text in the top left means “Serial Number:”. This card is very unlikely to have been used with the Wrath of the Lich King or prior clients since it has a variable number of digits per field, which this algorithm does not support, however it can still be used to illustrate how the system works.

The server would ask the client which digits are in cell “A6” and the client would enter “483”.
This would repeat for however many **rounds** the server has requested.
The results are then hashed and the **proof** is sent by the client.
The server makes the same calculations and compares the hashes.
If they are identical the client knows the layout of the **Matrix Card**.

The server may send a **width**, **height**, **digit count**, **challenge count**, and **seed** in `CMD_AUTH_LOGON_CHALLENGE_Server`

^{wiki}
.
The client may send the resulting **proof** in `CMD_AUTH_LOGON_PROOF_Client`

^{wiki}
.
Real clients will always reply with a **proof** if the fields from the server message are present.

If the server also sends the fields required for PIN authentication the PIN prompt will be displayed first, followed by the **Matrix Card** prompt.

Figure 2 shows the complete login process in a 3.3.5 client with **Matrix Card** validation with a **width** of 10 and **height** of 20, **digit count** of 3, and **challenge count** of 2.

# Implementation details

This section describes how to implement the algorithm on a function-by-function basis. A completelely functional program will only be possible when everything has been implemented.

The wow_srp Rust crate implements the full algorithm. This Github Gist by Barncastle implements the full algorithm in C#.

The algorithm requires keeping the state of in progress RC4 and HMAC-SHA1 objects alive while the algorithm is executed.

## Generate Coordinates

The coordinates control which fields are checked.

`width`

is the**width**sent by the server.`height`

is the**height**sent by the server.`challenge_count`

is the**challenge count**/**rounds**sent by the server.`seed`

is the**seed**sent by the server.`%`

is the modulus operator.

Generating the coordinate array in pseudo code:

```
function generate_coordinates
argument width: u8
argument height: u8
argument challenge_count: u8
argument seed: u64
returns array of u32s
# width, height, and challenge_count can not be 0
# challenge_count must be greater than or equal to width * height
matrix_indices = array of (width * height) u32s that where the value is the same as the index
# So if width * height was 4 the matrix_indices would be [0, 1, 2, 3]
coordinates = array of challenge_count u32s
for i, coordinate in enumerate(coordinates):
count = len(matrix_indices) - i;
index = seed % count;
coordinate = matrix_indices[index];
for j in index..(count - 1) {
matrix_indices[j] = matrix_indices[j + 1];
}
seed /= count;
return coordinates
```

### Implementations

### Verification values

`coordinates_regression`

contains the columns:

`width` | `height` | `challenge_count` | `seed` | `expected` |
---|---|---|---|---|

unsigned integer | unsigned integer | unsigned integer | unsigned integer | `challenge_count` amount of space separated unsigned integers |

## Create new objects

This function creates the `coordinates`

, `rc4`

, and `hmac`

objects required for further functions.

`width`

is the**width**sent by the server.`height`

is the**height**sent by the server.`challenge_count`

is the**challenge count**/**rounds**sent by the server.`seed`

is the**seed**sent by the server.`session_key`

is the 40 byte session key created by the SRP6 algorithm.`MD5`

is the MD5 hash function.`HMAC-SHA1`

is the HMAC-SHA1 function.`RC4`

is the RC4 function.`+`

is array concatenation

Creating the objects in pseudo code:

```
function create_new_objects
argument width: u8
argument height: u8
argument challenge_count: u8
argument seed: u64
argument session_key: array of 40 bytes
returns in-progress HMAC-SHA1, in-progress RC4, array of u32s
coordinates = generate_coordinates(width, height, challenge_count, seed)
md5 = MD5(seed as little endian array + session_key)
# Do not drop anything, unlike headers dropping 1024
rc4 = RC4(md5)
hmac = HMAC-SHA1(md5)
return hmac, rc4, coordinates
```

### Implementations

### Verification values

There are no verification values for this function since the HMAC and RC4 ciphers aren’t finished, and the coordinates are verified in Generate Coordinates.

## Get Coordinate

This function returns the `(x, y)`

location on the pre shared key that should be entered using Enter Value.

`width`

is the**width**sent by the server.`height`

is the**height**sent by the server.`challenge_count`

is the**challenge count**/**rounds**sent by the server.`round`

is the iteration number. So the first iteration would be 0, the second 1, and so on.`coordinates`

is the array created by Generate Coordinates.

Getting coordinates in pseudo code would look like:

```
function get_coordinate
argument challenge_count: u8
argument coordinates: array of u32s
argument width: u8
argument height: u8
argument round: u8
returns u8, u8
# round can not be greater than challenge_count
coord = coordinates[round]
x = coord % width
y = coord / width
# y can not be greater than or equal to height
return x, y
```

### Implementations

### Verification Values

`get_coordinate_regression`

contains the columns:

`height` | `width` | `seed` | `challenge_count` | `session_key` | `expected` |
---|---|---|---|---|---|

unsigned integer | unsigned integer | unsigned integer | unsigned integer | Big endian array | `challenge_count` pairs of `x` and `y` separated by space |

## Enter Digit

Enters a single digit from a cell.
Digits should be in the range `[0;9]`

and should be entered one by one.
Nothing needs to be done in between rounds.

Entering digits in pseudo code would look like:

```
function enter_digit
argument rc4: in progress RC4 object created by create_new_objects
argument hmac: in progress HMAC-SHA1 object created by create_new_objects
argument value: u8
new_value = rc4.apply(value)
hmac.apply(new_value)
```

### Implementations

### Verification Values

Since this function doesn’t provide any output there are no verification values. Test this using Create Proof instead.

## Create Proof

Finalizes the proof after digits have been entered.

In pseudo code it would look like:

```
function create_proof
argument hmac: in progress HMAC-SHA1 object created by create_new_objects
returns array of 20 bytes
return hmac.finalize()
```

### Implementations

### Verification Values

`proof_regression`

contains the columns:

`height` | `width` | `seed` | `session_key` | `expected` | `challenge_count` | `challenges` |
---|---|---|---|---|---|---|

unsigned integer | unsigned integer | unsigned integer | Big endian array | Big endian array | unsigned integer | `challenge_count` unsigned integers to be entered using Enter Digit |

## Usage

Usage in pseudo code would look like:

```
width = 26
height = 26
challenge_count = 2
digit_count = 3
seed = random()
session_key = a valid generated session key
hmac, rc4, coordinates = create_new_objects(width, height, challenge_count, seed, session_key)
for round in 0..challenge_count:
x, y = get_coordinate(challenge_count, coordinates, width, height, round)
digits = use x and y to get the cell digits
for digit in digits:
value = enter_digit(rc4, hmac, digit)
create_proof(hmac)
# Send the proof to the server, or compare it with what the client sent
```