0

Here's some C# code (I've modified it slightly to modify some of the hard coded values in it):

public static string Decrypt(string InputFile)
{
    string outstr = null;

    if ((InputFile != null))
    {
        if (File.Exists(InputFile))
        {

            FileStream fsIn = null;
            CryptoStream cstream = null;

            try
            {
                byte[] _b = { 94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101 };
                fsIn = File.Open(InputFile, FileMode.Open, System.IO.FileAccess.Read);

                SymmetricAlgorithm symm = new RijndaelManaged();
                PasswordDeriveBytes Key = new PasswordDeriveBytes(System.Environment.MachineName, System.Text.Encoding.Default.GetBytes("G:MFX62rlABW:IUYAX(i"));
                ICryptoTransform transform = symm.CreateDecryptor(Key.GetBytes(24), _b);
                cstream = new CryptoStream(fsIn, transform, CryptoStreamMode.Read);

                StreamReader sr = new StreamReader(cstream);

                char[] buff = new char[1000];
                sr.Read(buff, 0, 1000);

                outstr = new string(buff);
            }
            finally
            {
                if (cstream != null)
                {
                    cstream.Close();
                }
                if (fsIn != null)
                {
                    fsIn.Close();
                }
            }
        }
    }
    return outstr;
}

I need to come up with a function to do the same in PHP. Bear in mind, I did not write the C# code and I cannot modify it, so even if it's bad, I'm stuck with it. I've searched all over and have found bits and pieces around, but nothing that works so far. All examples I've found use mcrypt, which seems to be frowned upon these days, but I'm probably stuck using it. Next, I found the following post which has some useful info: Rewrite Rijndael 256 C# Encryption Code in PHP

So looks like the PasswordDeriveBytes class is the key to this. I created the following PHP code to try to decrypt:

function PBKDF1($pass,$salt,$count,$dklen) { 
    $t = $pass.$salt;
    //echo 'S||P: '.bin2hex($t).'<br/>';
    $t = sha1($t, true); 
    //echo 'T1:' . bin2hex($t) . '<br/>';
    for($i=2; $i <= $count; $i++) { 
        $t = sha1($t, true); 
        //echo 'T'.$i.':' . bin2hex($t) . '<br/>';
    } 
    $t = substr($t,0,$dklen); 
    return $t;      
}

$input = 'Ry5WdjGS8rpA9eA+iQ3aPw==';
$key   = "win7x64";
$salt  = implode(unpack('C*', "G:MFX62rlABW:IUYAX(i"));

$salt   = pack("H*", $salt); 
$it     = 1000; 
$keyLen = 16; 
$key    = PBKDF1($key, $salt, $it, $keyLen);
$key    = bin2hex(substr($key, 0, 8));
$iv     = bin2hex(substr($key, 8, 8));

echo trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($input), MCRYPT_MODE_CBC, $iv));

You'll note that for what I believe to be System.Environment.MachineName, I put in a fixed value for now which is the computer name of the machine I'm on, so should be equivalent of what the C# code is doing. Other than that, I've noticed that using MCRYPT_RIJNDAEL_256 doesn't work, it throws the error "The IV parameter must be as long as the blocksize". If I use MCRYPT_RIJNDAEL_128, I don't get that error, but decryption still fails. I assume I'm missing the piece for the byte array _b that's used by the CreateDecryptor function, I have no idea where that's supposed to fit in. Any help is appreciated.

UPDATE

This is the solution, which was made possible by the answer marked correct. Note that the code for the PBKDF1 function is not mine, it was linked to in the answer.

