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; } ?>
[…] Published EA FIFA12 Ultimate Team Secret Question Answer Hash. […]
How did you figure out to translate from a resourceID to a baseID?
I read this thread: http://www.se7ensins.com/forums/threads/calculating-resource-ids.531674/ and applied the logic to FIFA 12.
The constant was different but the jumps afterwards were the same
Ah right good thread there… Thanks Rob!
How would you go around calling the hash funtion with a string in HTML and it return the encrypted version?
Dont worry, got it, but the php code returns a Fatal error: Cannot redeclare hash()
Fix is to rename the function Hash() to something like HashMD5() probably because there is already a hash function in php
Hey Rob,
wooow, thanks for your link above. i’m looking for a method to identicate automaticly “non standard resourceIds” as IF1, IF2 etc.. / UP or Transfer . i couldnt find anything in the json-file. there ist just a property called “rareflag” for special cards, but it seems to be inremented for every new version of the player and doesn’t tells the kind of the card.
it would be nice if you can write me an email. if you help me i will reward you with a small amount of money (paypal or amazon wishlist).
I got the search feature working. I created http://fifasearch.com to keep updated with player prices
Hej rob. I just wanted to inform you that some hex values are wrong. I dumped an array for c++,but I could redump one for php. Idk if you want to make your code based on arrays,because I cann’t code in php. greetz(I would pass you the array)
This PHP script appears to generate the wrong hash value as of today 25th December, and possibly some time before this. This could be related to the previous post. Maybe this has changed for FIFA 13?