I’ve been playing around with the EA FIFA Ultimate Team webapp to amuse myself and learn how the thing works.
If you’ve read my previous posts on the subject you’ll have noticed that the code I was using required a hash to validate you on the EA servers. When I first started writing the code I used Google Chromes’ Developer tools to view the HTML Headers being sent when the Secret Question Answer was submitted and copying the hash which was sent as the POST value.

It annoyed me that that was the only current way of getting this hash. I compared the hash it returned against a whole host of hashing algorithms to see if I could find the one they used. I drew a blank and so had to go at it a different way.

I realised that the Secret Question page had to include hashing function as it passed the hashed value onto the next page. So again using Google Chromes’ Developer tools I dove into the Javascript files that were loaded with that page and a jackpot I did hit.

One of the Javascript files contained the hashing algorithm which it turns out is a modified version of the MD5 algorithm.
I’m not sure if they are intentional changes to the MD5 algorithm or if they are mistakes made when copy/pasting code, I guess we will never know.

Below is the original Javascript code followed by my PHP function which has the two changes commented on.

</pre>
function hash( securityAnswer )
{
 var cleanAnswer = cleanString( securityAnswer );
 if ( cleanAnswer == "" )
 {
 return;
 }
 var answer = md5( securityAnswer );
}

function cleanString( dirtyString )
{
 var cleanString;
 cleanString = dirtyString.replace(/^\s*/, "").replace(/\s*$/, "");
 cleanString = cleanString.replace(/\s{2,}/, " ");
 return cleanString;
}

//MD5
// shift amounts in each round
var r1Shifts = [ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 ];
var r2Shifts = [ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 ];
var r3Shifts = [ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 ];
var r4Shifts = [ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 ];

// convert number to hex string
var hex_chr = "0123456789abcdef";
function numToHex(num)
{
 str = "";
 for(j = 0; j <= 3; j++)
 {
 str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F);
 }
 return str;
}

// Process message into chunks
function chunkMessage( str )
{
 nblk = ((str.length + 8) >> 6) + 1;
 blks = new Array(nblk * 16);

 for(i = 0; i < nblk * 16; i++)
 {
 blks[i] = 0;
 }
 for(i = 0; i < str.length; i++)
 {
 blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
 }

 blks[i >> 2] |= 0x80 << ((i % 4) * 8);
 blks[nblk * 16 - 2] = str.length * 8;

 return blks;
}

function add(x, y)
{
 var lsw = (x & 0xFFFF) + (y & 0xFFFF);
 var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
 return (msw << 16) | (lsw & 0xFFFF);
}

// Bitwise rotate 32bit num to left
function bitwiseRotate( x, c )
{
 return ( x << c ) | ( x >>> (32 - c) );
}

// Basic MD5 operations
function cmn(q, a, b, x, s, t)
{
 return add( bitwiseRotate( add( add(a, q), add(x, t) ), s ), b );
}
function md5_f(a, b, c, d, x, s, t)
{
 return cmn( (b & c) | ((~b) & d), a, b, x, s, t );
}
function md5_g(a, b, c, d, x, s, t)
{
 return cmn( (b & d) | (c & (~d)), a, b, x, s, t );
}
function md5_h(a, b, c, d, x, s, t)
{
 return cmn( b ^ c ^ d, a, b, x, s, t );
}
function md5_i(a, b, c, d, x, s, t)
{
 return cmn( c ^ (b | (~d)), a, b, x, s, t );
}