function PBKDF1($pass, $salt, $count, $cb) {
  static $base;
  static $extra;
  static $extracount= 0;
  static $hashno;
  static $state = 0;

  if ($state == 0)
  {
    $hashno = 0;
    $state = 1;

    $key = $pass . $salt;
    $base = sha1($key, true);
    for($i = 2; $i < $count; $i++)
    {
      $base = sha1($base, true);
    }
  }

  $result = "";

  if ($extracount > 0)
  {
    $rlen = strlen($extra) - $extracount;
    if ($rlen >= $cb)
    {
      $result = substr($extra, $extracount, $cb);
      if ($rlen > $cb)
      {
        $extracount += $cb;
      }
      else
      {
        $extra = null;
        $extracount = 0;
      }
      return $result;
    }
    $result = substr($extra, $rlen, $rlen);
  }

  $current = "";
  $clen = 0;
  $remain = $cb - strlen($result);
  while ($remain > $clen)
  {
    if ($hashno == 0)
    {
      $current = sha1($base, true);
    }
    else if ($hashno < 1000)
    {
      $n = sprintf("%d", $hashno);
      $tmp = $n . $base;
      $current .= sha1($tmp, true);
    }
    $hashno++;
    $clen = strlen($current);     
  }

  // $current now holds at least as many bytes as we need
  $result .= substr($current, 0, $remain);

  // Save any left over bytes for any future requests
  if ($clen > $remain)
  {
    $extra = $current;
    $extracount = $remain;
  }

  return $result; 
}

$input  = 'base 64 encoded string to decrypt here';
$key    = strtoupper(gethostname());
$salt   = 'G:MFX62rlABW:IUYAX(i';
$it     = 100; 
$keyLen = 24;
$key    = PBKDF1($key, $salt, $it, $keyLen);
$iv     = implode(array_map('chr', [94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101]));
Community
  • 1
  • 1
Rocket04
  • 1,801
  • 5
  • 25
  • 47

1 Answers1

1

_b is a static value that is used as the IV (CreateDecryptor takes a key and IV parameter). Since it is 16 bytes long, this means that you're using Rijndael-128 or more commonly known AES.

Key.GetBytes(24) suggests that a 24 byte key is derived and not a 16 byte key.

Make sure that

  • System.Text.Encoding.Default is equivalent with implode(unpack('C*', ...,
  • Default value for iterations of PasswordDeriveBytes is 1000,
  • Default value for hash of PasswordDeriveBytes is SHA-1

Security problems:

  • PBKDF1 is obsolete and PBKDF2 isn't that much better. Use up-to-date key derivation algorithms like Argon2 or scrypt.
  • The IV must be randomly chosen to achieve semantic security. It doesn't have to be secret, so it can be sent along with the ciphertext.
  • Stretching a key by encoding it to hex doesn't provide any security (don't use bin2hex).
  • The ciphertext is not authenticated, which means that you cannot detect (malicious) manipulation of encrypted messages. Employ encrypt-then-MAC.
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • OK, so I think I have the $iv right now. My problem is the key. I think the fact that the key is 24 bytes is a problem, based on what is said here: http://stackoverflow.com/questions/3505453/rewrite-rijndael-256-c-sharp-encryption-code-in-php#comment11331849_3505588 " the PasswordDeriveBytes is only compatible with PBKDF1 if - and only if - the requested length is 20 or less bytes, the maximum output of PBKDF1 with SHA1. PasswordDeriveBytes uses a proprietary scheme, which is unsafe and not even consistent (e.g. asking 32 and then 16 bytes creates a different result than requesting 48 bytes)" – Rocket04 Apr 09 '16 at 14:50
  • I would only consider that you're screwed in that you would have to implement `PasswordDeriveBytes` in PHP, which shouldn't take long, since the [source is freely available](http://referencesource.microsoft.com/#mscorlib/system/security/cryptography/passwordderivebytes.cs,0b54c5106dead015). Or you could dig a little deeper in your favorite search engine – Artjom B. Apr 09 '16 at 14:55
  • Look no further. [This snippet](http://stackoverflow.com/a/22920556/1816580) works as expected. C# code: http://ideone.com/h5lMj1 and PHP code: http://ideone.com/o0YYm0 Keep in mind that default IterationCount seems to be 100 and not 1000 – Artjom B. Apr 09 '16 at 15:11
  • You are the single greatest human being on the face of the earth. Odes and sonnets should be written praising your awesomeness. Thanks so much, I will mark this the answer. – Rocket04 Apr 09 '16 at 19:29
  • **Don't use PBKDF1**. – Scott Arciszewski Apr 12 '16 at 15:28