<feed xmlns="http://www.w3.org/2005/Atom"><title>Home on GTKer.com</title><link href="https://gtker.com/index.xml" rel="self"/><link href="https://gtker.com/"/><updated>2024-05-06T00:00:00+00:00</updated><id>https://gtker.com/</id><author><name>Gtker</name><email>contact@gtker.com</email></author><generator>Hugo -- gohugo.io</generator><entry><title type="html">Implementation Guide for the World of Warcraft Matrix Card calculation</title><link href="https://gtker.com/implementation-guide-for-the-world-of-warcraft-matrix-card-calculation/"/><id>https://gtker.com/implementation-guide-for-the-world-of-warcraft-matrix-card-calculation/</id><author><name>Gtker</name></author><published>2024-05-06T00:00:00+00:00</published><updated>2024-05-06T00:00:00+00:00</updated><content type="html"><![CDATA[<p>World of Warcraft versions after <code>2.0.1.6180</code> supports using <strong>Matrix Cards</strong>/<strong>Grid Cards</strong> as multi factor authentication during login.
<strong>Matrix Cards</strong> are a form of <a href="https://en.wikipedia.org/wiki/Pre-shared_key">Pre-shared Key</a> where the client is tested on partial elements of the shared key on every login.</p>
<p>This post details how the implementation works and how to use it.</p>
<h1 id="background">Background</h1>
<p><strong>Matrix Cards</strong> are a relatively niche form of two factor authentication where the client is tested on partial elements of a <a href="https://en.wikipedia.org/wiki/Pre-shared_key">Pre-shared Key</a>.
The client is first given a <strong>Matrix Card</strong> in the form of either a document or a physical card akin to the one shown in figure 1.</p>
<figure>
    
    <img src="wow-matrix-card.jpg" alt="Matrix Card purported to be used on Chinese World of Warcraft clients."></img>
    
    <figcaption><b>Figure 1:</b> Matrix Card purported to be used on Chinese World of Warcraft clients.
        <br>
        
</figure>

<p>The Chinese text in the top left means &ldquo;Serial Number:&rdquo;.
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.</p>
<p>The server would ask the client which digits are in cell &ldquo;A6&rdquo; and the client would enter &ldquo;483&rdquo;.
This would repeat for however many <strong>rounds</strong> the server has requested.
The results are then hashed and the <strong>proof</strong> 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 <strong>Matrix Card</strong>.</p>
<p>The server may send a <strong>width</strong>, <strong>height</strong>, <strong>digit count</strong>, <strong>challenge count</strong>, and <strong>seed</strong> in <a href="https://gtker.com/wow_messages/docs/cmd_auth_logon_challenge_server.html">
    <code>CMD_AUTH_LOGON_CHALLENGE_Server</code>
</a>
<span>
    <sup>
        <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server">wiki</a>
    </sup>
</span>
.
The client may send the resulting <strong>proof</strong> in <a href="https://gtker.com/wow_messages/docs/cmd_auth_logon_proof_client.html">
    <code>CMD_AUTH_LOGON_PROOF_Client</code>
</a>
<span>
    <sup>
        <a href="https://wowdev.wiki/CMD_AUTH_LOGON_PROOF_Client">wiki</a>
    </sup>
</span>
.
Real clients will always reply with a <strong>proof</strong> if the fields from the server message are present.</p>
<p>If the server also sends the fields required for <a href="../wow-pin">PIN authentication</a> the PIN prompt will be displayed first, followed by the <strong>Matrix Card</strong> prompt.</p>
<p>Figure 2 shows the complete login process in a 3.3.5 client with <strong>Matrix Card</strong> validation with a <strong>width</strong> of 10 and <strong>height</strong> of 20, <strong>digit count</strong> of 3, and <strong>challenge count</strong> of 2.</p>
<figure>
    
    <video src="login.webm" alt="Full login procedure with Matrix Card in 3.3.5 client." controls></video>
    
    <figcaption><b>Figure 2:</b> Full login procedure with Matrix Card in 3.3.5 client.
        <br>
        
</figure>

<h1 id="implementation-details">Implementation details</h1>
<p>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.</p>
<p>The <a href="https://github.com/gtker/wow_srp/blob/main/src/matrix_card.rs">wow_srp Rust crate</a> implements the full algorithm.
<a href="https://web.archive.org/web/20240505081723/https://gist.github.com/barncastle/979c12a9c5e64d810a28ad1728e7e0f9">This Github Gist by Barncastle</a> implements the full algorithm in C#.</p>
<p>The algorithm requires keeping the state of in progress <a href="https://en.wikipedia.org/wiki/RC4">RC4</a> and <a href="https://en.wikipedia.org/wiki/HMAC">HMAC-SHA1</a> objects alive while the algorithm is executed.</p>
<h2 id="generate-coordinates">Generate Coordinates</h2>
<p>The coordinates control which fields are checked.</p>
<ul>
<li><code>width</code> is the <strong>width</strong> sent by the server.</li>
<li><code>height</code> is the <strong>height</strong> sent by the server.</li>
<li><code>challenge_count</code> is the <strong>challenge count</strong>/<strong>rounds</strong> sent by the server.</li>
<li><code>seed</code> is the <strong>seed</strong> sent by the server.</li>
<li><code>%</code> is the modulus operator.</li>
</ul>
<p>Generating the coordinate array in pseudo code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function generate_coordinates
</span></span><span style="display:flex;"><span>    argument width: u8
</span></span><span style="display:flex;"><span>    argument height: u8
</span></span><span style="display:flex;"><span>    argument challenge_count: u8
</span></span><span style="display:flex;"><span>    argument seed: u64
</span></span><span style="display:flex;"><span>    returns array of u32s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # width, height, and challenge_count can not be 0
</span></span><span style="display:flex;"><span>    # challenge_count must be greater than or equal to width * height
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    matrix_indices = array of (width * height) u32s that where the value is the same as the index
</span></span><span style="display:flex;"><span>    # So if width * height was 4 the matrix_indices would be [0, 1, 2, 3]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    coordinates = array of challenge_count u32s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    for i, coordinate in enumerate(coordinates):
</span></span><span style="display:flex;"><span>        count = len(matrix_indices) - i;
</span></span><span style="display:flex;"><span>        index = seed % count;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        coordinate = matrix_indices[index];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        for j in index..(count - 1) {
</span></span><span style="display:flex;"><span>            matrix_indices[j] = matrix_indices[j + 1];
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        seed /= count;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return coordinates
</span></span></code></pre></div><h3 id="implementations">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/89926ba8c2eb68fe8b270907c05f0e5ce18b8089/src/matrix_card.rs#L441">Rust (wow_srp)</a></li>
<li><a href="https://gist.github.com/barncastle/979c12a9c5e64d810a28ad1728e7e0f9#file-wowmatrixcard-cs-L112">C# (Barncastle Gist)</a></li>
</ul>
<h3 id="verification-values">Verification values</h3>
<ul>
<li><a href="coordinates_regression.txt"><code>coordinates_regression</code></a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th><code>width</code></th>
<th><code>height</code></th>
<th><code>challenge_count</code></th>
<th><code>seed</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td><code>challenge_count</code> amount of space separated unsigned integers</td>
</tr>
</tbody>
</table>
<h2 id="create-new-objects">Create new objects</h2>
<p>This function creates the <code>coordinates</code>, <code>rc4</code>, and <code>hmac</code> objects required for further functions.</p>
<ul>
<li><code>width</code> is the <strong>width</strong> sent by the server.</li>
<li><code>height</code> is the <strong>height</strong> sent by the server.</li>
<li><code>challenge_count</code> is the <strong>challenge count</strong>/<strong>rounds</strong> sent by the server.</li>
<li><code>seed</code> is the <strong>seed</strong> sent by the server.</li>
<li><code>session_key</code> is the 40 byte session key created by the <a href="05-wow-srp">SRP6 algorithm</a>.</li>
<li><code>MD5</code> is the <a href="https://en.wikipedia.org/wiki/MD5">MD5 hash function</a>.</li>
<li><code>HMAC-SHA1</code> is the <a href="https://en.wikipedia.org/wiki/HMAC">HMAC-SHA1 function</a>.</li>
<li><code>RC4</code> is the <a href="https://en.wikipedia.org/wiki/RC4">RC4 function</a>.</li>
<li><code>+</code> is array concatenation</li>
</ul>
<p>Creating the objects in pseudo code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function create_new_objects
</span></span><span style="display:flex;"><span>    argument width: u8
</span></span><span style="display:flex;"><span>    argument height: u8
</span></span><span style="display:flex;"><span>    argument challenge_count: u8
</span></span><span style="display:flex;"><span>    argument seed: u64
</span></span><span style="display:flex;"><span>    argument session_key: array of 40 bytes
</span></span><span style="display:flex;"><span>    returns in-progress HMAC-SHA1, in-progress RC4, array of u32s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    coordinates = generate_coordinates(width, height, challenge_count, seed)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    md5 = MD5(seed as little endian array + session_key)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # Do not drop anything, unlike headers dropping 1024
</span></span><span style="display:flex;"><span>    rc4 = RC4(md5)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    hmac = HMAC-SHA1(md5)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return hmac, rc4, coordinates
</span></span></code></pre></div><h3 id="implementations-1">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/89926ba8c2eb68fe8b270907c05f0e5ce18b8089/src/matrix_card.rs#L374">Rust (wow_srp)</a></li>
<li><a href="https://gist.github.com/barncastle/979c12a9c5e64d810a28ad1728e7e0f9#file-wowmatrixcard-cs-L20">C# (Barncastle Gist)</a></li>
</ul>
<h3 id="verification-values-1">Verification values</h3>
<p>There are no verification values for this function since the HMAC and RC4 ciphers aren&rsquo;t finished, and the coordinates are verified in <a href="#generate-coordinates">Generate Coordinates</a>.</p>
<h2 id="get-coordinate">Get Coordinate</h2>
<p>This function returns the <code>(x, y)</code> location on the pre shared key that should be entered using <a href="#enter-value">Enter Value</a>.</p>
<ul>
<li><code>width</code> is the <strong>width</strong> sent by the server.</li>
<li><code>height</code> is the <strong>height</strong> sent by the server.</li>
<li><code>challenge_count</code> is the <strong>challenge count</strong>/<strong>rounds</strong> sent by the server.</li>
<li><code>round</code> is the iteration number. So the first iteration would be 0, the second 1, and so on.</li>
<li><code>coordinates</code> is the array created by <a href="#generate-coordinates">Generate Coordinates</a>.</li>
</ul>
<p>Getting coordinates in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function get_coordinate
</span></span><span style="display:flex;"><span>    argument challenge_count: u8
</span></span><span style="display:flex;"><span>    argument coordinates: array of u32s
</span></span><span style="display:flex;"><span>    argument width: u8
</span></span><span style="display:flex;"><span>    argument height: u8
</span></span><span style="display:flex;"><span>    argument round: u8
</span></span><span style="display:flex;"><span>    returns u8, u8
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # round can not be greater than challenge_count
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    coord = coordinates[round]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    x = coord % width
</span></span><span style="display:flex;"><span>    y = coord / width
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # y can not be greater than or equal to height
</span></span><span style="display:flex;"><span>    return x, y
</span></span></code></pre></div><h3 id="implementations-2">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/89926ba8c2eb68fe8b270907c05f0e5ce18b8089/src/matrix_card.rs#L407">Rust (wow_srp)</a> <a href="https://github.com/gtker/wow_srp/blob/fd4ff7cd48cf3708ba499d6f09127e8d8bde53b1/src/matrix_card.rs#L594">Rust test implementation</a></li>
<li><a href="https://gist.github.com/barncastle/979c12a9c5e64d810a28ad1728e7e0f9#file-wowmatrixcard-cs-L60">C# (Barncastle Gist)</a></li>
</ul>
<h3 id="verification-values-2">Verification Values</h3>
<ul>
<li><a href="get_coordinate_regression.txt"><code>get_coordinate_regression</code></a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th><code>height</code></th>
<th><code>width</code></th>
<th><code>seed</code></th>
<th><code>challenge_count</code></th>
<th><code>session_key</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>Big endian array</td>
<td><code>challenge_count</code> pairs of <code>x</code> and <code>y</code> separated by space</td>
</tr>
</tbody>
</table>
<h2 id="enter-digit">Enter Digit</h2>
<p>Enters a single digit from a cell.
Digits should be in the range <code>[0;9]</code> and should be entered one by one.
Nothing needs to be done in between rounds.</p>
<p>Entering digits in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function enter_digit
</span></span><span style="display:flex;"><span>    argument rc4: in progress RC4 object created by create_new_objects
</span></span><span style="display:flex;"><span>    argument hmac: in progress HMAC-SHA1 object created by create_new_objects
</span></span><span style="display:flex;"><span>    argument value: u8
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    new_value = rc4.apply(value)
</span></span><span style="display:flex;"><span>    hmac.apply(new_value)
</span></span></code></pre></div><h3 id="implementations-3">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/89926ba8c2eb68fe8b270907c05f0e5ce18b8089/src/matrix_card.rs#L428">Rust (wow_srp)</a> <a href="https://github.com/gtker/wow_srp/blob/fd4ff7cd48cf3708ba499d6f09127e8d8bde53b1/src/matrix_card.rs#L530">Rust test implementation</a></li>
<li><a href="https://gist.github.com/barncastle/979c12a9c5e64d810a28ad1728e7e0f9#file-wowmatrixcard-cs-L80">C# (Barncastle Gist)</a></li>
</ul>
<h3 id="verification-values-3">Verification Values</h3>
<p>Since this function doesn&rsquo;t provide any output there are no verification values.
Test this using <a href="#create-proof">Create Proof</a> instead.</p>
<h2 id="create-proof">Create Proof</h2>
<p>Finalizes the proof after digits have been entered.</p>
<p>In pseudo code it would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function create_proof
</span></span><span style="display:flex;"><span>    argument hmac: in progress HMAC-SHA1 object created by create_new_objects
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return hmac.finalize()
</span></span></code></pre></div><h3 id="implementations-4">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/89926ba8c2eb68fe8b270907c05f0e5ce18b8089/src/matrix_card.rs#L436">Rust (wow_srp)</a></li>
<li><a href="https://gist.github.com/barncastle/979c12a9c5e64d810a28ad1728e7e0f9#file-wowmatrixcard-cs-L94">C# (Barncastle Gist)</a></li>
</ul>
<h3 id="verification-values-4">Verification Values</h3>
<ul>
<li><a href="proof_regression.txt"><code>proof_regression</code></a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th><code>height</code></th>
<th><code>width</code></th>
<th><code>seed</code></th>
<th><code>session_key</code></th>
<th><code>expected</code></th>
<th><code>challenge_count</code></th>
<th><code>challenges</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>unsigned integer</td>
<td>Big endian array</td>
<td>Big endian array</td>
<td>unsigned integer</td>
<td><code>challenge_count</code> unsigned integers to be entered using <a href="#enter-digit">Enter Digit</a></td>
</tr>
</tbody>
</table>
<h2 id="usage">Usage</h2>
<p>Usage in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>width = 26
</span></span><span style="display:flex;"><span>height = 26
</span></span><span style="display:flex;"><span>challenge_count = 2
</span></span><span style="display:flex;"><span>digit_count = 3
</span></span><span style="display:flex;"><span>seed = random()
</span></span><span style="display:flex;"><span>session_key = a valid generated session key
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>hmac, rc4, coordinates = create_new_objects(width, height, challenge_count, seed, session_key)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>for round in 0..challenge_count:
</span></span><span style="display:flex;"><span>    x, y = get_coordinate(challenge_count, coordinates, width, height, round)
</span></span><span style="display:flex;"><span>    digits = use x and y to get the cell digits
</span></span><span style="display:flex;"><span>    for digit in digits:
</span></span><span style="display:flex;"><span>        value = enter_digit(rc4, hmac, digit)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>create_proof(hmac)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># Send the proof to the server, or compare it with what the client sent
</span></span></code></pre></div>]]></content></entry><entry><title type="html">Implementation Guide for the World of Warcraft PIN calculation</title><link href="https://gtker.com/implementation-guide-for-the-world-of-warcraft-pin-calculation/"/><id>https://gtker.com/implementation-guide-for-the-world-of-warcraft-pin-calculation/</id><author><name>Gtker</name></author><published>2024-04-29T00:00:00+00:00</published><updated>2024-04-29T00:00:00+00:00</updated><content type="html"><![CDATA[<p>Starting with client version 1.12 the user can enter a PIN which is hashed and sent to the server.
This post details how the client calculates the hash for World of Warcraft 1.12 and provides replication values in order to test your own implementation.</p>
<h1 id="background">Background</h1>
<p>The server may send a PIN grid seed and PIN salt in <a href="https://gtker.com/wow_messages/docs/cmd_auth_logon_challenge_server.html">
    <code>CMD_AUTH_LOGON_CHALLENGE_Server</code>