function md5(str)
{
 x = chunkMessage(str);

 a = 1732584193;
 b = -271733879;
 c = -1732584194;
 d = 271733878;
 for ( i = 0; i < x.length; i += 16 )
 {
 tempA = a;
 tempB = b;
 tempC = c;
 tempD = d;
 a = md5_f(a, b, c, d, x[i+ 0], r1Shifts[0] , -680876936);
 d = md5_f(d, a, b, c, x[i+ 1], r1Shifts[1] , -389564586);
 c = md5_f(c, d, a, b, x[i+ 2], r1Shifts[2] , 606105819);
 b = md5_f(b, c, d, a, x[i+ 3], r1Shifts[3] , -1044525330);
 a = md5_f(a, b, c, d, x[i+ 4], r1Shifts[4] , -176418897);
 d = md5_f(d, a, b, c, x[i+ 5], r1Shifts[5] , 1200080426);
 c = md5_f(c, d, a, b, x[i+ 6], r1Shifts[6] , -1473231341);
 b = md5_f(b, c, d, a, x[i+ 7], r1Shifts[7] , -45705983);
 a = md5_f(a, b, c, d, x[i+ 8], r1Shifts[8] , 1770035416);
 d = md5_f(d, a, b, c, x[i+ 9], r1Shifts[9] , -1958414417);
 c = md5_f(c, d, a, b, x[i+10], r1Shifts[10], -42063);
 b = md5_f(b, c, d, a, x[i+11], r1Shifts[11], -1990404162);
 a = md5_f(a, b, c, d, x[i+12], r1Shifts[12], 1804603682);
 d = md5_f(d, a, b, c, x[i+13], r1Shifts[13], -40341101);
 c = md5_f(c, d, a, b, x[i+14], r1Shifts[14], -1502002290);
 b = md5_f(b, c, d, a, x[i+15], r1Shifts[15], 1236535329);
 a = md5_g(a, b, c, d, x[i+ 1], r2Shifts[0] , -165796510);
 d = md5_g(d, a, b, c, x[i+ 6], r2Shifts[1] , -1069501632);
 c = md5_g(c, d, a, b, x[i+11], r2Shifts[2] , 643717713);
 b = md5_g(b, c, d, a, x[i+ 0], r2Shifts[3] , -373897302);
 a = md5_g(a, b, c, d, x[i+ 5], r2Shifts[4] , -701558691);
 d = md5_g(d, a, b, c, x[i+10], r2Shifts[5] , 38016083);
 c = md5_g(c, d, a, b, x[i+15], r2Shifts[6] , -660478335);
 b = md5_g(b, c, d, a, x[i+ 4], r2Shifts[7] , -405537848);
 a = md5_g(a, b, c, d, x[i+ 9], r2Shifts[8] , 568446438);
 d = md5_g(d, a, b, c, x[i+14], r2Shifts[9] , -1019803690);
 c = md5_g(c, d, a, b, x[i+ 3], r2Shifts[10], -187363961);
 b = md5_g(b, c, d, a, x[i+ 8], r2Shifts[11], 1163531501);
 a = md5_g(a, b, c, d, x[i+13], r2Shifts[12], -1444681467);
 d = md5_g(d, a, b, c, x[i+ 2], r2Shifts[13], -51403784);
 c = md5_g(c, d, a, b, x[i+ 7], r2Shifts[14], 1735328473);
 b = md5_g(b, c, d, a, x[i+12], r2Shifts[15], -1926607734);

 a = md5_h(a, b, c, d, x[i+ 5], r3Shifts[0] , -378558);
 d = md5_h(d, a, b, c, x[i+ 8], r3Shifts[1] , -2022574463);
 //line below uses r2Shifts[2] where as MD5 would use r3Shifts[2]
 c = md5_h(c, d, a, b, x[i+11], r2Shifts[2] , 1839030562);
 b = md5_h(b, c, d, a, x[i+14], r3Shifts[3] , -35309556);
 a = md5_h(a, b, c, d, x[i+ 1], r3Shifts[4] , -1530992060);
 d = md5_h(d, a, b, c, x[i+ 4], r3Shifts[5] , 1272893353);
 c = md5_h(c, d, a, b, x[i+ 7], r3Shifts[6] , -155497632);
 b = md5_h(b, c, d, a, x[i+10], r3Shifts[7] , -1094730640);
 a = md5_h(a, b, c, d, x[i+13], r3Shifts[8] , 681279174);
 d = md5_h(d, a, b, c, x[i+ 0], r3Shifts[9] , -358537222);
 c = md5_h(c, d, a, b, x[i+ 3], r3Shifts[10], -722521979);
 b = md5_h(b, c, d, a, x[i+ 6], r3Shifts[11], 76029189);
 a = md5_h(a, b, c, d, x[i+ 9], r3Shifts[12], -640364487);
 d = md5_h(d, a, b, c, x[i+12], r3Shifts[13], -421815835);
 c = md5_h(c, d, a, b, x[i+15], r3Shifts[14], 530742520);
 b = md5_h(b, c, d, a, x[i+ 2], r3Shifts[15], -995338651);
 a = md5_i(a, b, c, d, x[i+ 0], r4Shifts[0] , -198630844);
 d = md5_i(d, a, b, c, x[i+ 7], r4Shifts[1] , 1126891415);
 c = md5_i(c, d, a, b, x[i+14], r4Shifts[2] , -1416354905);
 b = md5_i(b, c, d, a, x[i+ 5], r4Shifts[3] , -57434055);
 a = md5_i(a, b, c, d, x[i+12], r4Shifts[4] , 1700485571);
 d = md5_i(d, a, b, c, x[i+ 3], r4Shifts[5] , -1894986606);
 c = md5_i(c, d, a, b, x[i+10], r4Shifts[6] , -1051523);
 b = md5_i(b, c, d, a, x[i+ 1], r4Shifts[7] , -2054922799);
 a = md5_i(a, b, c, d, x[i+ 8], r4Shifts[8] , 1873313359);
 d = md5_i(d, a, b, c, x[i+15], r4Shifts[9] , -30611744);
 c = md5_i(c, d, a, b, x[i+ 6], r4Shifts[10], -1560198380);
 b = md5_i(b, c, d, a, x[i+13], r4Shifts[11], 1309151649);
 a = md5_i(a, b, c, d, x[i+ 4], r4Shifts[12], -145523070);
 d = md5_i(d, a, b, c, x[i+11], r4Shifts[13], -1120210379);
 c = md5_i(c, d, a, b, x[i+ 2], r4Shifts[14], 718787259);
 b = md5_i(b, c, d, a, x[i+ 9], r4Shifts[15], -343485551);
 //This line is doubled for some reason, line below is not in the MD5 version
 b = md5_i(b, c, d, a, x[i+ 9], r4Shifts[15], -343485551);
 a = add(a, tempA);
 b = add(b, tempB);
 c = add(c, tempC);
 d = add(d, tempD);
 }

 return numToHex(a) + numToHex(b) + numToHex(c) + numToHex(d);
}
<pre>

