Authentication - themeldingwars/Documentation GitHub Wiki

Authentication

For web calls to the backend the Firefall client has setup a custom protocol for authenticating with the server. During Beta some people quickly figured out how the authentication worked (Auth v1) and could thus send requests without needing the client. As a counter measure, Red 5 Studios changed the protocol slightly (Auth v2) and it's since also been figured out.

Auth v2

The authentication works the same way, except that it uses a different secret and re-hashes the payload 200 times. FauFau has an implementation for it: Auth.cs

Auth v1

Original notes from the Something Awful forums around 2010:

Firefall authenticates a message using the session_id and a token as far as I can tell.
The token is passed in with a header called X-Red5-Signature, which has the following contents:
Red5 <token> ver=1&tc=<timesinceepoch>&nonce=<hexnonce>&uid=<base64_httpescaped_uid>&host=<host>&path=<httpescaped_path>&body=<SHA1 hash body hex>&cid=<character id>

There are two primary uses of your email and password.
* The first uses only your email and a given salt, here we have assigned it to var S1:
S1 = SHA1(email + '-red5salt-2239nknn234j290j09rjdj28fh8fnj234k')
* The second uses email and password, assigned to var S2:
S2 = SHA1(email + '-' +  pw + '-red5salt-7nc9bsj4j734ughb8r8dhb8938h8by987c4f7h47b')

S1, when base64 encoded and http escaped, is used as the uid in the token string.
S2, when encoded as hex, is used for a number of things, all referred to by <secret>


The token itself is generated using the following steps:
1. A = SHA1(body of request).hexdigest()
2. Transform_1 = <secret> ^ 0x36, padded to 0x40 bytes with 0x36
3. Generate the request portion of string, just filling out values for the string above minus the Red5 and token.
	Let us assume it is placed in a variable named req_string.
4. B = SHA1(Transform_1 + req_string)
5. Transform_2 = <secret> & 0x5C, padded to 0x40 bytes with 0x5C
6. C = SHA1(Transform_2 + B.digest())
7. C.hexdigest() is our token

tc: Time since the epoch in milliseconds?
nonce: Randomly generated 16 hex digits sequence
uid: http.escape(base64.encode(SHA1(email+salt1)))
host: host of request
path: http.escape(uri)
body: SHA1(body).hex()
cid: character id, recieved from beta.firefallthegame.com/game/character/list.json

Working Python code and demo to generate a Version 1189 Token

import hashlib

def generateToken(secret, req_string):
    # Step 1, Perform the transform on secret (xor each byte with 0x36, pad with 0x36 up to 40 bytes if needed)
    A_transform = u''
    for char in secret:
        A_transform += chr((ord(char) ^ 0x36))
    diff = 0x40 - len(A_transform)
    if diff < 0: diff = 0
    for cnt in range(0, diff):
        A_transform += chr(0x36)

    # Step 2, Hash the transformed secret and request string
    m1 = hashlib.sha1()
    m1.update(A_transform)
    m1.update(req_string)
    C = m1.digest()

    # Step 3, Transform secret again, this time with 0x5C
    B_transform = ''
    for char in secret:
        B_transform += chr((ord(char) ^ 0x5C))
    diff = 0x40 - len(B_transform)
    if diff < 0: diff = 0
    for cnt in range(0, diff):
        B_transform += chr(0x5C)

    # Step 4, Hash transformed secret and the result of earlier hash to get final token
    m2 = hashlib.sha1()
    m2.update(B_transform)
    m2.update(C)
    D = m2.hexdigest()

    final_string = 'Red5 ' + D + ' ' + req_string

    return final_string
def main():
    # Pass our Secret and Request String and try to generate the token
    print generateToken('c9c6eb583bd91d7d923084b2fe57c790a7a30203', 'ver=1&tc=1464664242&nonce=0000295c00007084&uid=d8wP5gy2K%2BrsJYAi8PKf%2BJq4Bv8%3D&host=DUMPTRUCK&path=&hbody=da39a3ee5e6b4b0d3255bfef95601890afd80709')
    # Print out the token goal, if both lines match then it successfully generated the same token and works!
    print 'Red5 669d417a26d056693a63f3c63437be717febb615 ver=1&tc=1464664242&nonce=0000295c00007084&uid=d8wP5gy2K%2BrsJYAi8PKf%2BJq4Bv8%3D&host=DUMPTRUCK&path=&hbody=da39a3ee5e6b4b0d3255bfef95601890afd80709'

if __name__ == "__main__":
    main()

Working Node.js code and demo to generate a Version 1189 Token

var crypto = require('crypto');

// Creation example
var receivedAccountCreationData = JSON.parse(data);
var password_hash = GenerateSecret(bodyData.email.toLowerCase(), receivedAccountCreationData.password);
var email_hash = GenerateUID(receivedAccountCreationData.email.toLowerCase());

// Red5-Signature generation functions
function GenerateUID(email) {
    var hash = crypto.createHash('sha1');

    var S1 = hash.update(email + '-red5salt-2239nknn234j290j09rjdj28fh8fnj234k').digest('base64');
    var UID = encodeURIComponent(S1);
    // Remove last 3 characters of string which is %3D and converts into an equal sign ( = )
    return UID.substring(0, UID.length - 3);
}

function GenerateSecret(email, password) {
    var hash = crypto.createHash('sha1');

    return hash.update(email + '-' + password + '-red5salt-7nc9bsj4j734ughb8r8dhb8938h8by987c4f7h47b').digest('hex');
}

function GenerateToken(secret, req_string)
{
    // Step 1, Perform the transform on secret (xor each byte with 0x36, pad with 0x36 up to 40 bytes if needed)
    var A_Transform = '';
    for (var char in secret) {
        A_Transform += String.fromCharCode(secret[char].charCodeAt() ^ 0x36);
    }
    var diff = 0x40 - A_Transform.length
    if (diff < 0) diff = 0;
    for (i = 0; i < diff; i++) {
        A_Transform += String.fromCharCode(0x36);
    }

    // Step 2, Hash the transformed secret and request string
    var m1 = crypto.createHash('sha1');
    m1.update(A_Transform);
    m1.update(req_string);
    var C = m1.digest();

    // Step 3, Transform secret again, this time with 0x5C
    var B_Transform = '';
    for (var char in secret) {
        B_Transform += String.fromCharCode(secret[char].charCodeAt() ^ 0x5C);
    }
    var diff = 0x40 - B_Transform.length
    if (diff < 0) diff = 0;
    for (i = 0; i < diff; i++) {
        B_Transform += String.fromCharCode(0x5C);
    }

    // Step 4, Hash transformed secret and the result of earlier hash to get final token
    var m2 = crypto.createHash('sha1');
    m2.update(B_Transform);
    m2.update(C);
    var D = m2.digest('hex');
    return D;
}

console.log(GenerateToken('c9c6eb583bd91d7d923084b2fe57c790a7a30203','ver=1&tc=1464664242&nonce=0000295c00007084&uid=d8wP5gy2K%2BrsJYAi8PKf%2BJq4Bv8%3D&host=DUMPTRUCK&path=&hbody=da39a3ee5e6b4b0d3255bfef95601890afd80709'));
console.log('Red5 669d417a26d056693a63f3c63437be717febb615 ver=1&tc=1464664242&nonce=0000295c00007084&uid=d8wP5gy2K%2BrsJYAi8PKf%2BJq4Bv8%3D&host=DUMPTRUCK&path=&hbody=da39a3ee5e6b4b0d3255bfef95601890afd80709'');
⚠️ **GitHub.com Fallback** ⚠️