</a>
<span>
    <sup>
        <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server">wiki</a>
    </sup>
</span>
 if it chooses.
The client may send the PIN salt and client PIN hash in <a href="https://gtker.com/wow_messages/docs/cmd_auth_logon_proof_client.html">
    <code>CMD_AUTH_LOGON_PROOF_Client</code>
</a>
<span>
    <sup>
        <a href="https://wowdev.wiki/CMD_AUTH_LOGON_PROOF_Client">wiki</a>
    </sup>
</span>
 in the field just after the client proof.</p>
<p>Figure 1 shows the PIN window during login.</p>
<figure>
    
    <img src="pin.png" alt="The PIN window shown during login after entering password."></img>
    
    <figcaption><b>Figure 1:</b> The PIN window shown during login after entering password.
        <br>
        
</figure>

<p>The client does not require the server to prove that expected key matches like it does for the primary authentication check.</p>
<p>This will work for all versions after 1.12.</p>
<p>The PIN the client enters can either be a static (it never changes) or be <a href="https://en.wikipedia.org/wiki/Time-based_one-time_password">time based</a>, the client just sends the digits entered by the user.</p>
<p>PINs can be between 4 and 10 digits long.</p>
<p>The implementation is the same for the server and client.
They will both calculate a hash for the entered digits and the server will then compare the two hashes for equality.</p>
<p>The grid of the PIN can be randomized with the PIN grid seed sent by the server.
This will move the locations of the numbers so that they are no longer in order.
So instead of the keypad being <code>1, 2, 3, ...</code>, it could be <code>9, 2, 5, ...</code>.
Figure 2 shows the randomized PIN window.</p>
<figure>
    
    <img src="randomized-pin.png" alt="The randomized PIN window."></img>
    
    <figcaption><b>Figure 2:</b> The randomized PIN window.
        <br>
        
</figure>

<h1 id="implementation-details">Implementation details</h1>
<p><a href="#main-algorithm">The algorithm</a> goes through the following steps:</p>
<ul>
<li>Get the PIN from the client or database.</li>
<li><a href="#remap-pin-grid">Use the PIN grid seed to randomize the grid.</a></li>
<li><a href="#randomize-grid">Apply the randomized grid to the PIN.</a></li>
<li>Convert PIN to ASCII by adding <code>0x30</code> (48) to every value.</li>
<li>The PIN is SHA1 hashed twice along with server and client salts.</li>
</ul>
<p>The PIN is in the form of a byte array of at least 4 bytes and at most 10 bytes, where every byte is a single digit of the PIN.
So a PIN of <code>1, 2, 3, 4</code> would be an array of <code>[1, 2, 3, 4]</code>.</p>
<h2 id="main-algorithm">Main Algorithm</h2>
<p>This function ties everything together.</p>
<ul>
<li><code>pin</code> is the digits of the PIN code as a byte array.</li>
<li><code>pin_grid_seed</code> is the PIN grid seed.</li>
<li><code>server_salt</code> is the salt sent by the server.</li>
<li><code>client_salt</code> is the salt sent by the client.</li>
<li><code>%</code> is the modulus operator.</li>
<li><code>+</code> is array concatenation.</li>
<li><code>SHA1</code> is the SHA1 hash function.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function calculate_hash
</span></span><span style="display:flex;"><span>    argument pin: array of bytes
</span></span><span style="display:flex;"><span>    argument pin_grid_seed: u32
</span></span><span style="display:flex;"><span>    argument server_salt: array of 16 bytes
</span></span><span style="display:flex;"><span>    argument client_salt: array of 16 bytes
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    remapped_pin_grid = remap_pin_grid(pin_grid_seed)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    randomized_grid = randomize_grid(pin, remapped_pin_grid)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # Convert to ASCII
</span></span><span style="display:flex;"><span>    for b in randomized_grid:
</span></span><span style="display:flex;"><span>        b += 0x30
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    first_hash = SHA1(server_salt + randomized_grid)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return SHA1(client_salt + first_hash)
</span></span></code></pre></div><h3 id="implementations">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/6f9501c55dec35fe1ca5c2b4f2b19561d8b43244/src/pin.rs#L136">Rust (wow_srp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/953e6dcd8e006c49c56fd8b54c95cf00aba71f06/src/login/PINAuthenticator.cpp#L118">C++ (Ember)</a></li>
</ul>
<h3 id="verification-values">Verification values</h3>
<ul>
<li><a href="verification_values/regression.txt">regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>pin</code></th>
<th><code>pin_grid_seed</code></th>
<th><code>server_salt</code></th>
<th><code>client_salt</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned integer</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="remap-pin-grid">Remap PIN Grid</h2>
<p>This function converts the PIN grid seed into a remapped array.</p>
<ul>
<li><code>pin_grid_seed</code> is sent by the server in <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server"><code>CMD_AUTH_LOGON_CHALLENGE_Server</code></a>.</li>
<li><code>%</code> is the modulus operator.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function remap_pin_grid
</span></span><span style="display:flex;"><span>    argument pin_grid_seed: u32
</span></span><span style="display:flex;"><span>    returns array of 10 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    grid = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
</span></span><span style="display:flex;"><span>    remapped_grid = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # Go backwards
</span></span><span style="display:flex;"><span>    # Start on 10 and do not go to 0
</span></span><span style="display:flex;"><span>    for i in 10..0:
</span></span><span style="display:flex;"><span>        # Iteration count, could also use enumerate()
</span></span><span style="display:flex;"><span>        remapped_index = 10 - i
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        remainder = pin_grid_seed % i
</span></span><span style="display:flex;"><span>        pin_grid_seed = pin_grid_seed / i
</span></span><span style="display:flex;"><span>        remapped_grid[remapped_index] = grid[remainder]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        copy_size = i - remainder - 1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        for j in 0..copy_size:
</span></span><span style="display:flex;"><span>            grid[remainder + j] = grid[remainder + j + 1]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return remapped_grid
</span></span></code></pre></div><h3 id="implementations-1">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/6f9501c55dec35fe1ca5c2b4f2b19561d8b43244/src/pin.rs#L180">Rust (wow_srp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/953e6dcd8e006c49c56fd8b54c95cf00aba71f06/src/login/PINAuthenticator.cpp#L67">C++ (Ember)</a></li>
</ul>
<h3 id="verification-values-1">Verification values</h3>
<ul>
<li><a href="verification_values/remap_regression.txt">remap_regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>pin_grid_seed</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Unsigned integer</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="randomize-grid">Randomize Grid</h2>
<p>This function converts the PIN grid seed into a remapped array.</p>
<ul>
<li><code>bytes</code> is the PIN as a byte array.</li>
<li><code>remapped_pin_grid</code> is the remapped PIN grid created by <code>remap_pin_grid</code>.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function randomize_grid
</span></span><span style="display:flex;"><span>    argument bytes: array of bytes
</span></span><span style="display:flex;"><span>    argument remapped_pin_grid: array of 10 bytes
</span></span><span style="display:flex;"><span>    # Can return a new array if modifying bytes in place is not possible
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    for i, byte in enumerate(bytes):
</span></span><span style="display:flex;"><span>        remapped_value = index of byte value from remapped_pin_grid
</span></span><span style="display:flex;"><span>        bytes[i] = remapped_value
</span></span></code></pre></div><h3 id="implementations-2">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/6f9501c55dec35fe1ca5c2b4f2b19561d8b43244/src/pin.rs#L199">Rust (wow_srp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/953e6dcd8e006c49c56fd8b54c95cf00aba71f06/src/login/PINAuthenticator.cpp#L97-L98">C++ (Ember)</a></li>
</ul>
<h3 id="verification-values-2">Verification values</h3>
<ul>
<li><a href="verification_values/convert_regression.txt">convert_regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>bytes</code></th>
<th><code>remapped_pin_grid</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
]]></content></entry><entry><title type="html">Implementation Guide for the World of Warcraft client integrity check</title><link href="https://gtker.com/implementation-guide-for-the-world-of-warcraft-client-integrity-check/"/><id>https://gtker.com/implementation-guide-for-the-world-of-warcraft-client-integrity-check/</id><author><name>Gtker</name></author><published>2024-04-10T00:00:00+00:00</published><updated>2024-04-10T00:00:00+00:00</updated><content type="html"><![CDATA[<p>When an original World of Warcraft client (1.2 through to 1.12, 2.0 and newer uses a different method) authenticates with the server the client will send a hash of some local game files.
This post details how the client calculates the hash for World of Warcraft 1.12 and provides replication values in order to test your own implementation.</p>
<h1 id="background">Background</h1>
<p>The server sends a checksum salt in <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server"><code>CMD_AUTH_LOGON_CHALLENGE_Server</code></a> in the field just after the SRP6 salt.
The client sends the final hash in <a href="https://wowdev.wiki/CMD_AUTH_LOGON_PROOF_Client"><code>CMD_AUTH_LOGON_PROOF_Client</code></a> in the field just after the client proof.</p>
<p>The client does not require the server to make this calculation like it does for the SRP6 algorithm and malicious clients can just fake the proof, so there&rsquo;s no real security benefits to implementing this check.</p>
<p>The integrity check is different for login and reconnection.
The integrity check is not platform specific, but the specific files and contents of those files are platform and version specific.</p>
<p>This guide is specifically for 1.12, although it will probably work on other versions as well.</p>
<h1 id="implementation-details-for-login">Implementation details for login</h1>
<p>The implementation is a HMAC-SHA1 of game files together with the server provided checksum salt, and then a SHA1 of the resulting HMAC along with the client public key.
The implementation is not different for Mac/Windows, it only differs in the files that are checked.</p>
<h2 id="generic">Generic</h2>
<p>This describes a function that can be used for both Windows and Mac without any modifications.
The algorithm is the same as the platform specific versions, but all the checked files are just concatenated into a single slice to make the function generic.</p>
<ul>
<li><code>checksum_salt</code> is sent by the server in <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server"><code>CMD_AUTH_LOGON_CHALLENGE_Server</code></a> in the field just after the SRP6 salt.</li>
<li><code>all_files</code> is all platform specific files concatenated together. See the platform specific versions for which specific files.</li>
<li><code>client_public_key</code> is the client public key provided by the client.</li>
<li><code>+</code> is array concatenation.</li>
</ul>
<p>Calculating the integrity hash in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function integrity_check_generic
</span></span><span style="display:flex;"><span>    argument all_files: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument checksum_salt: array of 16 bytes
</span></span><span style="display:flex;"><span>    argument client_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    checksum_hmac = HmacSha1(checksum_salt)
</span></span><span style="display:flex;"><span>    checksum_hmac.update(all_files)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    checksum = checksum_hmac.finalize()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return SHA1(client_public_key + checksum)
</span></span></code></pre></div><h3 id="implementations">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/119468a4896b42f7e1bc2ecdfeb790592dd173ea/src/integrity.rs#L38">Rust (wow_srp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/db3cede29360a68bb516b80e60215181d8c01e9e/src/login/ExecutablesChecksum.cpp#L15">C++ (Ember)</a></li>
</ul>
<h3 id="verification-values">Verification values</h3>
<ul>
<li><a href="verification_values/generic_regression.txt">generic_regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>all_files</code></th>
<th><code>checksum_salt</code></th>
<th><code>client_public_key</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="windows">Windows</h2>
<p>The windows integrity calculation uses the generic version, but it specifically enumerates the files required for Windows.</p>
<ul>
<li><code>wow_exe</code>, the bytes of the <code>WoW.exe</code> file.</li>
<li><code>fmod_dll</code>, the bytes of the <code>fmod.dll</code> file.</li>
<li><code>ijl15_dll</code>, the bytes of the <code>ijl15.dll</code> file.</li>
<li><code>dbghelp_dll</code>, the bytes of the <code>dbghelp.dll</code> file.</li>
<li><code>unicows_dll</code>, the bytes of the <code>unicows.dll</code> file.</li>
<li><code>checksum_salt</code> is sent by the server in <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server"><code>CMD_AUTH_LOGON_CHALLENGE_Server</code></a> in the field just after the SRP6 salt.</li>
<li><code>client_public_key</code> is the client public key provided by the client.</li>
<li><code>+</code> is array concatenation.</li>
</ul>
<p>Calculating the integrity hash in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function integrity_check_windows
</span></span><span style="display:flex;"><span>    argument wow_exe: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument fmod_dll: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument ijl15_dll: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument dbghelp_dll: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument unicows_dll: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument checksum_salt: array of 16 bytes
</span></span><span style="display:flex;"><span>    argument client_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    all_files = wow_exe + fmod_dll + ijl15_dll + dbghelp_dll + unicows_dll
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return integrity_check_generic(all_files, checksum_salt, client_public_key)
</span></span></code></pre></div><h3 id="verification-values-1">Verification values</h3>
<ul>
<li><a href="verification_values/windows_regression.txt">windows_regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>wow_exe</code></th>
<th><code>fmod_dll</code></th>
<th><code>ijl15_dll</code></th>
<th><code>dbghelp_dll</code></th>
<th><code>unicows_dll</code></th>
<th><code>checksum_salt</code></th>
<th><code>client_public_key</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td></td>
</tr>
</tbody>
</table>
<h2 id="mac">Mac</h2>
<p>The windows integrity calculation uses the generic version, but it specifically enumerates the files required for Windows.</p>
<ul>
<li><code>world_of_warcraft</code>, the bytes of the <code>MacOS/World of Warcraft</code> file.</li>
<li><code>info_plist</code>, the bytes of the <code>Info.plist</code> file.</li>
<li><code>objects_xib</code>, the bytes of the <code>Resources/Main.nib/objects.xib</code> file.</li>
<li><code>wow_icns</code>, the bytes of the <code>Resources/wow.icns</code> file.</li>
<li><code>pkg_info</code>, the bytes of the <code>PkgInfo</code> file.</li>
<li><code>checksum_salt</code> is sent by the server <a href="https://wowdev.wiki/CMD_AUTH_LOGON_CHALLENGE_Server"><code>CMD_AUTH_LOGON_CHALLENGE_Server</code></a> in the field just after the SRP6 salt.</li>
<li><code>client_public_key</code> is the client public key provided by the client.</li>
<li><code>+</code> is array concatenation.</li>
</ul>
<p>Calculating the integrity hash in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function integrity_check_windows
</span></span><span style="display:flex;"><span>    argument world_of_warcraft: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument info_plist: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument objects_xib: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument wow_icns: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument pkg_info: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>    argument checksum_salt: array of 16 bytes
</span></span><span style="display:flex;"><span>    argument client_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    all_files = world_of_warcraft + info_plist + objects_xib + ow_icns + pkg_info
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return integrity_check_generic(all_files, checksum_salt, client_public_key)
</span></span></code></pre></div><h3 id="verification-values-2">Verification values</h3>
<ul>
<li><a href="verification_values/mac_regression.txt">mac_regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>world_of_warcraft</code></th>
<th><code>info_plist</code></th>
<th><code>objects_xib</code></th>
<th><code>wow_icns</code></th>
<th><code>pkg_info</code></th>
<th><code>checksum_salt</code></th>
<th><code>client_public_key</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td></td>
</tr>
</tbody>
</table>
<h1 id="implementation-details-for-reconnect">Implementation details for reconnect</h1>
<p>The reconnection integrity check does away with any local files and simply gets the SHA1 of the provided salt along with a zero buffer.
This is likely to save on resources since it would be very unlikely for the files to change in between login and reconnecting.</p>
<ul>
<li><code>checksum_salt</code> is sent by the server <a href="https://wowdev.wiki/CMD_AUTH_RECONNECT_CHALLENGE_Server"><code>CMD_AUTH_RECONNECT_CHALLENGE_Server</code></a> in the field just after the SRP6 salt.</li>
<li><code>+</code> is array concatenation.</li>
</ul>
<p>Calculating the reconnect integrity hash in pseudo code would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function integrity_check_reconnect
</span></span><span style="display:flex;"><span>    argument checksum_salt: array of an arbitrary amount of bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # Array of 20 bytes, filled with zero
</span></span><span style="display:flex;"><span>    all_zero = [0; 20]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return SHA1(checksum_salt + all_zero)
</span></span></code></pre></div><h3 id="verification-values-3">Verification values</h3>
<ul>
<li><a href="verification_values/reconnect_regression.txt">reconnect_regression</a></li>
</ul>
<table>
<thead>
<tr>
<th><code>checksum_salt</code></th>
<th><code>expected</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big endian Hex</td>
<td>Big endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="implementations-1">Implementations</h3>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/119468a4896b42f7e1bc2ecdfeb790592dd173ea/src/integrity.rs#L120">Rust (wow_srp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/03c130d3d6276e7032fc9e13c9d287ea7c6ed536/src/login/LoginHandler.cpp#L370">C++ (Ember)</a></li>
</ul>
]]></content></entry><entry><title type="html">Implementation Guide for the World of Warcraft flavor of SRP6</title><link href="https://gtker.com/implementation-guide-for-the-world-of-warcraft-flavor-of-srp6/"/><id>https://gtker.com/implementation-guide-for-the-world-of-warcraft-flavor-of-srp6/</id><author><name>Gtker</name></author><published>2021-07-05T00:00:00+00:00</published><updated>2025-08-17T00:00:00+00:00</updated><content type="html"><![CDATA[<p>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 <a href="https://tools.ietf.org/html/rfc2945">RFC2945</a>,
the 8 page, 20 year old, very broad and unspecific RFC that describes SRP6 in relatively academic terms.</p>
<p>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.</p>
<p>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.</p>
<p>Important terms have been highlighted with <strong>bold</strong>. The exact definitions of these will come later in the article.</p>
<p>This article will only cover the SRP6 protocol, not the network aspects of authenticating with a client.</p>
<p><strong>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.</strong></p>
<h2 id="ensure-that-you-actually-need-to-implement-this-from-scratch">Ensure that you actually need to implement this from scratch</h2>
<p>There is little reason to implement this from scratch if you can just use an existing library for it.</p>
<p>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&rsquo;t be used as libraries.</p>
<table>
<thead>
<tr>
<th>Name and link</th>
<th>Author</th>
<th>Language</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/gtker/wow_srp">wow_srp</a></td>
<td>Gtker</td>
<td>Rust</td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/gtker/wow_srp_python">wow_srp_python</a></td>
<td>Gtker</td>
<td>Python</td>
<td>Python bindings to <a href="https://github.com/gtker/wow_srp">wow_srp</a>.</td>
</tr>
<tr>
<td><a href="https://github.com/gtker/wow_srp_csharp">wow_srp_csharp</a></td>
<td>Gtker</td>
<td>C#</td>
<td>Native .NET Standard 2.1 compatible.</td>
</tr>
<tr>
<td><a href="https://github.com/gtker/wow_srp_c">wow_srp_c</a></td>
<td>Gtker</td>
<td>C/C++</td>
<td>C/C++ bindings to <a href="https://github.com/gtker/wow_srp">wow_srp</a>.</td>
</tr>
<tr>
<td><a href="https://github.com/oiramario/wow_srp6/tree/main">wow_srp6</a></td>
<td>oiramario</td>
<td>Python</td>
<td>Native Python implementation.</td>
</tr>
<tr>
<td><a href="https://github.com/Kangaroux/go-wow-srp6">go-wow-srp6</a></td>
<td>Kangaroux</td>
<td>Go</td>
<td>Native Go implementation.</td>
</tr>
</tbody>
</table>
<h2 id="srp6-overview">SRP6 Overview</h2>
<p>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.</p>
<p>When a new account is added, the server generates a random <strong>salt</strong> value and calculates
a <strong>password verifier</strong> before saving the <strong>username</strong>, <strong>salt</strong> and <strong>password verifier</strong> to the database.</p>
<p>When authenticating, the protocol works in 5 different stages:</p>
<ol>
<li>The client sends their <strong>username</strong> to the server.</li>
<li>The server retrieves the <strong>password verifier</strong> and <strong>salt</strong> from the database, then it randomly generates
a <strong>private key</strong> which it uses to calculate a <strong>public key</strong>. It then sends the <strong>public key</strong> and <strong>salt</strong> to the client,
as well as a <strong>large safe prime</strong> and a <strong>generator</strong>.</li>
<li>The client generates a <strong>private key</strong> and calculates a <strong>public key</strong>. Then it calculates a <strong>session key</strong> and <strong>client proof</strong>. It then
sends both the <strong>public key</strong> and <strong>client proof</strong> to the server.</li>
<li>The server calculates a <strong>session key</strong>, then calculates the <strong>client proof</strong> 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 <strong>server proof</strong> to the client to prove that it also knows the correct password.</li>
<li>If the client gets an identical <strong>server proof</strong> it is now authenticated and will send the &ldquo;Send Realmlist&rdquo; packet to ask the server to send a realmlist.</li>
<li>If the client loses connection, it can reconnect by proving that it knows the original session key. More on this in the <a href="#reconnecting">Reconnecting</a> section.</li>
</ol>
<p>The above description can also be seen as a sequence diagram in figure 1.</p>
<figure>
    
    <img src="srp6-overview.svg" alt="A sequence diagram of the SRP6 process as previously described."></img>
    
    <figcaption><b>Figure 1:</b> A sequence diagram of the SRP6 process as previously described.
        <br>
        <a href="srp6-overview.wsd">PlantUML Source</a></figcaption>