<?php
/**
* @llygoden
* @author - Rob McGhee
* @URL - www.robmcghee.com
* @date - 08/06/12
* @version - 1.0
**/

/* This is EAs implementation of the MD5 hashing algorithm
* It's identical to the standard MD5 algorithm apart from two changes
* Line 62 - uses 14 instead of 16 for the shift value
* Line 96 - they do the last II function twice on 'b' where MD5 only does it once
* I'm not sure if these are intentional changes
* or just errors when copy pasting by a programmer
*/

function hash($string){
 $a = "67452301";
 $b = "EFCDAB89";
 $c = "98BADCFE";
 $d = "10325476";

$words = init($string);

for($i = 0; $i <= count($words)/16-1; $i++){
 $A = $a;
 $B = $b;
 $C = $c;
 $D = $d;

/* ROUND 1 */
 FF ($A, $B, $C, $D, $words[0 + ($i * 16)], 7, "d76aa478");
 FF ($D, $A, $B, $C, $words[1 + ($i * 16)], 12, "e8c7b756");
 FF ($C, $D, $A, $B, $words[2 + ($i * 16)], 17, "242070db");
 FF ($B, $C, $D, $A, $words[3 + ($i * 16)], 22, "c1bdceee");
 FF ($A, $B, $C, $D, $words[4 + ($i * 16)], 7, "f57c0faf");
 FF ($D, $A, $B, $C, $words[5 + ($i * 16)], 12, "4787c62a");
 FF ($C, $D, $A, $B, $words[6 + ($i * 16)], 17, "a8304613");
 FF ($B, $C, $D, $A, $words[7 + ($i * 16)], 22, "fd469501");
 FF ($A, $B, $C, $D, $words[8 + ($i * 16)], 7, "698098d8");
 FF ($D, $A, $B, $C, $words[9 + ($i * 16)], 12, "8b44f7af");
 FF ($C, $D, $A, $B, $words[10 + ($i * 16)], 17, "ffff5bb1");
 FF ($B, $C, $D, $A, $words[11 + ($i * 16)], 22, "895cd7be");
 FF ($A, $B, $C, $D, $words[12 + ($i * 16)], 7, "6b901122");
 FF ($D, $A, $B, $C, $words[13 + ($i * 16)], 12, "fd987193");
 FF ($C, $D, $A, $B, $words[14 + ($i * 16)], 17, "a679438e");
 FF ($B, $C, $D, $A, $words[15 + ($i * 16)], 22, "49b40821");

 /* ROUND 2 */
 GG ($A, $B, $C, $D, $words[1 + ($i * 16)], 5, "f61e2562");
 GG ($D, $A, $B, $C, $words[6 + ($i * 16)], 9, "c040b340");
 GG ($C, $D, $A, $B, $words[11 + ($i * 16)], 14, "265e5a51");
 GG ($B, $C, $D, $A, $words[0 + ($i * 16)], 20, "e9b6c7aa");
 GG ($A, $B, $C, $D, $words[5 + ($i * 16)], 5, "d62f105d");
 GG ($D, $A, $B, $C, $words[10 + ($i * 16)], 9, "02441453");
 GG ($C, $D, $A, $B, $words[15 + ($i * 16)], 14, "d8a1e681");
 GG ($B, $C, $D, $A, $words[4 + ($i * 16)], 20, "e7d3fbc8");
 GG ($A, $B, $C, $D, $words[9 + ($i * 16)], 5, "21e1cde6");
 GG ($D, $A, $B, $C, $words[14 + ($i * 16)], 9, "c33707d6");
 GG ($C, $D, $A, $B, $words[3 + ($i * 16)], 14, "f4d50d87");
 GG ($B, $C, $D, $A, $words[8 + ($i * 16)], 20, "455a14ed");
 GG ($A, $B, $C, $D, $words[13 + ($i * 16)], 5, "a9e3e905");
 GG ($D, $A, $B, $C, $words[2 + ($i * 16)], 9, "fcefa3f8");
 GG ($C, $D, $A, $B, $words[7 + ($i * 16)], 14, "676f02d9");
 GG ($B, $C, $D, $A, $words[12 + ($i * 16)], 20, "8d2a4c8a");

 /* ROUND 3 */
 HH ($A, $B, $C, $D, $words[5 + ($i * 16)], 4, "fffa3942");
 HH ($D, $A, $B, $C, $words[8 + ($i * 16)], 11, "8771f681");
 //HH ($C, $D, $A, $B, $words[11 + ($i * 16)], 16, "6d9d6122");
 //MD5 would have used the line above here EA use the line below
 //EA change the function to use 14 rather than 16
 HH ($C, $D, $A, $B, $words[11 + ($i * 16)], 14, "6d9d6122");
 HH ($B, $C, $D, $A, $words[14 + ($i * 16)], 23, "fde5380c");
 HH ($A, $B, $C, $D, $words[1 + ($i * 16)], 4, "a4beea44");
 HH ($D, $A, $B, $C, $words[4 + ($i * 16)], 11, "4bdecfa9");
 HH ($C, $D, $A, $B, $words[7 + ($i * 16)], 16, "f6bb4b60");
 HH ($B, $C, $D, $A, $words[10 + ($i * 16)], 23, "bebfbc70");
 HH ($A, $B, $C, $D, $words[13 + ($i * 16)], 4, "289b7ec6");
 HH ($D, $A, $B, $C, $words[0 + ($i * 16)], 11, "eaa127fa");
 HH ($C, $D, $A, $B, $words[3 + ($i * 16)], 16, "d4ef3085");
 HH ($B, $C, $D, $A, $words[6 + ($i * 16)], 23, "04881d05");
 HH ($A, $B, $C, $D, $words[9 + ($i * 16)], 4, "d9d4d039");
 HH ($D, $A, $B, $C, $words[12 + ($i * 16)], 11, "e6db99e5");
 HH ($C, $D, $A, $B, $words[15 + ($i * 16)], 16, "1fa27cf8");
 HH ($B, $C, $D, $A, $words[2 + ($i * 16)], 23, "c4ac5665");

 /* ROUND 4 */
 II ($A, $B, $C, $D, $words[0 + ($i * 16)], 6, "f4292244");
 II ($D, $A, $B, $C, $words[7 + ($i * 16)], 10, "432aff97");
 II ($C, $D, $A, $B, $words[14 + ($i * 16)], 15, "ab9423a7");
 II ($B, $C, $D, $A, $words[5 + ($i * 16)], 21, "fc93a039");
 II ($A, $B, $C, $D, $words[12 + ($i * 16)], 6, "655b59c3");
 II ($D, $A, $B, $C, $words[3 + ($i * 16)], 10, "8f0ccc92");
 II ($C, $D, $A, $B, $words[10 + ($i * 16)], 15, "ffeff47d");
 II ($B, $C, $D, $A, $words[1 + ($i * 16)], 21, "85845dd1");
 II ($A, $B, $C, $D, $words[8 + ($i * 16)], 6, "6fa87e4f");
 II ($D, $A, $B, $C, $words[15 + ($i * 16)], 10, "fe2ce6e0");
 II ($C, $D, $A, $B, $words[6 + ($i * 16)], 15, "a3014314");
 II ($B, $C, $D, $A, $words[13 + ($i * 16)], 21, "4e0811a1");
 II ($A, $B, $C, $D, $words[4 + ($i * 16)], 6, "f7537e82");
 II ($D, $A, $B, $C, $words[11 + ($i * 16)], 10, "bd3af235");
 II ($C, $D, $A, $B, $words[2 + ($i * 16)], 15, "2ad7d2bb");
 II ($B, $C, $D, $A, $words[9 + ($i * 16)], 21, "eb86d391");
 //MD5 would finish on the line above
 //EA change this and use this last line twice
 II ($B, $C, $D, $A, $words[9 + ($i * 16)], 21, "eb86d391");

 addVars($a, $b, $c, $d, $A, $B, $C, $D);
 }

 $MD5 = '';

 foreach (array($a, $b, $c, $d) as $x) {
 $MD5 .= implode('', array_reverse(str_split(leftpad($x, 8), 2)));
 }

return $MD5;
}

