Question

There are already some helpful questions on SO:

However I am still having difficulties with my particular case.

I've tried various methods but end up getting the error "The IV parameter must be as long as the blocksize" or text that doesn't match the resulting hash.

I don't understand encryption enough to work out what I'm doing wrong.

Here is the php version:

$pass = 'hello';
$salt = 'application-salt';

echo Encrypt('hello', 'application-salt');

function Encrypt($pass, $salt)
{
    $derived = PBKDF1($pass, $salt, 100, 16);
    $key = bin2hex(substr($derived, 0, 8));
    $iv = bin2hex(substr($derived, 8, 8));
    return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $pass, MCRYPT_MODE_CBC, $iv);
}

function PBKDF1($pass, $salt, $count, $dklen)
{
    $t = $pass.$salt;
    $t = sha1($t, true);
    for($i=2; $i <= $count; $i++)
    {
        $t = sha1($t, true);
    }
    $t = substr($t,0,$dklen-1);
    return $t;
}

And the C# version:

Console.WriteLine(Encrypt("hello", "application-salt"));
// output: "Hk4he+qKGsO5BcL2HDtbkA=="

public static string Encrypt(string clearText, string Password)
{
    byte[] clearData = System.Text.Encoding.Unicode.GetBytes(clearText);
    PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password,
        new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

    MemoryStream ms = new MemoryStream();
    Rijndael alg = Rijndael.Create();
    alg.Key = pdb.GetBytes(32);
    alg.IV = pdb.GetBytes(16);
    CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
    cs.Write(clearData, 0, clearData.Length);
    cs.Close();
    byte[] encryptedData = ms.ToArray();

    return Convert.ToBase64String(encryptedData);
}

I want to be able to validate user logins in a new php-based application which will communicate to the same MySQL database as an existing C# application. I intend to encrypt the password and compare the resulting hash to the one stored in the database to authenticate.

Any pointers would be most appreciated.

Edit:

I realize that in the C# function, the PasswordDeriveBytes is being called and passed a byte array as an argument for which I don't have an analog in the PHP version. I discovered that this originates from a Codeproject example and that the byte array in ASCII spells "Ivan Medvedev" whom I assume to be the example author. Unfortunately I cannot change this.

Was it helpful?

Solution

I think that the PHP version may actually add 00h valued bytes to the key and IV. They both have an invalid size : 8 bytes for each. They need to be extended to 16 bytes for AES-128. In your C# code you use 32 bytes for the key, which will therefore use AES with a key size of 256 bits.

Futhermore, you don't specify the number of iterations in PasswordDeriveBytes, you should specify it as the class does not specify the default number of iterations - according to your comments, this would be 100, lets assume it is.

Oh, and you use the incorrect encryption method. MCRYPT_RIJNDAEL_256 specifies the Rijndael algorithm using a blocksize of 256 bits, not keys of 256 bits. Presumably, the bitsize of the keys is simply the number of bytes of the key times 8.

Could you replace your Encrypt function with this and try again?

function Encrypt($pass, $salt)
{
     $derived = PBKDF1($pass, $salt, 100, 48);
     $key = bin2hex(substr($derived, 0, 32));
     $iv = bin2hex(substr($derived, 32, 16));
     return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $pass, MCRYPT_MODE_CBC, $iv);
}

Finally, please check if the generated IV and key match with the ones in PHP before performing encryption or decryption. Are you sure that that PHP PBKDF1 function is correct?

UPDATE: Here is some more information on the M$ PBKDF1 routines in PasswordDeriveBytes (including Java code which you may try and convert):

ha, I see your point.

Interestingly, using .NET: the results are different when calling 48 or calling 32 followed by 16:

.NET GetBytes( 32 +16 ): 04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922655D8DD89AE1CAAC60A8041FCD7E8DA4

.NET GetBytes( 32 ) 04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922 Followed by GetBytes( 16 ) 89946D3662B3196860A8041FCD7E8DA4

True Microsoft code, and they cannot change it because it could break applications in the field. Note that they also would return different results when calling it with 16 and then 8 bytes or directly by 24 bytes by design. You'd better upgrade to PBKDF2, and keep PBKDF1 limited to 20 bytes max, as defined in the standards.

OTHER TIPS

Below snippet may be helpful who are looking for exact conversion from C# to PHP

<?php
    class Foo {
      protected $mcrypt_cipher = MCRYPT_RIJNDAEL_128;
      protected $mcrypt_mode = MCRYPT_MODE_CBC;

      public function decrypt($key, $iv, $encrypted)
      {
        return mcrypt_decrypt($this->mcrypt_cipher, $key, base64_decode($encrypted), $this->mcrypt_mode, $iv);
      }

      public function encrypt($key, $iv, $password)
      {
        $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
          $padding = $block - (strlen($password) % $block);
          $password .= str_repeat(chr($padding), $padding);
        return mcrypt_encrypt($this->mcrypt_cipher, $key, $password, $this->mcrypt_mode, $iv);
      }
    }
    $foo = new Foo;
    $pass = 'p@ss';
    $salt = 's@1t';

    $key = PBKDF1($pass, $salt, 2, 32);

    $iv = "@1B2c3D4e5F6g7H8";

    $encrypted = $foo->encrypt($key,$iv,'test@123');

    $encrypted = base64_encode($encrypted);

    echo 'Encrypted: '.$encrypted.'</br>';
    echo 'Decrypted: '.$foo->decrypt($key, $iv, $encrypted);



    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; 
    }

PHP already has this capability built into its Mcrypt module.

Try this: http://www.php.net/manual/en/book.mcrypt.php

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top