</figure>

<p>There are some important conceptual points to remember:</p>
<ol>
<li>The <strong>private keys</strong> are never sent over the network. Only the client will know what the <strong>client private key</strong> is, and only
the server will know what the <strong>server private key</strong> is. Both values are chosen completely at random and are not identical.</li>
<li>The <strong>public keys</strong> are sent over the network and are not identical.</li>
<li>The <strong>session keys</strong> are calculated by both the client and the server based on different variables.
These keys are identical and the <strong>client proof</strong> and <strong>server proof</strong> prove to the other part that they both have the same <strong>session key</strong>
without saying what the key is.</li>
<li>The <strong>client proof</strong> and <strong>server proof</strong> are calculated using the public information and their respective <strong>session keys</strong>. This means that the
client calculates a <strong>client proof</strong>, then the server calculates a <strong>client proof</strong> using the same algorithm and if the <strong>client proofs</strong> match the <strong>session keys</strong> used are identical.
The same happens for the <strong>server proofs</strong>.</li>
</ol>
<p><a href="#reconnecting">Reconnecting</a> and <a href="#world-packet-header-encryption">packet header encryption</a> are described in their own sections.</p>
<h2 id="important-knowledge-before-starting">Important Knowledge Before Starting</h2>
<p>Before we dive into the implementation itself, there are a few things you should know that would
ease your implementation.</p>
<h3 id="integers">Integers</h3>
<p>You will need to convert an array of bytes to a number, this is often called a <code>BigInt</code>, <code>BigInteger</code> or arbitrary size integer.
You might need a third party library for this although some languages do have standard library support for this.</p>
<p>The integers should be <strong>signed and positive</strong>.</p>
<h4 id="modulo-vs-remainder">Modulo vs Remainder</h4>
<p>It&rsquo;s important that your <code>BigInteger</code> implementation uses modulo rather than the remainder for <code>mod_pow</code>.
With remainder, <code>(-2)^3 % 9 = -8</code> while with modulo <code>(-2)^3 % 9 = 1</code>.
Test your <code>BigInteger</code> implementation and if it uses remainder, add the <strong>large safe prime</strong> to the result to get the correct result.
For example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>large_safe_prime <span style="color:#f92672">=</span> <span style="color:#ae81ff">9</span>
</span></span><span style="display:flex;"><span>a <span style="color:#f92672">=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>b <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>result <span style="color:#f92672">=</span> a<span style="color:#f92672">.</span>mod_pow(b, large_safe_prime)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># if result == 1 you don&#39;t need to do anything else</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># if result == -8 you need to create a wrapper around your mod_pow like so:</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">real_mod_pow</span>(a, b, large_safe_prime):
</span></span><span style="display:flex;"><span>    c <span style="color:#f92672">=</span> a<span style="color:#f92672">.</span>mod_pow(b, large_safe_prime)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> c <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>        c <span style="color:#f92672">+=</span> large_safe_prime
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> c
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>result <span style="color:#f92672">=</span> real_mod_pow(a, b, large_safe_prime)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># result is now 1</span>
</span></span></code></pre></div><h4 id="convertion-of-hexadecimal-strings-to-byte-arrays">Convertion of hexadecimal strings to byte arrays</h4>
<p>When using the provided examples you will need to convert a hexadecimal string to arrays of bytes.
It&rsquo;s important to understand the difference between a byte array of a hexadecimal integer, and a byte array of a string.</p>
<p>Hexadecimal strings can be thought of as containing a byte every 2 characters.
So the string <code>FF</code> would be an array with a single element containing the value <code>255</code> (<code>0xFF</code>), while the string <code>FFEE</code> would be an array with two elements containg the value <code>255</code> (<code>0xFF</code>) and the value <code>238</code> (<code>0xEE</code>).</p>
<p>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 <code>FF</code> would contain two elements with the value <code>70</code> (0x46) and the value <code>70</code>, since these are the ASCII/UTF-8 values of the <code>F</code> character.</p>
<p>Consider two the functions <code>HexToBytes</code> and <code>StringToBytes</code>.
<code>HexToBytes</code> converts a hexadecimal string to an array of the byte values while <code>StringToBytes</code> converts any string to an array of the character values.</p>
<p>If you read one of the examples and get the hexadecimal string <code>ABCDEF</code> 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.</p>
<p>The array returned by <code>HexToBytes</code> for the string <code>ABCDEF</code> would contain 3 elements (<code>[ 0xAB, 0xCD, 0xEF ]</code>), while the array returned by <code>StringToBytes</code> for the string <code>ABCDEF</code> would contain 6 elements (<code>[ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 ]</code>).</p>
<h3 id="endianness">Endianness</h3>
<p>All operations are performed on <strong>little endian</strong> arrays.
This means that if your arbitrary size integer has generic
<code>as_bytes()</code>, <code>to_bytes()</code> or <code>from_bytes()</code> functions, you will need to investigate which endianness it expects and outputs.</p>
<p>To visualize endianness consider the <a href="https://simple.wikipedia.org/wiki/Hexadecimal">hexadecimal (base/radix 16)</a> number <code>0xABCD</code> (43981 in <a href="https://simple.wikipedia.org/wiki/Decimal_numeral_system">decimal (base/radix 10)</a>).</p>
<p>Most hexadecimal string parsers would consider the number to be in big endian representation with the most significant digit <code>A</code> to the far left representing the digit that would affect the number most if changed, in the same way that <code>1</code> is the most significant digit in <code>100</code> (one hundred).</p>
<p>If you convert the number to big endian bytes the most significant byte, <code>AB</code>, is in the first position and the rest follow along.
In little endian bytes the least significant byte, <code>CD</code>, is the in the first position followed by the second least significant byte, <code>AB</code> in this case.</p>
<p>See the psuedo code below to build a mental model of endianness.</p>
<pre tabindex="0"><code>BigInt value = 0xABCD
byte[] big_endian = { 0xAB, 0xCD }
byte[] little_endian = { 0xCD, 0xAB }
</code></pre><p>To test your <code>BigInt</code> implementation, try to load up the hexadecimal number <code>ABCD</code> and get it as bytes.
The Rust <code>num_bigint</code> library has both <code>to_bytes_le()</code> and <code>to_bytes_be()</code> so there&rsquo;s no confusion.
Make a note of whether the bytes you get are little endian or big endian.</p>
<p><a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=f5f7ea4392b514ca20be74d23ad6f122">Rust Playground</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> num_bigint; <span style="color:#75715e">// 0.4.0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">use</span> num_traits::Num;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> number <span style="color:#f92672">=</span> num_bigint::BigInt::from_str_radix(<span style="color:#e6db74">&#34;ABCD&#34;</span>, <span style="color:#ae81ff">16</span>).unwrap();
</span></span><span style="display:flex;"><span>    dbg!(<span style="color:#f92672">&amp;</span>number.to_bytes_be()); <span style="color:#75715e">// Outputs [ 171, 205 ] which is [ 0xAB, 0xCD ] in hex
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    dbg!(<span style="color:#f92672">&amp;</span>number.to_bytes_le()); <span style="color:#75715e">// Outputs [ 205, 171 ] which is [ 0xCD, 0xAB ] in hex
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>Remember that there&rsquo;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.</p>
<h3 id="padding">Padding</h3>
<p>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.</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Size (bytes)</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<tr>
<td>Session Key</td>
<td>40</td>
<td>Two SHA-1 hashes appended are always 40 bytes.</td>
</tr>
<tr>
<td>Server and Client Proof</td>
<td>20</td>
<td>Result of SHA-1 hash is always 20 bytes.</td>
</tr>
<tr>
<td>Public Key</td>
<td>32</td>
<td>Packet field is always 32 bytes.</td>
</tr>
<tr>
<td>Private Key</td>
<td>32</td>
<td>Arbitrarily chosen.</td>
</tr>
<tr>
<td>Salt</td>
<td>32</td>
<td>Packet field is always 32 bytes.</td>
</tr>
<tr>
<td>Password Verifier</td>
<td>32</td>
<td>Result of modulus large safe prime is always 32 bytes or less.</td>
</tr>
<tr>
<td>Large Safe Prime</td>
<td>32</td>
<td>Largest value client will accept. Any larger can result in public key not fitting into packet field.</td>
</tr>
<tr>
<td>S Key</td>
<td>32</td>
<td>Result of modulus large safe prime is always 32 bytes or less.</td>
</tr>
<tr>
<td>Reconnect Seed Data</td>
<td>16</td>
<td>Packet field is always 16 bytes for both client and server.</td>
</tr>
<tr>
<td>Generator</td>
<td>1</td>
<td>There is no reason to have a generator value above 255.</td>
</tr>
</tbody>
</table>
<h3 id="hashing">Hashing</h3>
<p>All operations use SHA-1 which produces an output of 20 bytes.</p>
<p>To test that your SHA-1 implementation works correctly, ensure that hashing the string <code>test</code> leads to the output of <code>a94a8fe5ccb19ba61c4c0873d391e987982fbbd3</code>, and that hashing the bytes <code>0x53 0x51</code> leads to <code>0c3d7a19ac7c627290bf031ec3df76277b0f7f75</code>.</p>
<h3 id="constants">Constants</h3>
<p>The <strong>large safe prime</strong> should always be <code>0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7</code> (big endian string).
The <strong>large safe prime</strong> can be expressed as a <strong>little endian</strong> Rust array like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">const</span> LARGE_SAFE_PRIME_LITTLE_ENDIAN: [<span style="color:#66d9ef">u8</span>; <span style="color:#ae81ff">32</span>] <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xb7</span>, <span style="color:#ae81ff">0x9b</span>, <span style="color:#ae81ff">0x3e</span>, <span style="color:#ae81ff">0x2a</span>, <span style="color:#ae81ff">0x87</span>, <span style="color:#ae81ff">0x82</span>, <span style="color:#ae81ff">0x3c</span>, <span style="color:#ae81ff">0xab</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x8f</span>, <span style="color:#ae81ff">0x5e</span>, <span style="color:#ae81ff">0xbf</span>, <span style="color:#ae81ff">0xbf</span>, <span style="color:#ae81ff">0x8e</span>, <span style="color:#ae81ff">0xb1</span>, <span style="color:#ae81ff">0x01</span>, <span style="color:#ae81ff">0x08</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x53</span>, <span style="color:#ae81ff">0x50</span>, <span style="color:#ae81ff">0x06</span>, <span style="color:#ae81ff">0x29</span>, <span style="color:#ae81ff">0x8b</span>, <span style="color:#ae81ff">0x5b</span>, <span style="color:#ae81ff">0xad</span>, <span style="color:#ae81ff">0xbd</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x5b</span>, <span style="color:#ae81ff">0x53</span>, <span style="color:#ae81ff">0xe1</span>, <span style="color:#ae81ff">0x89</span>, <span style="color:#ae81ff">0x5e</span>, <span style="color:#ae81ff">0x64</span>, <span style="color:#ae81ff">0x4b</span>, <span style="color:#ae81ff">0x89</span>
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><p>or as a <strong>big endian</strong> Rust array like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">const</span> LARGE_SAFE_PRIME_BIG_ENDIAN: [<span style="color:#66d9ef">u8</span>; <span style="color:#ae81ff">32</span>] <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x89</span>, <span style="color:#ae81ff">0x4b</span>, <span style="color:#ae81ff">0x64</span>, <span style="color:#ae81ff">0x5e</span>, <span style="color:#ae81ff">0x89</span>, <span style="color:#ae81ff">0xe1</span>, <span style="color:#ae81ff">0x53</span>, <span style="color:#ae81ff">0x5b</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xbd</span>, <span style="color:#ae81ff">0xad</span>, <span style="color:#ae81ff">0x5b</span>, <span style="color:#ae81ff">0x8b</span>, <span style="color:#ae81ff">0x29</span>, <span style="color:#ae81ff">0x06</span>, <span style="color:#ae81ff">0x50</span>, <span style="color:#ae81ff">0x53</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x08</span>, <span style="color:#ae81ff">0x01</span>, <span style="color:#ae81ff">0xb1</span>, <span style="color:#ae81ff">0x8e</span>, <span style="color:#ae81ff">0xbf</span>, <span style="color:#ae81ff">0xbf</span>, <span style="color:#ae81ff">0x5e</span>, <span style="color:#ae81ff">0x8f</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xab</span>, <span style="color:#ae81ff">0x3c</span>, <span style="color:#ae81ff">0x82</span>, <span style="color:#ae81ff">0x87</span>, <span style="color:#ae81ff">0x2a</span>, <span style="color:#ae81ff">0x3e</span>, <span style="color:#ae81ff">0x9b</span>, <span style="color:#ae81ff">0xb7</span>,
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><p>The <strong>generator</strong> should always be <code>7</code>.</p>
<p>The <strong>k</strong> value should always be <code>3</code>.</p>
<p>The <strong>XOR Hash</strong> with these values will always be <code>0xA7C27B6C96CA6F505A7C98031173AC383AB07BDD</code> (big endian string).
The <strong>XOR Hash</strong> can be expressed as a <strong>little endian</strong> Rust array like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> PRECALCULATED_XOR_HASH: [<span style="color:#66d9ef">u8</span>; <span style="color:#ae81ff">20</span>] <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xdd</span>, <span style="color:#ae81ff">0x7b</span>, <span style="color:#ae81ff">0xb0</span>, <span style="color:#ae81ff">0x3a</span>, <span style="color:#ae81ff">0x38</span>, <span style="color:#ae81ff">0xac</span>, <span style="color:#ae81ff">0x73</span>, <span style="color:#ae81ff">0x11</span>, <span style="color:#ae81ff">0x3</span>, <span style="color:#ae81ff">0x98</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x7c</span>, <span style="color:#ae81ff">0x5a</span>, <span style="color:#ae81ff">0x50</span>, <span style="color:#ae81ff">0x6f</span>, <span style="color:#ae81ff">0xca</span>, <span style="color:#ae81ff">0x96</span>, <span style="color:#ae81ff">0x6c</span>, <span style="color:#ae81ff">0x7b</span>, <span style="color:#ae81ff">0xc2</span>, <span style="color:#ae81ff">0xa7</span>,
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><h3 id="aliases">Aliases</h3>
<p>Previously terms like <strong>client public key</strong> and <strong>large safe prime</strong> have been used instead of the single letters <code>A</code> and <code>N</code> which are used in <a href="https://tools.ietf.org/html/rfc2945">RFC2945</a>.</p>
<p>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.</p>
<table>
<thead>
<tr>
<th>Short Form</th>
<th>Long Form</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>A</code></td>
<td>Client public key</td>
</tr>
<tr>
<td><code>a</code></td>
<td>Client private key</td>
</tr>
<tr>
<td><code>B</code></td>
<td>Server public key</td>
</tr>
<tr>
<td><code>b</code></td>
<td>Server private key</td>
</tr>
<tr>
<td><code>N</code></td>
<td>Large safe prime</td>
</tr>
<tr>
<td><code>g</code></td>
<td>Generator</td>
</tr>
<tr>
<td><code>k</code></td>
<td>K value</td>
</tr>
<tr>
<td><code>s</code></td>
<td>Salt</td>
</tr>
<tr>
<td><code>U</code></td>
<td>Username</td>
</tr>
<tr>
<td><code>p</code></td>
<td>Password</td>
</tr>
<tr>
<td><code>v</code></td>
<td>Password verifier</td>
</tr>
<tr>
<td><code>M1</code></td>
<td>Client proof (proof first sent by client, calculated by both)</td>
</tr>
<tr>
<td><code>M2</code></td>
<td>Server proof (proof first sent by server, calculated by both)</td>
</tr>
<tr>
<td><code>M</code></td>
<td>(Client or server) proof</td>
</tr>
<tr>
<td><code>S</code></td>
<td>S key</td>
</tr>
<tr>
<td><code>K</code></td>
<td>Session key</td>
</tr>
</tbody>
</table>
<h2 id="implementation-details">Implementation Details</h2>
<p>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.</p>
<p>The <a href="https://github.com/gtker/wow_srp">wow_srp</a> Rust crate implements the full algorithm, with examples for both client and server located in the <a href="https://github.com/gtker/wow_messages/tree/main/examples">examples/</a> directory.
<a href="https://github.com/gtker/wow_srp">WowSrp</a> has a C# version.
<a href="https://github.com/EmberEmu/Ember/tree/development/src/libs/srp6">Ember</a> has a C++ version.
<a href="https://gitlab.com/shadowburn/shadowburn/-/tree/master/apps/logind">Shadowburn</a> has an Elixir version.
<a href="https://github.com/oiramario/wow_srp6">wow_srp6</a> is a Python version.</p>
<p>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.</p>
<h3 id="password-verifier">Password Verifier</h3>
<p>The password verifier, <code>v</code>, is calculated as <code>v = g^x % N</code>, where:</p>
<ul>
<li>
<p><code>g</code> is the static <strong>generator</strong> equal to 7 defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>x</code> is discussed below.</p>
</li>
<li>
<p><code>%</code> is the modulo operator.</p>
</li>
<li>
<p><code>N</code> is the static <strong>large safe prime</strong> defined in <a href="#constants">Constants</a>.</p>
</li>
</ul>
<p>Calculating the password verifier in pseudo code would look like</p>
<pre tabindex="0"><code>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)
</code></pre><h5 id="implementations">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L105">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L24">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/include/srp6/Generator.h#L29">C++ (Ember)</a>. Slightly confusing way of doing it. Callsite is <a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/include/srp6/Util.h#L46">here</a>, then <a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/include/srp6/Util.h#L53">here</a> and then <a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Util.cpp#L192">here</a>.</li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/common/lib/accounts/accounts.ex#L88">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L46">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L14">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_v_values.txt">calculate_v_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>Salt</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>String</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="calculating-x">Calculating <code>x</code></h4>
<p>The <code>x</code> value is calculated as <code>x = SHA1( s | SHA1( U | : | p ))</code>, where:</p>
<ul>
<li>
<p><code>U</code> is the uppercased <strong>username</strong> of the user. See <a href="#notes-on-uppercasing-username-and-password">this section</a> for notes.</p>
</li>
<li>
<p><code>p</code> is the uppercased <strong>password</strong> of the user. See <a href="#notes-on-uppercasing-username-and-password">this section</a> for notes.</p>
</li>
<li>
<p><code>s</code> is the randomly generated <strong>salt</strong> value for the user.</p>
</li>
<li>
<p><code>|</code> is concatenating the values.</p>
</li>
<li>
<p><code>SHA1</code> is SHA-1 hashing the values.</p>
</li>
<li>
<p><code>:</code> is the literal character <code>:</code>.</p>
</li>
</ul>
<p>Calculating <code>x</code> in pseudo code would look like</p>
<pre tabindex="0"><code>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 + &#34;:&#34; + password )

    // Array concatenation
    return SHA1( salt + interim )
