3

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.

Community
  • 1
  • 1
JYelton
  • 35,664
  • 27
  • 132
  • 191

3 Answers3

3

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; 
    }
fuzionpro
  • 601
  • 6
  • 19
  • Few modifications for 128 or 256 keySize, if you use `MCRYPT_RIJNDAEL_128` then you need to use `PBKDF1($pass, $salt, 2, 16)` where `2` is the number of iterations and `16` is `128/8`, if you want to use `MCRYPT_RIJNDAEL_256` then change `16` to `32` because `256/8=32` – Ahsaan Yousuf Mar 12 '18 at 09:59
2

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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Your information is helpful, but I think you misunderstand one part: I am trying to convert from C# to PHP (not the other way around). The `PasswordDeriveBytes` apparently defaults to 100 iterations [(reference)](http://stackoverflow.com/questions/1356899/passwordderivebytes-vs-rfc2898derivebytes-obsolete-but-way-faster). Should I extend the key and IV lengths in PHP from 8 to 32, because of the usage of AES-256? Thank you. – JYelton Jan 26 '12 at 17:09
  • Try and avoid defaults (if you can change the C# code). I think the arguments hold...however the reasoning will be the oposite. I'll rewrite my answer accordingly. – Maarten Bodewes Jan 26 '12 at 18:15
  • If the key is correct and the IV is not, you might want to try to call PBKDF1 twice, once for the key requesting 32 bytes and a second one using 16 bytes for the IV, but I think the above code should be right. I'll remove this comment if it is. – Maarten Bodewes Jan 26 '12 at 19:31
  • The information about the encryption method is very helpful, thanks! I am not 100% sure that the PBKDF1 function is correct, it was taken from [another answer](http://stackoverflow.com/a/3505588/161052). I have not successfully gotten the PBKDF1 function to generate the correct keys. My last attempt was to set the key and IV manually based on output from the C# application. However the encryption still results in a different result. (I did this for example by setting `$key = base64_decode('key_from_c#_base64_encoded');` Encryption is frustrating! – JYelton Jan 26 '12 at 19:40
  • The error is as follows: `'Size of key is too large for this algorithm'` – JYelton Jan 26 '12 at 19:42
  • It doesn't run, I tried running it but the PBKDF1 function in PHP does not perform the "extension" of the PBKDF1 function created by microsoft, and leave it to those idiots to forget to specify it. I'll guess a bit. Could you print out the key and iv in hex, retrieved from your C# code? – Maarten Bodewes Jan 26 '12 at 19:45
  • Here is the output from C# using "hello" and "application-salt" as password and salt, respectively: Key: `n8zJk2+gKAhVn4z8bW0RjenDNnnXQvFDtMe5m+Wifv4=`, IV: `VZ+M/G1tEY258MjRohziAg==`, Output: `Hk4he+qKGsO5BcL2HDtbkA==` – JYelton Jan 26 '12 at 19:51
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/7076/discussion-between-owlstead-and-jyelton) – Maarten Bodewes Jan 26 '12 at 19:57
  • Although I did not wind up recreating the Microsoft-specific PBKDF1 functionality, you wound up exposing the heart of the problem and providing much-needed assistance. This answer absolutely explains the headache with this particular case and thus gets my acceptance. – JYelton Jan 27 '12 at 18:00
-1

PHP already has this capability built into its Mcrypt module.

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

Chris Laplante
  • 29,338
  • 17
  • 103
  • 134
  • This very page is open on another tab already and I've used `mcrypt_encrypt` in my function. However, generating the correct key and initialization vector aren't working out. Additional help is needed! :) – JYelton Jan 25 '12 at 23:01
  • Sorry about that, I didn't read your question close enough. Have you checked out any of the examples on the Mcrypt pages? They have good examples of doing what you want. – Chris Laplante Jan 25 '12 at 23:03
  • Yes the two examples are specific to TripleDES and ECB; despite that I think I may need to employ `mcrypt_create_iv` which is shown in example 2. However it was about this point I felt in well over my head in encryption territory. – JYelton Jan 25 '12 at 23:06
  • They should be similar. Unfortunately I too am not that knowledgeable about encryption when it comes to specific algorithms. Hopefully someone else can deal with the specifics. Sorry :( – Chris Laplante Jan 25 '12 at 23:59