不确定如何将C#加密函数移植到PHP

I have a basic encryption function in C# that I use to encrypt and store save files for a game, which uses a user's username and password for the game's website. Recently I realized that if a user was to change their password, it would cause their save to become inaccessible, since it's encrypted with their old password. So, I'm trying to build a PHP function to "reconvert" their save when they update their password. Unfortunately, the problem comes in that C# uses byte arrays to perform hashing, and PHP uses strings, and I'm not sure how to reconcile the two. Here's the general process:

  • hash username (into byte array)
  • hash password (into byte array)
  • concatenate username + password hashes (into twice-as-long byte array)
  • hash result to get key (which is back to the 256-bit length)
  • xor file byte-by-byte with key to get encrypted file

I'm not sure how to accomplish this in PHP. I was originally trying to use unpack('C*', hash("sha256", $thing_to_be_hashed, false)) but this didn't seem to yield the correct byte arrays. Then I tried this:

$new_key = hash("sha256", hash("sha256", $username, false) . hash("sha256", $newpass, false), false);
$old_key = hash("sha256", hash("sha256", $username, false) . hash("sha256", $oldpass, false), false);

$conversion_key = array();
for($i = 0; $i < strlen($new_key); ++$i) {
    $conversion_key[] = $old_key[$i] ^ $new_key[$i]; 
    // this part with the array is a bit wonky, I know, but it didn't seem like
    // it would have worked anyway since I was xor'ing things like 'f' and '3'
    // instead of bytes like '23' and '57'.
}
$file_contents = file_get_contents("savefile.sav");
for($i = 0; $i < strlen($file_contents); ++$i) {
    $file_contents[$i] = $file_contents[$i] ^ $conversion_key[$i % count($conversion_key)]; // I read somewhere I can access $file_contents like this to get bytes
}

Neither method works, unfortunately, at any stage (the previous unpack method didn't produce correct byte arrays for even the first stage of hashing, nor am I sure I was putting them back into a string correctly for the second stage). Would appreciate any help I can get. Thanks!

EDIT: It occurred to me unpack() wouldn't be giving me the correct byte arrays because it's literally decoding the strings; so I guess my main question is firstly "how would I convert the hexits returned by hash() to byte arrays?" and secondly "how would I go about hashing those byte arrays?" (with corollary "would converting the byte arrays back to strings of hexits and hashing those be sufficient?")

In contrast to .NET where strings are encoded Unicode sequences, PHP's strings are dumb sequences of bytes -- so in essence, they already are byte arrays. So on the PHP side you can do all you need with strings and there is no need to involve arrays at all, which in turn means unpack is not required.

The main thing to deal with is that PHP hash functions return hex-encoded instead of straight binary output by default. It's very easy to convert from one to the other: use hex2bin, but it's also much easier to just give the third (optional) argument as true.

So you can get the hashes you need with:

// for convenience
function sha256($data) {
    return hash("sha256", $data, true);
}

$new_key = sha256(sha256($username).sha256($newpass));
$old_key = sha256(sha256($username).sha256($oldpass));

This function will then XOR the two keys:

function str_xor($str1, $str2) {
    if (strlen($str1) != strlen($str2)) {
        return false;
    }

    $len = strlen($str1);
    for($i=0; $i < $len; $i++) {
        $str1[$i] = $str1[$i] ^ $str2[$i];
    }

    return $str1;
}

So you can then use your existing loop to "re-encrypt" the saved data from one key to the other.