</code></pre><h5 id="implementations-1">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L66">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L39">C# (WowSrp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/03c130d3d6276e7032fc9e13c9d287ea7c6ed536/src/libs/srp6/src/Util.cpp#L97">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/common/lib/accounts/accounts.ex#L82">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L37">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L74">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-1">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_x_salt_values.txt">calculate_x_salt_values.txt</a> assumes a static <strong>username</strong> of <code>USERNAME123</code> and a static <strong>password</strong> of <code>PASSWORD123</code>.
It contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Salt</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<ul>
<li><a href="verification_values/calculate_x_values.txt">calculate_x_values.txt</a> assumes a static <strong>salt</strong> of <code>0xCAC94AF32D817BA64B13F18FDEDEF92AD4ED7EF7AB0E19E9F2AE13C828AEAF57</code> (big endian).
It contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>String</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="server-public-key">Server public key</h3>
<p>The <strong>server public key</strong>, <code>B</code>, is calculated as <code>B = (k * v + (g^b % N)) % N</code>, where:</p>
<ul>
<li>
<p><code>k</code> is a <strong>k value</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>v</code> is the <strong>password verifier</strong> for the user.</p>
</li>
<li>
<p><code>g</code> is a <strong>generator</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>b</code> is the <strong>server private key</strong>.</p>
</li>
<li>
<p><code>%</code> is the modulo operator.</p>
</li>
<li>
<p><code>N</code> is a <strong>large safe prime</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
</ul>
<p>Calculating the <strong>server public key</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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
</code></pre><h5 id="implementations-2">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L122">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L53">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Server.cpp#L25">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/character-login/apps/logind/lib/authenticator.ex#L386">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L55">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L22">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-2">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_B_values.txt">calculate_B_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Password Verifier</th>
<th>Server Private Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="session-key">Session key</h3>
<p>The <strong>session key</strong>, <code>K</code>, is calculated differently depending on whether it is the client or server doing the calculation because they only have access to their own <strong>private keys</strong>.</p>
<p>Both ways calculate an interim <strong>S key</strong> value that is finally <strong>interleaved</strong> in order to get the common <strong>session key</strong>.</p>
<p>For completeness, both the server and client <strong>session key</strong>, <code>K</code>, is calculated as <code>K = interleaved(S)</code>, where <code>interleaved</code> is a function described below.
The calculation of the client and server <strong>S keys</strong> are below.</p>
<h4 id="client-s-key">Client S Key</h4>
<p>The <strong>client S Key</strong>, <code>S</code>, is calculated as <code>S = (B - (k * (g^x % N)))^(a + u * x) % N</code>, where:</p>
<ul>
<li>
<p><code>g</code> is a <strong>generator</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>%</code> is the modulo operator.</p>
</li>
<li>
<p><code>N</code> is a <strong>large safe prime</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>k</code> is a <strong>k value</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>a</code> is the <strong>client private key</strong>.</p>
</li>
<li>
<p><code>B</code> is the <strong>server public key</strong>.</p>
</li>
<li>
<p><code>x</code> is an interim value described under the <a href="#calculating-x">Password Verifier</a> section.</p>
</li>
<li>
<p><code>u</code> is an interim value described below.</p>
</li>
</ul>
<p>Calculating the <strong>client S key</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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
</code></pre><h5 id="implementations-3">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal_client.rs#L24">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L98">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Client.cpp#L59">C++ (Ember)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L66">Python (wow_srp6)</a></li>
</ul>
<h5 id="verification-values-3">Verification values</h5>
<p>If you&rsquo;re getting an error on the 6th line of this file, your <code>BigInteger</code> implementation is likely using <a href="#integers">remainder instead of modulo</a>.</p>
<ul>
<li><a href="verification_values/calculate_client_S_values.txt">calculate_client_S_values.txt</a> expects a static <strong>generator</strong> and <strong>large safe prime</strong> from <a href="#constants">Constants</a>, and contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Server Public Key</th>
<th>Client Private Key</th>
<th>x</th>
<th>u</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="server-s-key">Server S Key</h4>
<p>The <strong>server S key</strong>, <code>S</code>, is calculated as <code>S = (A * (v^u % N))^b % N</code>, where:</p>
<ul>
<li>
<p><code>A</code> is the <strong>client public key</strong>.</p>
</li>
<li>
<p><code>v</code> is the <strong>password verifier</strong> for the user.</p>
</li>
<li>
<p><code>%</code> is the modulo operator.</p>
</li>
<li>
<p><code>N</code> is a <strong>large safe prime</strong> statically defined in <a href="#constants">Constants</a>.</p>
</li>
<li>
<p><code>b</code> is the <strong>server private key</strong>.</p>
</li>
<li>
<p><code>u</code> is an interim value described below.</p>
</li>
</ul>
<p>Calculating the <strong>server S key</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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
</code></pre><h5 id="implementations-4">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L148">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L77">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Server.cpp#L45">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L395">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L78">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L37">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-4">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_S_values.txt">calculate_S_values.txt</a> expects a static <strong>generator</strong> and <strong>large safe prime</strong> from <a href="#constants">Constants</a>, and contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Client Public Key</th>
<th>Password Verifier</th>
<th>u</th>
<th>Server Private Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="u">u</h4>
<p>The <code>u</code> value is calculated as <code>u = SHA1( A | B )</code>, where:</p>
<ul>
<li>
<p><code>A</code> is the client public key.</p>
</li>
<li>
<p><code>B</code> is the server public key.</p>
</li>
<li>
<p><code>SHA1</code> is SHA-1 hashing.</p>
</li>
<li>
<p><code>|</code> is array concatenation.</p>
</li>
</ul>
<p>Calculating the <strong>server S key</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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 )
</code></pre><h5 id="implementations-5">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L137">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L67">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Server.cpp#L44">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L394">Elixir (Shadowburn)</a> where it&rsquo;s called the scrambler.</li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L90">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L83">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-5">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_u_values.txt">calculate_u_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Client Public Key</th>
<th>Server Public Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="interleaved">Interleaved</h4>
<p>The <strong>interleaved</strong> function converts the <strong>S key</strong> to the <strong>session key</strong>.
It is calculated as <code>K = SHA_Interleave(S)</code>, where:</p>
<ul>
<li>
<p><code>K</code> is the <strong>session key</strong>.</p>
</li>
<li>
<p><code>S</code> is the <strong>S key</strong>.</p>
</li>
<li>
<p><code>SHA_Interleave()</code> is a function described below:</p>
</li>
</ul>
<p>If the least significant byte is 0, the <strong>two</strong> 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 <strong>S key</strong> are then hashed, as are the odd elements.
The two hashes are then zipped together, with the even elements in the first position.</p>
<p>The <code>SHA_Interleave()</code> function in pseudo code would look like:</p>
<pre tabindex="0"><code>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 &lt; 20, i += 2
        session_key[i * 2] = G[i]
        session_key[(i * 2) + 1] = H[i]

    return session_key