/* General functions */
function hexbin($str){
 $hexbinmap = array( "0" => "0000",
 "1" => "0001",
 "2" => "0010",
 "3" => "0011"
 , "4" => "0100"
 , "5" => "0101"
 , "6" => "0110"
 , "7" => "0111"
 , "8" => "1000"
 , "9" => "1001"
 , "A" => "1010"
 , "a" => "1010"
 , "B" => "1011"
 , "b" => "1011"
 , "C" => "1100"
 , "c" => "1100"
 , "D" => "1101"
 , "d" => "1101"
 , "E" => "1110"
 , "e" => "1110"
 , "F" => "1111"
 , "f" => "1111");
 $bin = "";

for ($i = 0; $i < strlen($str); $i++){
 $bin .= $hexbinmap[$str[$i]];
 }

$bin = ltrim($bin, '0');
 return $bin;
}

function strhex($str){
 $hex = "";

 for ($i = 0; $i < strlen($str); $i++){
 $hex = $hex.leftpad(dechex(ord($str[$i])), 2);
 }

 return $hex;
}

/* MD5-specific functions */
function init($string){
 $len = strlen($string) * 8;
 $hex = strhex($string); // convert ascii string to hex
 $bin = leftpad(hexbin($hex), $len); // convert hex string to bin
 $padded = pad($bin);
 $padded = pad($padded, 1, $len);
 $block = str_split($padded, 32);

 foreach ($block as &$b) {
 $b = implode('', array_reverse(str_split($b, 8)));
 }

 return $block;
}

