javascriptphpxormongoose-web-server

XOR strings: JS vs PHP


I tried to XOR two strings in PHP and in JS and I got different results:

PHP function

function xh($a, $b) {
  $res = ""; $i = strlen($a); $j = strlen($b);
  while($i-->0 && $j-->0) {
    $res.= $a[$i] ^ $b[$j];
  }
  return base64_encode($res);
}

JS function

function xh(a, b) {
  var res = "", i = a.length, j = b.length;
  while (i-->0 && j-->0) {
    res+= String.fromCharCode(a.charCodeAt(i) ^ b.charCodeAt(j));
  }
  return btoa(res);
}

I examined the bytes and found out that the sixth byte in PHP function is always zero, so I updated JS function to

JS function equivalent to PHP

function xh2(a, b) {
  var res = "", i = a.length, j = b.length;
  while (i-->0 && j-->0) {
    res+= String.fromCharCode((a.charCodeAt(i) ^ b.charCodeAt(j)) & 95);
  }
  return btoa(res);
}

So what is happening to that bit?

Example input/output:

string a: 5D41402ABC4B2A76B9719D911017C592
string b: FE2D010308A6B3799A3D9C728EE74244
PHP says: Bg0HVwBUVQkDDgcAVQRYWw8AUlBUVVtSUgIBBFUGAVM=
 JS says: Bg0HdwB0dQkDDgcAdQR4ew8AcnB0dXtycgIBBHUGAXM=
JS2 says: Bg0HVwBUVQkDDgcAVQRYWw8AUlBUVVtSUgIBBFUGAVM=

First difference in this example:

C: 0x43  = 0100 0011
4: 0x34  = 0011 0100
C^4 (JS) = 0111 0111 = 0x77 (correct)
C^4 (PHP)= 0101 0111 = 0x57
             ^
             sixth bit wrong

The inputs are MD5 hashes, I use default encoding, my OEM charset is CP1250, locale cs-cz, the files are stored in UTF-8 encoding and the page is generated with HTTP header text/html;charset=UTF-8 and with meta tag UTF-8 if any of these matters.

My web server is Mongoose 6.7 with php 5.6 (cgi) bundled. I also tried the latest 7.3 (x86 and x64) with the same results, however @apokryfos in the comments tested it with the sixth bit correct.


Solution

  • The root of the problem is case-sensitivity: seems like some buggy implementations of MD5 doesn't lower the case of md5 output. Two different libraries were used on the client side and on the server side.

    'A' starts at 0x41 = 0100 0001
    'a' starts at 0x61 = 0110 0001
                           ^
                           here is the sixth bit