</code></pre><h5 id="implementations-6">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/6cfef1615cf412b6580608126922c7050ff3af8d/src/key.rs#L293">Rust (wow_srp) (<code>split_s_key</code>)</a></li>
<li><a href="https://github.com/gtker/wow_srp/blob/6cfef1615cf412b6580608126922c7050ff3af8d/src/srp_internal.rs#L156">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L122">C# (WowSrp (<code>SplitSKey</code>)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L142">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Util.cpp#L42">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L417">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L98">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L45">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-6">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_interleaved_values.txt">calculate_interleaved_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>S Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<ul>
<li><a href="verification_values/calculate_split_s_key.txt">calculate_split_s_key.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>S Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="server-session-key">Server Session Key</h4>
<p>The server session key combines all the previous functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function calculate_server_session_key:
</span></span><span style="display:flex;"><span>    argument client_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument server_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument password_verifier: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument server_private_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    returns array of 40 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    u = calculate_u(client_public_key, server_public_key)
</span></span><span style="display:flex;"><span>    S = calculate_S(client_public_key, password_verifier, u, server_private_key)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return calculate_interleaved(S)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h5 id="implementations-7">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/8e632f27c3b3850f77a9b9e5e732b295b0dadeb9/src/srp_internal.rs#L182">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L263">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/9da97165902c36b976a27764af028195da93c60a/src/libs/srp6/src/Server.cpp#L32">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L391">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L440">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/srp.go#L30">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-7">Verification values</h5>
<p>You will need to calculate the <strong>server public key</strong> using the supplied <strong>password verifier</strong> and <strong>server private key</strong> in order to test this.</p>
<ul>
<li><a href="verification_values/calculate_server_session_key.txt">calculate_server_session_key.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Client Public Key</th>
<th>Password Verifier</th>
<th>Server Private Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="client-session-key">Client Session Key</h4>
<p>The client session key combines all the previous functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function calculate_client_session_key:
</span></span><span style="display:flex;"><span>    argument username: string
</span></span><span style="display:flex;"><span>    argument password: string
</span></span><span style="display:flex;"><span>    argument server_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument client_private_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument generator: unsigned integer
</span></span><span style="display:flex;"><span>    argument large_safe_prime: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument client_public_key: array of 32 bytes
</span></span><span style="display:flex;"><span>    argument salt: array of 32 bytes
</span></span><span style="display:flex;"><span>    returns array of 40 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    x = calculate_x(username, password, salt)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    u = calculate_u(client_public_key, server_public_key)
</span></span><span style="display:flex;"><span>    S = calculate_client_S(
</span></span><span style="display:flex;"><span>        server_public_key,
</span></span><span style="display:flex;"><span>        x,
</span></span><span style="display:flex;"><span>        client_private_key,
</span></span><span style="display:flex;"><span>        u,
</span></span><span style="display:flex;"><span>        generator,
</span></span><span style="display:flex;"><span>        large_safe_prime,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return calculate_interleaved(S)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h5 id="implementations-8">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/8e632f27c3b3850f77a9b9e5e732b295b0dadeb9/src/srp_internal_client.rs#L23">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L279">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/9da97165902c36b976a27764af028195da93c60a/src/libs/srp6/src/Client.cpp#L38">C++ (Ember)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L425">Python (wow_srp6)</a></li>
</ul>
<h5 id="verification-values-8">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_client_session_key.txt">calculate_client_session_key.txt</a> contains the columns:</li>
</ul>
<p><strong>The usernames and passwords will need to be uppercased before this test will pass.</strong></p>
<table>
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>Server Public Key</th>
<th>Client Private Key</th>
<th>Generator</th>
<th>Large Safe Prime</th>
<th>Client Public Key</th>
<th>Salt</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>String</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Integer</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="server-proof">Server proof</h3>
<p>The <strong>server proof</strong>, <code>M2</code> or sometimes just <code>M</code>, is calculated as <code>M2 = SHA1(A | M1 | K)</code>, where:</p>
<ul>
<li>
<p><code>A</code> is the <strong>client public key</strong>.</p>
</li>
<li>
<p><code>M1</code> is the <strong>client proof</strong>. Remember that <code>M1</code> <strong>must</strong> be calculated and verified on the server before this calculation takes place.</p>
</li>
<li>
<p><code>K</code> is the <strong>session key</strong>.</p>
</li>
<li>
<p><code>|</code> is concatenation.</p>
</li>
</ul>
<p>Calculating the <strong>server proof</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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)
</code></pre><h5 id="implementations-9">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L211">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L176">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Server.cpp#L57">C++ (Ember)</a> calls into <a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Util.cpp#L169">here</a>.</li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L413">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L124">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/proof.go#L31">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-9">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_M2_values.txt">calculate_M2_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Client Public Key</th>
<th>Client Proof</th>
<th>Session Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="client-proof">Client proof</h3>
<p>The <strong>client proof</strong>, <code>M1</code> or sometimes just <code>M</code>, is calculated as <code>M1 = SHA1( X | SHA1(U) | s | A | B | K )</code>, where:</p>
<ul>
<li>
<p><code>X</code> is the <code>XOR Hash</code>, described below. It requires the <strong>large safe prime</strong> and the <strong>generator</strong>. Can be precalculated if both are known.</p>
</li>
<li>
<p><code>SHA1(U)</code> is a SHA-1 hash of the uppercased <strong>username</strong>.</p>
</li>
<li>
<p><code>s</code> is the <strong>salt</strong>.</p>
</li>
<li>
<p><code>A</code> is the <strong>client public key</strong>.</p>
</li>
<li>
<p><code>B</code> is the <strong>server public key</strong>.</p>
</li>
<li>
<p><code>K</code> is the <strong>session key</strong>.</p>
</li>
</ul>
<p>Calculating the <strong>client proof</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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 )
</code></pre><h5 id="implementations-10">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L241">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L188">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Util.cpp#L134">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L403">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L147">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/proof.go#L11">Go (go-wow-srp6)</a></li>
</ul>
<h5 id="verification-values-10">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_M1_values.txt">calculate_M1_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Username</th>
<th>Session Key</th>
<th>Client Public Key</th>
<th>Server Public Key</th>
<th>Salt</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>Big endian hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h4 id="xor-hash">XOR Hash</h4>
<p>This value can be precalculated on the server and you can avoid the calculation.
See the verification values for the value.</p>
<p>The <code>xor_hash</code> is calculated as <code>SHA1(N) XOR SHA1(g)</code>, where:</p>
<ul>
<li>
<p><code>SHA1(g)</code> is a SHA-1 hash of the <strong>generator</strong> statically defined in <a href="#constants">Constants</a> on the server and variable on the client.</p>
</li>
<li>
<p><code>SHA1(N)</code> is a SHA-1 hash of the <strong>large safe prime</strong> statically defined in <a href="#constants">Constants</a> on the server and variable on the client.</p>
</li>
<li>
<p><code>XOR</code> uses the XOR operator on every element of the two arrays.</p>
</li>
</ul>
<p>Calculating the XOR Hash in pseudo code would look like:</p>
<pre tabindex="0"><code>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
</code></pre><h5 id="implementations-11">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L225">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L207">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Util.cpp#L142">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L401">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L132">Python (wow_srp6)</a></li>
</ul>
<h5 id="verification-values-11">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_xor_hash.txt">calculate_xor_hash.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Generator</th>
<th>Large Safe Prime</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Decimal Number</td>
<td>Big endian hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="client-public-key">Client public key</h3>
<p>Generating the <strong>client public key</strong> is not necessary if you&rsquo;re only writing the server part.</p>
<p>The <strong>client public key</strong>, <code>A</code>, is calculated as <code>A = g^a % N</code>, where:</p>
<ul>
<li>
<p><code>g</code> is a <strong>generator</strong>. Clients do not get to choose the <strong>generator</strong>, so they can not assume a static value is used.</p>
</li>
<li>
<p><code>a</code> is the <strong>client private key</strong>.</p>
</li>
<li>
<p><code>%</code> is the modulo operator.</p>
</li>
<li>
<p><code>N</code> is a <strong>large safe prime</strong>. Clients do not get to choose the <strong>large safe prime</strong>, so they can not assume a static value is used.</p>
</li>
</ul>
<p>Calculating the <strong>client public key</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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)
</code></pre><h5 id="implementations-12">Implementations</h5>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal_client.rs#L9">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L224">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/368c9525fb0e550909fabe6ce6e39f029f8634df/src/libs/srp6/src/Client.cpp#L30">C++ (Ember)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L156">Python (wow_srp6)</a></li>
</ul>
<h5 id="verification-values-12">Verification values</h5>
<ul>
<li><a href="verification_values/calculate_A_values.txt">calculate_A_values.txt</a> expects a static <strong>generator</strong> and <strong>large safe prime</strong> as defined under <a href="#constants">Constants</a>. It contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Client Private Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="reconnecting">Reconnecting</h2>
<p>If a client loses connection with the logon server after getting a <strong>session key</strong> but before connecting to a realm, the client will attempt bypass the creation of a new <strong>session key</strong> by proving to the client that it knows the old <strong>session key</strong>.</p>
<p>When reconnecting, the protocol works in 6 steps:</p>
<ol>
<li>The client sends their <strong>username</strong> to the server.</li>
<li>The server checks for an existing session with the <strong>username</strong> and creates randomized <strong>server data</strong> that it sends to the client.</li>
<li>The client generates randomized <strong>client data</strong> and uses the <strong>server data</strong> along with the <strong>username</strong> and <strong>session key</strong> to create a <strong>reconnect proof</strong>. It then sends the <strong>client data</strong> and <strong>reconnect proof</strong> to the server.</li>
<li>The server calculates their own <strong>reconnect proof</strong> from the <strong>username</strong>, <strong>server data</strong>, <strong>client data</strong>, and <strong>session key</strong> to see if it matches the client <strong>reconnect proof</strong>.</li>
<li>The client is now authenticated again and will send the &lsquo;Send Realmlist&rsquo; packet to as the server to send a realmlist.</li>
</ol>
<p>The above description can also be seen as a sequence diagram in figure 2.</p>
<figure>
    
    <img src="reconnect-overview.svg" alt="A sequence diagram of the reconnection process as previously described."></img>
    
    <figcaption><b>Figure 2:</b> A sequence diagram of the reconnection process as previously described.
        <br>
        <a href="reconnect-overview.wsd">PlantUML Source</a></figcaption>
</figure>

<p>The only function needed for the above is calculating the <strong>reconnect proof</strong> and generating random 16 byte values.</p>
<h3 id="reconnect-proof">Reconnect proof</h3>
<p>The <strong>reconnect proof</strong> is not part of SRP6 and does not have a short term. It is calculated the same for the client and server.
The <strong>reconnect proof</strong> is calculated as <code>SHA1( username | client_data | server_data | session_key )</code>, where:</p>
<ul>
<li>
<p><code>username</code> is the <strong>username</strong> of the user attempting to reconnect.</p>
</li>
<li>
<p><code>client_data</code> is the randomized data sent by the client.</p>
</li>
<li>
<p><code>server_data</code> is the randomized data sent by the server.</p>
</li>
<li>
<p><code>session_key</code> is the <strong>session key</strong> from a previous successful authentication.</p>
</li>
<li>
<p><code>|</code> is array concatenation. Strings are converted to UTF-8 bytes.</p>
</li>
</ul>
<p>Calculating the <strong>client public key</strong> in pseudo code would look like:</p>
<pre tabindex="0"><code>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)
</code></pre><h4 id="implementations-13">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/b72d2a396d0c9db5df3a6815e8ebf23230c157fe/src/srp_internal.rs#L263">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L237">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/90b9a7b43df4744f856e81e3efcf1de92bcb4ebf/src/login/Authenticator.cpp#L59">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/logind/lib/authenticator.ex#L435">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L166">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/proof.go#L41">Go (go-wow-srp6)</a></li>
</ul>
<h4 id="verification-values-13">Verification values</h4>
<ul>
<li><a href="verification_values/calculate_reconnection_values.txt">calculate_reconnection_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Username</th>
<th>Client Data</th>
<th>Server Data</th>
<th>Session Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="vanilla-world-message-header-encryption">Vanilla World Message Header Encryption</h2>
<p><a href="https://wowdev.wiki/World_Packet">World Packets</a> encrypt their header using the <strong>session key</strong>.
None of the <a href="https://wowdev.wiki/Login_Packet">Login Packets</a> require encryption, but this section is included because it can be tedious to implement and is necessary for getting to the character screen.</p>
<p>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.</p>
<p>The &ldquo;encryption&rdquo; is not real encryption, since it&rsquo;s very easily breakable and can leak the session key which is used for reconnection.</p>
<p>To be explicit, if you&rsquo;re writing a server you will only ever <strong>decrypt</strong> headers received from the client and <strong>encrypt</strong> headers sent to the client.</p>
<p>Figure 3 shows both encryption and decryption visually.
Individual images can be found at <a href="encryption/page-0.png">step 1</a>, <a href="encryption/page-1.png">step 2</a>, <a href="encryption/page-2.png">step 3</a>, <a href="encryption/page-3.png">step 4</a> and <a href="encryption/page-4.png">step 5</a>.</p>
<figure>
    
    <img src="encryption/encryption.gif" alt="The encryption and decryption of example data. The figure is animated. Notice how decryption is just the same process in reverse."></img>
    
    <figcaption><b>Figure 3:</b> The encryption and decryption of example data. The figure is animated. Notice how decryption is just the same process in reverse.
        <br>
        
</figure>