function pad($bin, $type=0, $len = 0){
 if($type == 0){
 $bin = $bin."1";
 $buff = strlen($bin) % 512;
 if($buff != 448){
 while(strlen($bin) % 512 != 448){
 $bin = $bin."0";
 }
 }
 }
 // append length (b) of string to latter 64 bits
 elseif($type == 1){
 $bLen = leftpad(decbin($len), 64);
 $bin .= implode('', array_reverse(str_split($bLen, 8)));
 }
 return $bin;
}

/* MD5 base functions */
function F($X, $Y, $Z){
 $X = hexdec($X);
 $Y = hexdec($Y);
 $Z = hexdec($Z);
 $calc = (($X & $Y) | ((~ $X) & $Z)); // X AND Y OR NOT X AND Z
 return $calc;
}

function G($X, $Y, $Z){
 $X = hexdec($X);
 $Y = hexdec($Y);
 $Z = hexdec($Z);
 $calc = (($X & $Z) | ($Y & (~ $Z))); // X AND Z OR Y AND NOT Z
 return $calc;
}

function H($X, $Y, $Z){
 $X = hexdec($X);
 $Y = hexdec($Y);
 $Z = hexdec($Z);
 $calc = ($X ^ $Y ^ $Z); // X XOR Y XOR Z
 return $calc;
}