<h3 id="encryption">Encryption</h3>
<p>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.</p>
<p>The encryption is calculated as <code>E = (x ^ S) + L</code> where:</p>
<ul>
<li>
<p><code>E</code> is the encrypted value, what is sent over the wire.</p>
</li>
<li>
<p><code>x</code> is the unencrypted value, a byte of the opcode or size.</p>
</li>
<li>
<p><code>S</code> is a byte of the session key, incrementing after every encryption.</p>
</li>
<li>
<p><code>L</code> is the last encrypted value. This is <code>E</code> from the previous iteration.</p>
</li>
</ul>
<p>In pseudo code this would look like:</p>
<pre tabindex="0"><code>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
</code></pre><h4 id="implementations-14">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/bc10da51a44b368b64fe5491dc305287bb431e74/src/header_crypto/mod.rs#L106">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/HeaderImplementation.cs#L58">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/90b9a7b43df4744f856e81e3efcf1de92bcb4ebf/src/gateway/PacketCrypto.h#L44">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/serverd/lib/session.ex#L56">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L173">Python (wow_srp6)</a></li>
</ul>
<h4 id="verification-values-14">Verification values</h4>
<ul>
<li><a href="verification_values/encryption/calculate_encrypt_values.txt">calculate_encrypt_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Session Key</th>
<th>Data (50 bytes)</th>
<th>Expected (50 bytes)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<p><a href="https://gtker.com/wow_messages/ir/implementing_world.html#message-layout">World Packet</a> headers contain a 2 byte <strong>big endian</strong> size field and either a 4 (SMSG) or 6 (CMSG) byte opcode field.
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.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>session_key <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2E</span>, <span style="color:#ae81ff">0xFE</span>, <span style="color:#ae81ff">0xE7</span>, <span style="color:#ae81ff">0xB0</span>, <span style="color:#ae81ff">0xC1</span>, <span style="color:#ae81ff">0x77</span>, <span style="color:#ae81ff">0xEB</span>, <span style="color:#ae81ff">0xBD</span>, <span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0x66</span>, <span style="color:#ae81ff">0x76</span>, <span style="color:#ae81ff">0xC5</span>, <span style="color:#ae81ff">0x6E</span>, <span style="color:#ae81ff">0xFC</span>, <span style="color:#ae81ff">0x23</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x39</span>, <span style="color:#ae81ff">0xBE</span>, <span style="color:#ae81ff">0x9C</span>, <span style="color:#ae81ff">0xAD</span>, <span style="color:#ae81ff">0x14</span>, <span style="color:#ae81ff">0xBF</span>, <span style="color:#ae81ff">0x8B</span>, <span style="color:#ae81ff">0x54</span>, <span style="color:#ae81ff">0xBB</span>, <span style="color:#ae81ff">0x5A</span>, <span style="color:#ae81ff">0x86</span>, <span style="color:#ae81ff">0xFB</span>, <span style="color:#ae81ff">0xF8</span>, <span style="color:#ae81ff">0x1F</span>, <span style="color:#ae81ff">0x6D</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x42</span>, <span style="color:#ae81ff">0x4A</span>, <span style="color:#ae81ff">0xA2</span>, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0xC9</span>, <span style="color:#ae81ff">0xA3</span>, <span style="color:#ae81ff">0x14</span>, <span style="color:#ae81ff">0x9F</span>, <span style="color:#ae81ff">0xB1</span>, <span style="color:#ae81ff">0x75</span>,
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>username <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;A&#34;</span>;
</span></span><span style="display:flex;"><span>client_seed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xDEADBEEF</span>;
</span></span><span style="display:flex;"><span>server_seed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xDEADBEEF</span>;
</span></span></code></pre></div><ul>
<li><a href="verification_values/encryption/vanilla_server.txt">vanilla_server</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Data (8 bytes)</th>
<th>Size (2 bytes <strong>big endian</strong>)</th>
<th>Opcode (2 bytes <strong>little endian</strong>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned Integer</td>
<td>Unsigned</td>
</tr>
</tbody>
</table>
<ul>
<li><a href="verification_values/encryption/vanilla_client.txt">vanilla_client</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Data (8 bytes)</th>
<th>Size (2 bytes <strong>big endian</strong>)</th>
<th>Opcode (4 bytes <strong>little endian</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned Integer</td>
<td>Unsigned</td>
</tr>
</tbody>
</table>
<h3 id="decryption">Decryption</h3>
<p>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.</p>
<p>The decryption is calculated as <code>x = (E - L) ^ S</code> where:</p>
<ul>
<li>
<p><code>x</code> is the unencrypted value, a byte of the opcode or size.</p>
</li>
<li>
<p><code>E</code> is the encrypted value, what is sent over the wire.</p>
</li>
<li>
<p><code>L</code> is the last encrypted value. This is <code>E</code> from the previous iteration.</p>
</li>
<li>
<p><code>S</code> is a byte of the session key, incrementing after every encryption.</p>
</li>
</ul>
<p>In pseudo code this would look like:</p>
<pre tabindex="0"><code>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
</code></pre><h4 id="implementations-15">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/bc10da51a44b368b64fe5491dc305287bb431e74/src/header_crypto/mod.rs#L166">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/HeaderImplementation.cs#L34">C# (WowSrp</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/90b9a7b43df4744f856e81e3efcf1de92bcb4ebf/src/gateway/PacketCrypto.h#L58">C++ (Ember)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/serverd/lib/session.ex#L73">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L191">Python (wow_srp6)</a></li>
</ul>
<h4 id="verification-values-15">Verification values</h4>
<ul>
<li><a href="verification_values/encryption/calculate_decrypt_values.txt">calculate_decrypt_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Session Key</th>
<th>Data (50 bytes)</th>
<th>Expected (50 bytes)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="tbc-world-message-header-encryption">TBC World Message Header Encryption</h2>
<p>Has the same implementation for encryption/decryption as Vanilla, but it performs an HMAC-SHA1 on the <strong>session key</strong> along with a <strong>seed</strong> and iterates over that instead.
Verification values are provided for</p>
<p>Remember that the key for TBC is only 20 bytes, while it&rsquo;s 40 for Vanilla so you will probably need different functions for each.</p>
<h3 id="key-generation">Key Generation</h3>
<ul>
<li><code>HMAC-SHA1</code> is the <a href="https://en.wikipedia.org/wiki/HMAC">HMAC-SHA1 function</a>.</li>
</ul>
<p>In pseudo code the generation looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function create_tbc_key:
</span></span><span style="display:flex;"><span>    argument session_key: array of 40 bytes
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    S = [0x38, 0xA7, 0x83, 0x15, 0xF8, 0x92, 0x25, 0x30, 0x71, 0x98, 0x67, 0xB1, 0x8C, 0x4, 0xE2,
</span></span><span style="display:flex;"><span>        0xAA]
</span></span><span style="display:flex;"><span>    hmac = HMAC-SHA1(S)
</span></span><span style="display:flex;"><span>    hmac.update(session_key)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    return hmac.finalize()
</span></span></code></pre></div><h4 id="implementations-16">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/35c69c542fba64d7fdfdaeb6ad16990fd79312de/src/tbc_header/mod.rs#L316">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/HeaderImplementation.cs#L29">C# (WowSrp)</a></li>
</ul>
<h4 id="verification-values-16">Verification values</h4>
<ul>
<li><a href="verification_values/encryption/create_tbc_key.txt">create_tbc_key.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Session Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="encryptiondecryption">Encryption/Decryption</h3>
<p>This is identical to the Vanilla version except the key is only 20 bytes instead of 40 bytes.</p>
<p>Encrypt this and verify that it matches <code>Expected Encrypt</code>, then decrypt it and verify that it matches the original <code>Data</code>.</p>
<h4 id="verification-values-17">Verification values</h4>
<ul>
<li><a href="verification_values/encryption/calculate_tbc_encrypt_values.txt">calculate_tbc_encrypt_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Session Key</th>
<th>Data</th>
<th>Expected Encrypt</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<p><a href="https://gtker.com/wow_messages/ir/implementing_world.html#message-layout">World Packet</a> headers contain a 2 byte <strong>big endian</strong> size field and either a 4 (SMSG) or 6 (CMSG) byte opcode field.
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.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>session_key <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2E</span>, <span style="color:#ae81ff">0xFE</span>, <span style="color:#ae81ff">0xE7</span>, <span style="color:#ae81ff">0xB0</span>, <span style="color:#ae81ff">0xC1</span>, <span style="color:#ae81ff">0x77</span>, <span style="color:#ae81ff">0xEB</span>, <span style="color:#ae81ff">0xBD</span>, <span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0x66</span>, <span style="color:#ae81ff">0x76</span>, <span style="color:#ae81ff">0xC5</span>, <span style="color:#ae81ff">0x6E</span>, <span style="color:#ae81ff">0xFC</span>, <span style="color:#ae81ff">0x23</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x39</span>, <span style="color:#ae81ff">0xBE</span>, <span style="color:#ae81ff">0x9C</span>, <span style="color:#ae81ff">0xAD</span>, <span style="color:#ae81ff">0x14</span>, <span style="color:#ae81ff">0xBF</span>, <span style="color:#ae81ff">0x8B</span>, <span style="color:#ae81ff">0x54</span>, <span style="color:#ae81ff">0xBB</span>, <span style="color:#ae81ff">0x5A</span>, <span style="color:#ae81ff">0x86</span>, <span style="color:#ae81ff">0xFB</span>, <span style="color:#ae81ff">0xF8</span>, <span style="color:#ae81ff">0x1F</span>, <span style="color:#ae81ff">0x6D</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x42</span>, <span style="color:#ae81ff">0x4A</span>, <span style="color:#ae81ff">0xA2</span>, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0xC9</span>, <span style="color:#ae81ff">0xA3</span>, <span style="color:#ae81ff">0x14</span>, <span style="color:#ae81ff">0x9F</span>, <span style="color:#ae81ff">0xB1</span>, <span style="color:#ae81ff">0x75</span>,
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>username <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;A&#34;</span>;
</span></span><span style="display:flex;"><span>client_seed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xDEADBEEF</span>;
</span></span><span style="display:flex;"><span>server_seed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xDEADBEEF</span>;
</span></span></code></pre></div><ul>
<li><a href="verification_values/encryption/tbc_server.txt">tbc_server</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Data (8 bytes)</th>
<th>Size (2 bytes <strong>big endian</strong>)</th>
<th>Opcode (2 bytes <strong>little endian</strong>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned Integer</td>
<td>Unsigned</td>
</tr>
</tbody>
</table>
<ul>
<li><a href="verification_values/encryption/tbc_client.txt">tbc_client</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Data (8 bytes)</th>
<th>Size (2 bytes <strong>big endian</strong>)</th>
<th>Opcode (4 bytes <strong>little endian</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned Integer</td>
<td>Unsigned</td>
</tr>
</tbody>
</table>
<h2 id="wrath-world-message-header-encryption">Wrath World Message Header Encryption</h2>
<p>Wrath creates an intermediate key by running a fixed key and the <strong>session key</strong> through HMAC-SHA1, and then uses this to seed an ARC4 cipher that encrypts the header.</p>
<p>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.</p>
<p>The seed keys are:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#75715e">// Used for Client (Encryption) to Server (Decryption)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> S: [<span style="color:#66d9ef">u8</span>; <span style="color:#ae81ff">16</span>] <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xC2</span>, <span style="color:#ae81ff">0xB3</span>, <span style="color:#ae81ff">0x72</span>, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0xC6</span>, <span style="color:#ae81ff">0xAE</span>, <span style="color:#ae81ff">0xD9</span>, <span style="color:#ae81ff">0xB5</span>, <span style="color:#ae81ff">0x34</span>, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0x53</span>, <span style="color:#ae81ff">0xEE</span>, <span style="color:#ae81ff">0x2F</span>, <span style="color:#ae81ff">0x43</span>, <span style="color:#ae81ff">0x67</span>, <span style="color:#ae81ff">0xCE</span>,
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Used for Server (Encryption) to Client (Decryption) messages
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> R: [<span style="color:#66d9ef">u8</span>; <span style="color:#ae81ff">16</span>] <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xCC</span>, <span style="color:#ae81ff">0x98</span>, <span style="color:#ae81ff">0xAE</span>, <span style="color:#ae81ff">0x04</span>, <span style="color:#ae81ff">0xE8</span>, <span style="color:#ae81ff">0x97</span>, <span style="color:#ae81ff">0xEA</span>, <span style="color:#ae81ff">0xCA</span>, <span style="color:#ae81ff">0x12</span>, <span style="color:#ae81ff">0xDD</span>, <span style="color:#ae81ff">0xC0</span>, <span style="color:#ae81ff">0x93</span>, <span style="color:#ae81ff">0x42</span>, <span style="color:#ae81ff">0x91</span>, <span style="color:#ae81ff">0x53</span>, <span style="color:#ae81ff">0x57</span>,
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><h3 id="key-generation-1">Key Generation</h3>
<ul>
<li><code>HMAC-SHA1</code> is the <a href="https://en.wikipedia.org/wiki/HMAC">HMAC-SHA1 function</a>.</li>
</ul>
<p>In pseudo code the generation looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function create_wrath_key:
</span></span><span style="display:flex;"><span>    argument session_key: array of 40 bytes
</span></span><span style="display:flex;"><span>    argument key: array of 16 bytes
</span></span><span style="display:flex;"><span>    returns array of 20 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    hmac = HMAC-SHA1(key)
</span></span><span style="display:flex;"><span>    hmac.update(session_key)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    return hmac.finalize()
</span></span></code></pre></div><h4 id="implementations-17">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/e13ad2a397f385ee3236781a9838150652714f5d/src/wrath_header/inner_crypto/mod.rs#L35">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/HeaderImplementation.cs#L31">C# (WowSrp)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/d1ff15d12198aa172ba15520fb8347485bb4c77f/header/wrath.go#L151">Go (go-wow-srp6)</a></li>
</ul>
<h4 id="verification-values-18">Verification values</h4>
<ul>
<li><a href="verification_values/encryption/create_wrath_hmac_key.txt">create_wrath_hmac_key.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Session Key</th>
<th>Key</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h3 id="encryptiondecryption-1">Encryption/Decryption</h3>
<p>Initialize a <a href="https://en.wikipedia.org/wiki/RC4">RC4A</a> with the previously calculated key.
Then apply the keystream to 1024 bytes (since this is the drop1024 variant).</p>
<p>After this apply the keystream to the header to be encrypted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>function create_crypto:
</span></span><span style="display:flex;"><span>    argument session_key: array of 40 bytes
</span></span><span style="display:flex;"><span>    argument key: array of 16 bytes
</span></span><span style="display:flex;"><span>    returns RC4A in progress
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    S = create_wrath_key(session_key, key)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    rc4 = RC4(S)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    rc4.apply_keystream to 1024 0 bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    retrurn rc4
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>function encrypt/decrypt:
</span></span><span style="display:flex;"><span>    argument rc4: RC4A in progress 
</span></span><span style="display:flex;"><span>    argument data: array of bytes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    # Modifies data in place
</span></span><span style="display:flex;"><span>    rc4.apply(data)
</span></span></code></pre></div><h4 id="implementations-18">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/e13ad2a397f385ee3236781a9838150652714f5d/src/wrath_header/inner_crypto/mod.rs#L20">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Header/WrathServerDecryption.cs#L18">C# (WowSrp)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/d1ff15d12198aa172ba15520fb8347485bb4c77f/header/wrath.go#L138">Go (go-wow-srp6)</a></li>
</ul>
<h4 id="verification-values-19">Verification values</h4>
<p>These verification values use the above keys. Ensure that you&rsquo;re using the correct one for the correct direction and side.</p>
<ul>
<li><a href="verification_values/encryption/calculate_wrath_encrypt_values.txt">calculate_wrath_encrypt_values.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Session Key</th>
<th>Data</th>
<th>Expected Client Encrypt</th>
<th>Expected Server Encrypt</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td>Big Endian Hex</td>
<td></td>
</tr>
</tbody>
</table>
<p><a href="https://gtker.com/wow_messages/ir/implementing_world.html#message-layout">World Message</a> headers contain a 2 byte <strong>big endian</strong> size field and either a 4/5 (SMSG) or 6 (CMSG) byte opcode field.
If, after decrypting, the top level bit is set for SMSG (that is <code>(top_byte &amp; 0x80) != 0</code>), the size field is 3 bytes instead of 2.
This must also be set when sending messages larger than 0x7fff.
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.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>session_key <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2E</span>, <span style="color:#ae81ff">0xFE</span>, <span style="color:#ae81ff">0xE7</span>, <span style="color:#ae81ff">0xB0</span>, <span style="color:#ae81ff">0xC1</span>, <span style="color:#ae81ff">0x77</span>, <span style="color:#ae81ff">0xEB</span>, <span style="color:#ae81ff">0xBD</span>, <span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0x66</span>, <span style="color:#ae81ff">0x76</span>, <span style="color:#ae81ff">0xC5</span>, <span style="color:#ae81ff">0x6E</span>, <span style="color:#ae81ff">0xFC</span>, <span style="color:#ae81ff">0x23</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x39</span>, <span style="color:#ae81ff">0xBE</span>, <span style="color:#ae81ff">0x9C</span>, <span style="color:#ae81ff">0xAD</span>, <span style="color:#ae81ff">0x14</span>, <span style="color:#ae81ff">0xBF</span>, <span style="color:#ae81ff">0x8B</span>, <span style="color:#ae81ff">0x54</span>, <span style="color:#ae81ff">0xBB</span>, <span style="color:#ae81ff">0x5A</span>, <span style="color:#ae81ff">0x86</span>, <span style="color:#ae81ff">0xFB</span>, <span style="color:#ae81ff">0xF8</span>, <span style="color:#ae81ff">0x1F</span>, <span style="color:#ae81ff">0x6D</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x42</span>, <span style="color:#ae81ff">0x4A</span>, <span style="color:#ae81ff">0xA2</span>, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0xC9</span>, <span style="color:#ae81ff">0xA3</span>, <span style="color:#ae81ff">0x14</span>, <span style="color:#ae81ff">0x9F</span>, <span style="color:#ae81ff">0xB1</span>, <span style="color:#ae81ff">0x75</span>,
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>username <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;A&#34;</span>;
</span></span><span style="display:flex;"><span>client_seed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xDEADBEEF</span>;
</span></span><span style="display:flex;"><span>server_seed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xDEADBEEF</span>;
</span></span></code></pre></div><ul>
<li><a href="verification_values/encryption/wrath_server.txt">wrath_server</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Data (8 bytes)</th>
<th>Size (2 or 3 bytes <strong>big endian</strong>)</th>
<th>Opcode (2 bytes <strong>little endian</strong>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned Integer</td>
<td>Unsigned</td>
</tr>
</tbody>
</table>
<ul>
<li><a href="verification_values/encryption/wrath_client.txt">wrath_client</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Data (8 bytes)</th>
<th>Size (2 bytes <strong>big endian</strong>)</th>
<th>Opcode (4 bytes <strong>little endian</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Big Endian Hex</td>
<td>Unsigned Integer</td>
<td>Unsigned</td>
</tr>
</tbody>
</table>
<h2 id="world-server-proof">World Server Proof</h2>
<p>After receiving the <a href="https://gtker.com/wow_messages/docs/cmsg_auth_session.html">
    <code>CMSG_AUTH_SESSION</code>
</a>
<span>
    <sup>
        <a href="https://wowdev.wiki/CMSG_AUTH_SESSION">wiki</a>
    </sup>
</span>
 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.</p>
<p>The world server proof works for all versions from Vanilla through to Wrath.</p>
<p>The proof is calculated as <code>SHA1( username | 0 | client_seed | server_seed | session_key )</code>, where:</p>
<ul>
<li>
<p><code>username</code> is the <strong>username</strong> of the user attempting to connect.</p>
</li>
<li>
<p><code>0</code> is the literal value zero, taking up 4 bytes (32 bits, a standard <code>int</code>).</p>
</li>
<li>
<p><code>client_seed</code> is a random 4 byte value sent by the client.</p>
</li>
<li>
<p><code>server_seed</code> is a random 4 byte value generated by the server.</p>
</li>
<li>
<p><code>session_key</code> is the <strong>session key</strong> from the original authentication.</p>
</li>
</ul>
<p>Calculating the world proof in pseudo code would look like:</p>
<pre tabindex="0"><code>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)
</code></pre><h4 id="implementations-19">Implementations</h4>
<ul>
<li><a href="https://github.com/gtker/wow_srp/blob/761b040d9d7f18dcb760424eeaef15994f321116/src/srp_internal.rs#L260">Rust (wow_srp)</a></li>
<li><a href="https://github.com/gtker/wow_srp_csharp/blob/f5ff42224697c220a7eb5e9c03e3c5d2b0f0e678/WowSrp/src/Internal/Implementation.cs#L251">C# (WowSrp)</a></li>
<li><a href="https://github.com/EmberEmu/Ember/blob/b0dbeb3dc4230a18c7be38c2cedd712ef6e3cfd4/src/gateway/states/Authentication.cpp#L166">C++ (Ember)</a></li>
<li><a href="https://github.com/mangoszero/server/blob/e45aa74e0fa3a9677d90fc012b8161e1e9935742/src/game/Server/WorldSocket.cpp#L784">C++ (Mangos)</a></li>
<li><a href="https://gitlab.com/shadowburn/shadowburn/-/blob/ac905fabf56579b3bda6f16689c74f544da043e2/apps/serverd/lib/session.ex#L127">Elixir (Shadowburn)</a></li>
<li><a href="https://github.com/oiramario/wow_srp6/blob/3bf8e4660a35dbc6a49eb17adae6ec914ac07588/srp6.py#L209">Python (wow_srp6)</a></li>
<li><a href="https://github.com/Kangaroux/go-wow-srp6/blob/7a61e15fd8d75f4ebe8ac91e07eef140219ef8ff/proof.go#L52">Go (go-wow-srp6)</a></li>
</ul>
<h4 id="verification-values-20">Verification values</h4>
<ul>
<li><a href="verification_values/encryption/calculate_world_server_proof.txt">calculate_world_server_proof.txt</a> contains the columns:</li>
</ul>
<table>
<thead>
<tr>
<th>Username</th>
<th>Session Key</th>
<th>Server Seed</th>
<th>Client Seed</th>
<th>Expected</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>Big Endian Hex</td>
<td>Number</td>
<td>Number</td>
<td>Big Endian Hex</td>
</tr>
</tbody>
</table>
<h2 id="notes-on-uppercasing-username-and-password">Notes on uppercasing username and password</h2>
<p>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 <a href="https://docs.rs/wow_srp/0.2.0/wow_srp/normalized_string/index.html#background">wow_srp library documentation</a>.</p>
<h2 id="external-resources">External Resources</h2>
<ul>
<li>The <a href="https://shadowburn-project.org/2018/10/17/logging-in-with-vanilla.html">Shadowburn Project</a> has a guide on authenticating that focuses more on the networking and specific packets.</li>
<li>The <a href="https://wowdev.wiki/Login_Packet">WoWDev Wiki</a> has an overview of packets.</li>
<li><a href="https://www.wireshark.org/download.html">Wireshark</a> at version 3.5 or greater has support for parsing 1.12 packets through the <code>WOW</code> and <code>WOWW</code> protocols. You <strong>will</strong> need this if you&rsquo;re trying to debug a networked implementation.</li>
</ul>
]]></content></entry><entry><title type="html">Running assembly on the BL602 RISC-V microcontroller and directly controlling GPIO</title><link href="https://gtker.com/running-assembly-on-the-bl602-risc-v-microcontroller-and-directly-controlling-gpio/"/><id>https://gtker.com/running-assembly-on-the-bl602-risc-v-microcontroller-and-directly-controlling-gpio/</id><author><name>Gtker</name></author><published>2021-01-14T01:48:56+00:00</published><updated>2021-01-14T01:48:56+00:00</updated><content type="html"><![CDATA[<p>The Boufallo Lab BL602 is a 32 bit RISC-V microcontroller with support for 2.4 GHz WiFi and Bluetooth Low Energy 5.0.
It has an <a href="https://github.com/bouffalolab/bl_iot_sdk">official SDK</a>, however even the “Hello World example” includes a large amount of boilerplate.
This article will show how to manipulate the GPIO pins using assembly and no dependencies.</p>
<h2 id="git-repository">Git repository</h2>
<p>The full assembly and a makefile can be found <a href="https://github.com/gtker/bl602-assembly">in this Github repository</a>.</p>
<h2 id="background">Background</h2>
<p>As seen on the <a href="https://github.com/pine64/bl602-docs/blob/4c72881b6b4e1aa2ff5cdc5991de46e07fe39160/mirrored/Pine64%20BL602%20EVB%20Schematic%20ver%201.1.pdf">Pine64 BL602 EVB ver 1.1 schematics</a> <code>LED1</code> is three separate diodes controlled through <code>GPIO17</code> (Red), <code>GPIO14</code> (Green) and <code>GPIO11</code> (Blue).
<code>LED1</code> is not to be confused with <code>LED2</code> which just lights up red when the board is plugged in.</p>
<p><a href="https://github.com/pine64/bl602-docs/tree/42b5581e37b8c21ed6320c43392c6c93e0803ab3">Page 18 of the BL602 reference manual</a> shows that the XIP (eXecute In Place) memory starts at address <code>0x23000000</code>.
<code>blflash</code> <a href="https://github.com/spacemeowx2/blflash/releases/tag/v0.3.0">version 0.3.0</a> writes binary files to address <code>0x23000000</code> by default so no linker scripts are necessary.</p>
<h2 id="gpio-on-the-bl602">GPIO on the BL602</h2>
<p>GPIO on the BL602 can be controlled through several pre defined registers.
<a href="https://github.com/pine64/bl602-docs/tree/42b5581e37b8c21ed6320c43392c6c93e0803ab3">Page 24 to 29 of the BL602 reference manual</a> describes the general approach.</p>
<p>In order to initialize and control the GPIO pins the following must be done:</p>
<ol>
<li>The GPIO pin function and resistor type must be set. The exact register depends on the GPIO pin.</li>
<li>The relevant bit in the GPIO Output Enable register (<code>GPIO_CFGCTL34_OFFSET</code>) must be set.</li>
<li>The relevant bit in the GPIO Output register (<code>GPIO_CFGCTL32_OFFSET</code>) must be set.</li>
</ol>
<p>The locations are described in the <a href="https://github.com/pine64/bl602-docs/tree/42b5581e37b8c21ed6320c43392c6c93e0803ab3/hardware_notes/registers/glb">unofficial hardware notes</a>.</p>
<h2 id="exact-plan-for-gpio11">Exact plan for <code>GPIO11</code></h2>
<p>The Global Register (GLB) is located at address <code>0x40000000</code>.</p>
<p><a href="https://github.com/pine64/bl602-docs/tree/42b5581e37b8c21ed6320c43392c6c93e0803ab3">Page 28 and 29 of the BL602 reference manual</a> list <code>GPIO_CFGCTL5</code> as the register for configuring <code>GPIO10</code> and <code>GPIO11</code>.
The register overview on page 36 describes the 16 most significant bits as being used for <code>GPIO11</code> and the 16 least significant bits as being used for <code>GPIO10</code>.
The two parts of the register have the same layout, <code>GPIO11</code> is just bitshifted left 16:</p>
<table>
<thead>
<tr>
<th>15</th>
<th>14</th>
<th>13</th>
<th>12</th>
<th>11</th>
<th>10</th>
<th>9</th>
<th>8</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2</th>
<th>1</th>
<th>0</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td>F</td>
<td>F</td>
<td>F</td>
<td>F</td>
<td></td>
<td></td>
<td>PD</td>
<td>PU</td>
<td>DRV</td>
<td>DRV</td>
<td>SMT</td>
<td>IE</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>Where reserved bits are blank and following symbols are used:</p>
<table>
<thead>
<tr>
<th>Symbol</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>F</td>
<td>Function select</td>
</tr>
<tr>
<td>PD</td>
<td>Pull-Down Resistor</td>
</tr>
<tr>
<td>PU</td>
<td>Pull-Up Resistor</td>
</tr>
<tr>
<td>DRV</td>
<td>Driving control</td>
</tr>
<tr>
<td>SMT</td>
<td>Schmitt trigger</td>
</tr>
<tr>
<td>IE</td>
<td>Input enabled</td>
</tr>
</tbody>
</table>
<p>The function select should be set to 11 (0b1011) in order to choose Software GPIO, and the pull up resistor should be enabled.
This means we will have to write the value <code>0b0000101100100000 &lt;&lt; 16</code> to the <code>GPIO_CFGCTL5</code> register at address <code>0x40000144</code>.
The LEDs require sinking current into the GPIO pins, so it should be off until you explicitly pull it <code>LOW</code>.
If the pulldown resistor was enabled the LED would be on by default.</p>
<p>Next, the 11th bit of the Output Enable register (<code>GPIO_CFGCTL34_OFFSET</code>) at address <code>0x40000190</code> must be set.
Bit shifting <code>1 &lt;&lt; 11</code> will work.</p>
<p>Finally the 11th bit of the Output register (<code>GPIO_CFGCTL32_OFFSET</code>) at address <code>0x40000188</code> must be set <code>LOW</code>.</p>
<h2 id="c-and-assembly-examples">C and assembly examples</h2>
<p>The C code is provided for reference only and has not been tested.
In C we would do:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span><span style="color:#f92672">*</span> GPIO_CFGCTL5 <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x40000114</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> GPIO_BITSET_CFGCTL5 <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>b0000101100100000 <span style="color:#f92672">&lt;&lt;</span> <span style="color:#ae81ff">16</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">*</span>GPIO_CFGCTL5 <span style="color:#f92672">=</span> GPIO_BITSET_CFGCTL5;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span><span style="color:#f92672">*</span> GPIO_CFGCTL32 <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x40000188</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> GPIO_BITSET_OUTPUT_ENABLE <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#ae81ff">11</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">*</span>GPIO_CFGCTL32 <span style="color:#f92672">=</span> GPIO_BITSET_OUTPUT_ENABLE;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span><span style="color:#f92672">*</span> GPIO_CFGCTL34 <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x40000190</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> GPIO_BITSET_OUTPUT <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#ae81ff">11</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> (<span style="color:#ae81ff">1</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Trap execution to prevent weird results.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Continuously set GPIO status to ensure it isn&#39;t
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#75715e">// quickly toggled off for some reason.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#f92672">*</span>GPIO_CFGCTL34 <span style="color:#f92672">=</span> GPIO_BITSET_OUTPUT;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In assembly we do almost the same, except we use the GLB as a reference point and compute relative addresses using the <code>sw</code> instruction.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#75715e"># RM = BL602 Reference Manual 1.2. See the Pine64 bl602-docs repo.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Global Register base address. Page 23 of the RM.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GLB_BASE</span>, <span style="color:#ae81ff">0x40000000</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># GPIO 10 and 11 configuration. Page 36 of the RM.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GPIO_CFGCTL5_OFFSET</span>, <span style="color:#ae81ff">0x114</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># GPIO Output Register. Not described in RM.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GPIO_CFGCTL32_OFFSET</span>, <span style="color:#ae81ff">0x188</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># GPIO Output Enable register. Not described in RM.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GPIO_CFGCTL34_OFFSET</span>, <span style="color:#ae81ff">0x190</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Bitset to set GP11FUNC to 11 (SWGPIO), Pullup to 1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># and rest to zero. Page 36 of the RM.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># GPIO11 are the 16 highest bits.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GPIO_BITSET_CFGCTL5</span>, <span style="color:#ae81ff">0</span><span style="color:#66d9ef">b0000101100010000</span> <span style="color:#960050;background-color:#1e0010">&lt;&lt;</span> <span style="color:#ae81ff">16</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Bitset for Output and Output Enable register. 11th bit = GPIO11.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># High enables the output.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GPIO_BITSET_OUTPUT_ENABLE</span>, <span style="color:#ae81ff">1</span> <span style="color:#960050;background-color:#1e0010">&lt;&lt;</span> <span style="color:#ae81ff">11</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Low enables the LED because the GPIO sinks current.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">.set</span> <span style="color:#66d9ef">GPIO_BITSET_OUTPUT</span>, <span style="color:#ae81ff">0</span> <span style="color:#960050;background-color:#1e0010">&lt;&lt;</span> <span style="color:#ae81ff">11</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">.section</span> <span style="color:#66d9ef">.text</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>main:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># la = Load Address
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># Can be used to compute an offset from address, 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># but not used here.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># Load GLB_BASE pointer into &#39;temporary1&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># int* t1 = (int*) GLB_BASE;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">la</span> <span style="color:#66d9ef">t1</span>, <span style="color:#66d9ef">GLB_BASE</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Load bitset for config control 5 into &#39;temporary2&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># int t2 = GPIO_BITSET_CFGCTL5;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">la</span> <span style="color:#66d9ef">t2</span>, <span style="color:#66d9ef">GPIO_BITSET_CFGCTL5</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># sw = Store Word (32 bits)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># Store &#39;temporary2&#39; in GLB_BASE pointer, plus the
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># GPIO10/GPIO11 register offset.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># *(t1 + GPIO_CFGCTL5_OFFSET) = t2;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">sw</span> <span style="color:#66d9ef">t2</span>, <span style="color:#66d9ef">GPIO_CFGCTL5_OFFSET</span>(<span style="color:#66d9ef">t1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Load bitset for enabling GPIO11 into &#39;temporary2&#39;.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># t2 = GPIO_BITSET_OUTPUT_ENABLE; 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">la</span> <span style="color:#66d9ef">t2</span>, <span style="color:#66d9ef">GPIO_BITSET_OUTPUT_ENABLE</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Store &#39;temporary2&#39; in GLB_BASE pointer, plus the
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># Output Enable register offset.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># *(t1 + GPIO_CFGCTL34_OFFSET) = t2;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">sw</span> <span style="color:#66d9ef">t2</span>, <span style="color:#66d9ef">GPIO_CFGCTL34_OFFSET</span>(<span style="color:#66d9ef">t1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># t2 = GPIO_BITSET_OUTPUT;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># The bitset for output enable should not be used for
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># output.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">la</span> <span style="color:#66d9ef">t2</span>, <span style="color:#66d9ef">GPIO_BITSET_OUTPUT</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Loop label.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>loop:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Store &#39;temporary2&#39; in GLB_BASE pointer, plus the
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># Out register offset.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># This is the bit you would switch in order to blink
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># LED.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># *(t1 + GPIO_CFGCTL32_OFFSET) = t2;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">sw</span> <span style="color:#66d9ef">t2</span>, <span style="color:#66d9ef">GPIO_CFGCTL32_OFFSET</span>(<span style="color:#66d9ef">t1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Unconditionally Jump to loop.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e"># goto loop;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">j</span> <span style="color:#66d9ef">loop</span>
</span></span></code></pre></div><h2 id="building-linking-and-flashing">Building, linking and flashing</h2>
<p>The general approach is to:</p>
<ol>
<li>build the assembly with <code>as</code>,</li>
<li>link it with <code>ld</code>,</li>
<li>convert the ELF file to binary with <code>objcopy</code>,</li>
<li>flash it to the board with <code>blflash</code>.</li>
</ol>
<p>The <a href="https://github.com/pine64/bl_iot_sdk">Pine64</a> version of the repository include the necessary <code>riscv64-unknown-elf</code> versions of every tool mentioned above (in <code>toolchain/riscv/Linux/bin</code>), except for <code>blflash</code> which can be found <a href="https://github.com/spacemeowx2/blflash">at the Github repository</a>.</p>
<p>Exact commands can be found in the <code>Makefile</code> at the <a href="https://github.com/gtker/bl602-assembly">git repository</a>.</p>
]]></content></entry><entry><title type="html">Regulating Unwanted Smart Device Traffic Using PiHole and Edgerouter X</title><link href="https://gtker.com/regulating-unwanted-smart-device-traffic-using-pihole-and-edgerouter-x/"/><id>https://gtker.com/regulating-unwanted-smart-device-traffic-using-pihole-and-edgerouter-x/</id><author><name>Gtker</name></author><published>2020-12-06T21:30:44+00:00</published><updated>2020-12-06T21:30:44+00:00</updated><content type="html"><![CDATA[<p>98% of smart assistants and 72% of smart TVs use hard coded DNS servers. <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
The inclusion of ads on smart TVs even by well known brands like <a href="https://www.rtings.com/tv/tests/ads-in-smart-tv">Samsung, Sony and LG</a> (<a href="https://web.archive.org/web/20201112014902/https://www.rtings.com/tv/tests/ads-in-smart-tv">archive</a>) significantly increases the need for forced DNS adblocking of devices that deliberately ignore DHCP provided DNS.
A Pihole DNS by itself can prevent ads from devices that are well behaved, but misbehaving devices will have to be forced by either a firewall or router.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>It is assumed that a working Pihole newer than version 5 is set up at <code>192.168.1.11</code> with an accessible admin interface at <code>http://192.168.1.11</code> and that a working Edgerouter X is set up at <code>192.168.1.1</code> with an accessible admin interface at <code>https://192.168.1.1</code>.
It might be necessary to downgrade the Edgerouter X connection to HTTP depending on settings.</p>
<p>The device to be regulated is assumed to be located at <code>192.168.1.60</code>, and it will be referred to as &ldquo;the TV&rdquo; from now on.
It is assumed that the TV has a reason for being on the network, since the most effective way of preventing something from communicating through a network is by not connecting it to the network.
The tutorial below assumes that a single service must be used through a smart TV, and that everything else must be blocked.</p>
<p>Be aware that <code>192.168.**1**.0</code> and <code>192.168.**0**.0</code> are two popular address spaces for home networks that look alike at a glance.
Make sure that you&rsquo;re using the correct network for you, despite what the examples say.</p>
<h2 id="dns-blocking-on-pihole">DNS blocking on Pihole</h2>
<p>The images are from version 5.1.1 of the Pihole Web Interface.</p>
<p>Log in to the Pihole Web Interface at <code>http://192.168.1.11/admin</code>.</p>
<p>Go to &ldquo;Groups&rdquo; under &ldquo;Group Management&rdquo; in the left menu as seen in figure 1.
<figure>
    
    <img src="pihole/navigation-menu.png" alt="Navigation Menu on the left side of the Pihole Web Interface."></img>
    
    <figcaption><b>Figure 1:</b> Navigation Menu on the left side of the Pihole Web Interface.
        <br>
        
</figure>
</p>
<p>Add a new group called &ldquo;TV&rdquo; and put any comments in the &ldquo;Description&rdquo; field as seen in figure 2.</p>
<figure>
    
    <img src="pihole/new-group.png" alt="Adding a new group named &#39;TV&#39;. Only &#39;Name&#39; is relevant, &#39;Description&#39; is only documentation."></img>
    
    <figcaption><b>Figure 2:</b> Adding a new group named &#39;TV&#39;. Only &#39;Name&#39; is relevant, &#39;Description&#39; is only documentation.
        <br>
        
</figure>

<p>Go to &ldquo;Clients&rdquo; through the same menu as figure 1.</p>
<p>Add the address of the TV as seen in figure 3 and include a short description.
It is recommended to identify the address in some way, even if it&rsquo;s just &ldquo;TV&rdquo;, since few people can correctly remember the IP-addresses of every device on their LAN.
If the address of the TV is not found in the dropdown you can manually insert it using the &ldquo;Custom, specified below&rdquo; option.</p>
<figure>
    
    <img src="pihole/add-new-client.png" alt="Adding a new client by using the &#39;Custom, specified below...&#39; option and manually inputting the address."></img>
    
    <figcaption><b>Figure 3:</b> Adding a new client by using the &#39;Custom, specified below...&#39; option and manually inputting the address.
        <br>
        
</figure>

<p>Assign the new client to the group made in figure 2 and remove it from the &ldquo;Default&rdquo; group as seen in figure 4.</p>
<figure>
    
    <img src="pihole/client-to-group.png" alt="Adding a new client to the &#39;TV&#39; group and removing it from the &#39;Default&#39; group."></img>
    
    <figcaption><b>Figure 4:</b> Adding a new client to the &#39;TV&#39; group and removing it from the &#39;Default&#39; group.
        <br>
        
</figure>

<p>Next, go to &ldquo;Domains&rdquo; through the same menu as figure 1.</p>
<p>Add <code>[a-z]*|[0-9]*|\.</code> as a &ldquo;Domain&rdquo;, and <code>Block all domains.</code> as the &ldquo;Comment&rdquo; as seen in figure 5, and click &ldquo;Add to Blacklist&rdquo; just below.
Remember to check the &ldquo;Add domain as wildcard.
This will block all domains.</p>
<figure>
    
    <img src="pihole/block-all-domains.png" alt="Adding the regular expression &#39;[a-z]*|[0-9]*|\.&#39; to &#39;Domain&#39; in order to block all domains. Make sure to check &#39;Add domain as wildcard&#39;. The &#39;Add to Blacklist&#39; button is found just below this."></img>
    
    <figcaption><b>Figure 5:</b> Adding the regular expression &#39;[a-z]*|[0-9]*|\.&#39; to &#39;Domain&#39; in order to block all domains. Make sure to check &#39;Add domain as wildcard&#39;. The &#39;Add to Blacklist&#39; button is found just below this.
        <br>
        
</figure>

<p>Scroll down until you find the filter you added in figure 5.</p>
<p>Ensure that the <code>[a-z]*|[0-9]*|\.</code> filter you just added is a &ldquo;Regex blacklist&rdquo;, &ldquo;Enabled&rdquo; and applied only to the group you created in figure 2.
It <strong>must</strong> not be applied to the &ldquo;Default&rdquo; group, otherwise all domains will be blocked on all devices.</p>
<figure>
    
    <img src="pihole/apply-all-to-tv.png" alt="List of blocklists on the Pihole. Ensure that the recently added filter is listed as &#39;Regex  blacklist&#39;, &#39;Enabled&#39; and in the &#39;TV&#39; group but not in the &#39;Default&#39; group."></img>
    
    <figcaption><b>Figure 6:</b> List of blocklists on the Pihole. Ensure that the recently added filter is listed as &#39;Regex  blacklist&#39;, &#39;Enabled&#39; and in the &#39;TV&#39; group but not in the &#39;Default&#39; group.
        <br>
        
</figure>

<p>All DNS queries are now blocked on the TV, and you must manually approve individual services that you require.
To find out whether you need to set up your Edgerouter X for DNS redirection you should turn on your TV and check if network features are completely broken.
If they are, your TV respects the DHCP provided DNS, and if they are not you either have not set your Pihole as the DNS in your DHCP settings or your TV does not respect DHCP settings.</p>
<p>You will now need to approve the necessary domains for your service to work.
After trying to access the required service on your TV, go into the &ldquo;Query Log&rdquo; under &ldquo;Long-term data&rdquo; in the left menu.
Select the date and time range &ldquo;Today&rdquo;.
Scroll down and narrow the search to your TV by entering <code>192.168.1.60</code> in the &ldquo;Search&rdquo; field like in figure 7.
The exact domain depends on the service you&rsquo;re trying to allow.
If you&rsquo;re in doubt, do a search for the domain in question and read up on what it does.
It might be necessary to do this over several iterations, as services on the TV might require different domains at several points.</p>
<figure>
    
    <img src="pihole/approve-domains.png" alt="Approving domains for the TV. The search has been used to narrow the list to only the TV. Domains can be allowed with the &#39;Whitelist&#39; button on the right. A records are IPv4 and AAAA records are IPv6."></img>
    
    <figcaption><b>Figure 7:</b> Approving domains for the TV. The search has been used to narrow the list to only the TV. Domains can be allowed with the &#39;Whitelist&#39; button on the right. A records are IPv4 and AAAA records are IPv6.
        <br>
        
</figure>

<h2 id="dns-redirection-on-edgerouter-x">DNS Redirection on Edgerouter X</h2>
<p>If your TV does not use the DHCP DNS by default, you should set up DNS redirection on a firewall or router.</p>
<p>Log into the web interface of your Edgerouter X and click the &ldquo;Firewall/NAT&rdquo; tab as shown in figure 8.</p>
<figure>
    
    <img src="edgerouterx/firewall.png" alt="Firewall/NAT tab on the Edgerouter X."></img>
    
    <figcaption><b>Figure 8:</b> Firewall/NAT tab on the Edgerouter X.
        <br>
        
</figure>

<p>Next, click the &ldquo;NAT&rdquo; tab as shown in figure 9.</p>
<figure>
    
    <img src="edgerouterx/nat.png" alt="NAT tab in the Firewall/NAT tab."></img>
    
    <figcaption><b>Figure 9:</b> NAT tab in the Firewall/NAT tab.
        <br>
        
</figure>

<p>Click the &ldquo;Add Source NAT Rule&rdquo; button as shown in figure 10.</p>
<figure>
    
    <img src="edgerouterx/add-source-nat.png" alt="Adding new source rule."></img>
    
    <figcaption><b>Figure 10:</b> Adding new source rule.
        <br>
        
</figure>

<p>Set up the rule as shown in figure 11, paying special attention to the below fields:</p>
<ol>
<li><strong>Description:</strong> Name of the rule.</li>
<li><strong>Outbound Interface:</strong> If you used the Basic Wizard to set up the Edgerouter X, you should set this to <code>switch0</code>.</li>
<li><strong>Src Address:</strong> The addresses that this rule should apply to. Since my DHCP range is <code>192.168.1.100</code> to <code>192.168.1.254</code> that&rsquo;s what I&rsquo;m putting in. If you only want to apply to the TV, set it to <code>192.168.1.1.60</code>.</li>
<li><strong>Src Port:</strong> Leave blank.</li>
<li><strong>Dest Address:</strong> Set to the address of your Pihole. Set to <code>192.168.1.11</code>.</li>
<li><strong>Dest Port:</strong> Set to <code>53</code>.</li>
</ol>
<figure>
    
    <img src="edgerouterx/source-nat-settings.png" alt="Screen for creating new source NAT rules."></img>
    
    <figcaption><b>Figure 11:</b> Screen for creating new source NAT rules.
        <br>
        
</figure>

<p>Click save on figure 11 and then &ldquo;Add Destination NAT Rule&rdquo; just below the button in figure 10.</p>
<p>Set up the rule as shown in figure 12, paying special attention to the below fields:</p>
<ol>
<li><strong>Description:</strong> Name of the rule.</li>
<li><strong>Inbound Interface:</strong> Same as in figure 11.</li>
<li><strong>Translations Address:</strong> Set to the address of your Pihole, <code>192.168.1.11</code>.</li>
<li><strong>Translations Port:</strong> Set to <code>53</code>.</li>
<li><strong>Src Address:</strong> Set to addresses you want to redirect DNS for. For everything except your pihole set to <code>!192.168.1.11</code>, noticing the <code>!</code> which means <code>NOT</code>.</li>
<li><strong>Src Port:</strong> Leave blank.</li>
<li><strong>Dest Address:</strong> Set to the same as Src Address.</li>
<li><strong>Dest Port:</strong> Set to 53.</li>
</ol>
<figure>
    
    <img src="edgerouterx/destination-nat-settings.png" alt="Screen for creating new source NAT rules."></img>
    
    <figcaption><b>Figure 12:</b> Screen for creating new source NAT rules.
        <br>
        
</figure>

<h2 id="testing">Testing</h2>
<p>In order to test your solution you should, on Windows or Linux, run <code>nslookup [blocked domain] 8.8.8.8</code> and see that you get a <code>0.0.0.0</code> result back.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>M. H. Mazhar and Z. Shafiq, &ldquo;<a href="https://arxiv.org/pdf/2001.08288.pdf">Characterizing Smart Home IoT Traffic in the Wild</a>&rdquo;, 2020 IEEE/ACM Fifth International Conference on Internet-of-Things Design and Implementation (IoTDI), pp. 2, Mar. 2020.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content></entry><entry><title type="html">Improved way of rendering TeX math in Hugo</title><link href="https://gtker.com/improved-way-of-rendering-tex-math-in-hugo/"/><id>https://gtker.com/improved-way-of-rendering-tex-math-in-hugo/</id><author><name>Gtker</name></author><published>2020-11-29T00:09:00+00:00</published><updated>2020-11-29T00:09:00+00:00</updated><content type="html"><![CDATA[<p><a href="https://latkin.org/blog/about/">Lincoln Atkinson</a> wrote a post about <a href="https://latkin.org/blog/2016/08/07/better-tex-math-typesetting-in-hugo/">TeX math typesetting in Hugo</a> (<a href="https://web.archive.org/web/20201108141222/https://latkin.org/blog/2016/08/07/better-tex-math-typesetting-in-hugo/">archive</a>) where he described a solution that could do TeX math typesetting with and without Javascript enabled.</p>
<p>I have improved slightly upon his method, creating a simple Python script to download the equation images prior to publication and serving them from the local web server.</p>
<h2 id="why">Why?</h2>
<p>The primary reason was that I wanted a missing <code>noscript</code> equation to be an error at compile time instead of just a dead link.
When going to the <a href="https://latex.codecogs.com/">codecogs</a> website at the time of writing, I see the following in a little box:</p>
<pre tabindex="0"><code>Licence Details
Registered use:     CLOUD
Expiration date:    31-12-2020
</code></pre><p>What does that mean?
I have no idea.
It might mean that service transistions to a new backend at the start of 2021, or it might mean that the service completely shuts down.</p>
<h2 id="how">How?</h2>
<p>Lincoln created a shortcode<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> that inserts an image with <code>src=latex.codecogs.com/gif.latex?</code> followed by the equation to render, ending with <code> title=</code> (the <code>&quot;</code> are removed by Hugo).</p>
<p>Knowing this it is possible to create a script that finds all instances of the <code>codecogs.com</code> sources, downloads the equation as a file and replaces the <code>src=</code> attribute with the local file.</p>
<p>The benefits of this approach is that the noscript equations still work before the script is run, it&rsquo;s just fetched from <code>codecogs.com</code> instead of locally.</p>
<h2 id="script">Script</h2>
<p>Running the script in the directory that contains the <code>public</code> folder will download equations, place them next to the <code>.html</code> file and replace the <code>src=</code> attribute.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/user/bin/env python3</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> pathlib <span style="color:#f92672">import</span> Path
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> urllib
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>EXTENSION <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;svg&#34;</span>
</span></span><span style="display:flex;"><span>LATEX_URL <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;https://latex.codecogs.com/</span><span style="color:#e6db74">{</span>EXTENSION<span style="color:#e6db74">}</span><span style="color:#e6db74">.latex?&#34;</span>
</span></span><span style="display:flex;"><span>LATEX_INLINE <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;https://latex.codecogs.com/</span><span style="color:#e6db74">{</span>EXTENSION<span style="color:#e6db74">}</span><span style="color:#e6db74">.latex?\inline&amp;space;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> path <span style="color:#f92672">in</span> Path(<span style="color:#e6db74">&#39;public&#39;</span>)<span style="color:#f92672">.</span>rglob(<span style="color:#e6db74">&#39;*.html&#39;</span>):
</span></span><span style="display:flex;"><span>    contents <span style="color:#f92672">=</span> Path(path)<span style="color:#f92672">.</span>read_text();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> (LATEX_INLINE <span style="color:#f92672">in</span> contents) <span style="color:#f92672">or</span> (LATEX_URL <span style="color:#f92672">in</span> contents):
</span></span><span style="display:flex;"><span>        latex_url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> LATEX_INLINE <span style="color:#f92672">in</span> contents:
</span></span><span style="display:flex;"><span>            latex_url <span style="color:#f92672">=</span> LATEX_INLINE
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> LATEX_URL <span style="color:#f92672">in</span> contents:
</span></span><span style="display:flex;"><span>            latex_url <span style="color:#f92672">=</span> LATEX_URL
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;PATH: &#34;</span> <span style="color:#f92672">+</span> str(path))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        index <span style="color:#f92672">=</span> contents<span style="color:#f92672">.</span>find(latex_url)
</span></span><span style="display:flex;"><span>        end <span style="color:#f92672">=</span> contents<span style="color:#f92672">.</span>find(<span style="color:#e6db74">&#34; title=&#34;</span>, index)
</span></span><span style="display:flex;"><span>        equation <span style="color:#f92672">=</span> contents[index <span style="color:#f92672">+</span> len(latex_url):end]
</span></span><span style="display:flex;"><span>        url <span style="color:#f92672">=</span> contents[index:end]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        f <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url)<span style="color:#f92672">.</span>content
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> open(path<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>joinpath(equation <span style="color:#f92672">+</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;.</span><span style="color:#e6db74">{</span>EXTENSION<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>), <span style="color:#e6db74">&#39;wb&#39;</span>) <span style="color:#66d9ef">as</span> w:
</span></span><span style="display:flex;"><span>            w<span style="color:#f92672">.</span>write(f)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">Replacing </span><span style="color:#e6db74">{</span>url<span style="color:#e6db74">}</span><span style="color:#e6db74"> with </span><span style="color:#e6db74">{</span>urllib<span style="color:#f92672">.</span>parse<span style="color:#f92672">.</span>quote(equation)<span style="color:#e6db74">}</span><span style="color:#e6db74">.</span><span style="color:#e6db74">{</span>EXTENSION<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        contents <span style="color:#f92672">=</span> contents<span style="color:#f92672">.</span>replace(url, urllib<span style="color:#f92672">.</span>parse<span style="color:#f92672">.</span>quote(equation) <span style="color:#f92672">+</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;.</span><span style="color:#e6db74">{</span>EXTENSION<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> open(path, <span style="color:#e6db74">&#39;w&#39;</span>) <span style="color:#66d9ef">as</span> w:
</span></span><span style="display:flex;"><span>        w<span style="color:#f92672">.</span>write(contents)
</span></span></code></pre></div><h2 id="future-work">Future work</h2>
<p>Ideally the equations would be done entirely at compile time without external dependencies.
Currently it does not seem possible to do entirely in HTML, since the math requires some fonts that are not installed by default on most platforms.
A LaTeX to SVG tool could be used, but would break the <code>hugo serve</code> tool since it does not seem possible to insert arbitrary commands in that process.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Think of it like a function for Hugo that inserts text and can take parameters.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content></entry></feed>