function I($X, $Y, $Z){
 $X = hexdec($X);
 $Y = hexdec($Y);
 $Z = hexdec($Z);
 $calc = ($Y ^ ($X | (~ $Z))) ; // Y XOR (X OR NOT Z)
 return $calc;
}

/* MD5 round functions */
/* $A - hex, $B - hex, $C - hex, $D - hex (F - dec)
$M - binary
$s - decimal
$t - hex
*/
function FF(&$A, $B, $C, $D, $M, $s, $t){
 $A = hexdec($A);
 $t = hexdec($t);
 $M = bindec($M);
 $A = ($A + F($B, $C, $D) + $M + $t) & 0xffffffff; //decimal
 $A = rotate($A, $s);
 $A = dechex((hexdec($B) + hexdec($A)) & 0xffffffff);
}

function GG(&$A, $B, $C, $D, $M, $s, $t){
 $A = hexdec($A);
 $t = hexdec($t);
 $M = bindec($M);
 $A = ($A + G($B, $C, $D) + $M + $t) & 0xffffffff; //decimal
 $A = rotate($A, $s);
 $A = dechex((hexdec($B) + hexdec($A)) & 0xffffffff);
}

function HH(&$A, $B, $C, $D, $M, $s, $t){
 $A = hexdec($A);
 $t = hexdec($t);
 $M = bindec($M);
 $A = ($A + H($B, $C, $D) + $M + $t) & 0xffffffff; //decimal
 $A = rotate($A, $s);
 $A = dechex((hexdec($B) + hexdec($A)) & 0xffffffff);
}

function II(&$A, $B, $C, $D, $M, $s, $t){
 $A = hexdec($A);
 $t = hexdec($t);
 $M = bindec($M);
 $A = ($A + I($B, $C, $D) + $M + $t) & 0xffffffff; //decimal
 $A = rotate($A, $s);
 $A = dechex((hexdec($B) + hexdec($A)) & 0xffffffff);
}

// shift
function rotate ($decimal, $bits) { //returns hex
 return dechex((($decimal << $bits) | ($decimal >> (32 - $bits))) & 0xffffffff);
}

function addVars(&$a, &$b, &$c, &$d, $A, $B, $C, $D){
 $A = hexdec($A);
 $B = hexdec($B);
 $C = hexdec($C);
 $D = hexdec($D);
 $aa = hexdec($a);
 $bb = hexdec($b);
 $cc = hexdec($c);
 $dd = hexdec($d);

 $aa = ($aa + $A) & 0xffffffff;
 $bb = ($bb + $B) & 0xffffffff;
 $cc = ($cc + $C) & 0xffffffff;
 $dd = ($dd + $D) & 0xffffffff;

 $a = dechex($aa);
 $b = dechex($bb);
 $c = dechex($cc);
 $d = dechex($dd);
}

function leftpad($needs_padding, $alignment) {
 if (strlen($needs_padding) % $alignment) {
 $pad_amount = $alignment - strlen($needs_padding) % $alignment;
 $left_pad = implode('', array_fill(0, $pad_amount, '0'));
 $needs_padding = $left_pad . $needs_padding;
 }

 return $needs_padding;
}

?>