Rewrite Rijndael 256 C# Encryption Code in PHP
Asked Answered
D

4

12

I have an encryption/decryption algorithm written in C# - I need to be able to produce the same encryption in PHP so I can send the encrypted text over HTTP to be decrypted on the C# side. Here is the C# code for the encryption.

this.m_plainText = string.Empty;
this.m_passPhrase = "passpharse";
this.m_saltValue = "saltvalue";
this.m_hashAlgorithm = "SHA1";
this.m_passwordIterations = 2;
this.m_initVector = "1a2b3c4d5e6f7g8h";
this.m_keySize = 256;

public string Encrypt()
{
    string plainText = this.m_plainText;
    string passPhrase = this.m_passPhrase;
    string saltValue = this.m_saltValue;
    string hashAlgorithm = this.m_hashAlgorithm;
    int passwordIterations = this.m_passwordIterations;
    string initVector = this.m_initVector;
    int keySize = this.m_keySize;

    // Convert strings into byte arrays.
    // Let us assume that strings only contain ASCII codes.
    // If strings include Unicode characters, use Unicode, UTF7, or UTF8 
    // encoding.
    byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
    byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

    // Convert our plaintext into a byte array.
    // Let us assume that plaintext contains UTF8-encoded characters.
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

    // First, we must create a password, from which the key will be derived.
    // This password will be generated from the specified passphrase and 
    // salt value. The password will be created using the specified hash 
    // algorithm. Password creation can be done in several iterations.
    PasswordDeriveBytes password = new PasswordDeriveBytes(
                                                    passPhrase,
                                                    saltValueBytes,
                                                    hashAlgorithm,
                                                    passwordIterations);

    // Use the password to generate pseudo-random bytes for the encryption
    // key. Specify the size of the key in bytes (instead of bits).
    byte[] keyBytes = password.GetBytes(keySize / 8);

    // Create uninitialized Rijndael encryption object.
    RijndaelManaged symmetricKey = new RijndaelManaged();

    // It is reasonable to set encryption mode to Cipher Block Chaining
    // (CBC). Use default options for other symmetric key parameters.
    symmetricKey.Mode = CipherMode.CBC;

    // Generate encryptor from the existing key bytes and initialization 
    // vector. Key size will be defined based on the number of the key 
    // bytes.
    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
                                                     keyBytes,
                                                     initVectorBytes);

    // Define memory stream which will be used to hold encrypted data.
    MemoryStream memoryStream = new MemoryStream();

    // Define cryptographic stream (always use Write mode for encryption).
    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 encryptor,
                                                 CryptoStreamMode.Write);
    // Start encrypting.
    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);

    // Finish encrypting.
    cryptoStream.FlushFinalBlock();

    // Convert our encrypted data from a memory stream into a byte array.
    byte[] cipherTextBytes = memoryStream.ToArray();

    // Close both streams.
    memoryStream.Close();
    cryptoStream.Close();

    // Convert encrypted data into a base64-encoded string.
    string cipherText = Convert.ToBase64String(cipherTextBytes);

    // Return encrypted string.
    return cipherText;
}

I have some similar PHP code that may help. It doesn't do exactly as needed, but I think it may be a good place to start.

<?php

/*
 * DEFINE CONSTANTS
 */
$HashPassPhrase = "passpharse";
$HashSalt = "saltvalue";
$HashAlgorithm = "SHA1";
$HashIterations = "2";
$InitVector = "1a2b3c4d5e6f7g8h";        // Must be 16 bytes
$keySize = "256";

class Cipher {
    private $securekey, $iv;
    function __construct($textkey) {
        $this->securekey = hash($HashAlgorithm,$textkey,TRUE);
        $this->iv = $InitVector;
    }
    function encrypt($input) {
        return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->securekey, $input, MCRYPT_MODE_CBC, $this->iv));
    }
    function decrypt($input) {
        return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->securekey, base64_decode($input), MCRYPT_MODE_CBC, $this->iv));
    }
}

$cipher = new Cipher($HashPassPhrase);

$encryptedtext = $cipher->encrypt("Text To Encrypt");
echo "->encrypt = $encryptedtext<br />";

$decryptedtext = $cipher->decrypt($encryptedtext);
echo "->decrypt = $decryptedtext<br />";

var_dump($cipher);

?>

Digestive answered 17/8, 2010 at 17:59 Comment(0)
L
17

You need to derive the key from the pass phrase the same way as the C# code does in the PasswordDeriveBytes. This is documented to do PBKDF1 key derivation, as per RFC2898:

This class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0 standard to derive bytes suitable for use as key material from a password. The standard is documented in IETF RRC 2898.

there are PHP libraries that implement PBKDF1 out there, but is really simple to write one from scratch based ont he RFC:

PBKDF1 (P, S, c, dkLen)

Options: Hash
underlying hash function

Input: P
password, an octet string S salt, an eight-octet string c iteration count, a positive integer dkLen intended length in octets of derived key, a positive integer, at most 16 for MD2 or MD5 and 20 for SHA-1

Output: DK derived key, a dkLen-octet string

Steps:

  1. If dkLen > 16 for MD2 and MD5, or dkLen > 20 for SHA-1, output
     "derived key too long" and stop.

  2. Apply the underlying hash function Hash for c iterations to the
     concatenation of the password P and the salt S, then extract
     the first dkLen octets to produce a derived key DK:

               T_1 = Hash (P || S) ,
               T_2 = Hash (T_1) ,
               ...
               T_c = Hash (T_{c-1}) ,
               DK = Tc<0..dkLen-1>

  3. Output the derived key DK.

Updated

When you find youself in this situation, you usually search for an example implementaiton that shows the values at every step. for instance the one at http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf:

Password = "password" 
         = (0x)70617373776F7264
Salt     = (0x)78578E5A5D63CB06
Count    = 1000
kLen     = 16
Key      = PBKDF1(Password, Salt, Count, kLen)
         = (0x)DC19847E05C64D2FAF10EBFB4A3D2A20

P || S = 70617373776F726478578E5A5D63CB06
T_1=     D1F94C4D447039B034494400F2E7DF9DCB67C308
T_2=     2BB479C1D369EA74BB976BBA2629744E8259C6F5
...
T_999=   6663F4611D61571068B5DA168974C6FF2C9775AC
T_1000=  DC19847E05C64D2FAF10EBFB4A3D2A20B4E35EFE
Key=     DC19847E05C64D2FAF10EBFB4A3D2A20

So now lets write a PHP function that does this:

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

Now you can see the errs of your ways: you did not specify the all important raw=true parameter to sha1. Lets see what is our function output:

$HashPassPhrase = pack("H*","70617373776F7264");
$HashSalt = pack("H*","78578E5A5D63CB06"); 
$HashIterations = 1000; 
$devkeylength = 16; 
$devkey = PBKDF1($HashPassPhrase,$HashSalt,$HashIterations,$devkeylength);
echo 'Key:' . bin2hex(substr($devkey, 0, 8)) . '<br/>';
echo 'IV:' . bin2hex(substr($devkey, 8, 8)) .'<br/>';
echo 'Expected: DC19847E05C64D2FAF10EBFB4A3D2A20<br/>';

this output exactly the expected result:

Key:dc19847e05c64d2f
IV:af10ebfb4a3d2a20
Expected: DC19847E05C64D2FAF10EBFB4A3D2A20

Next, we can validate that the C# function does the same:

            byte[] password = Encoding.ASCII.GetBytes("password");
            byte[] salt = new byte[] { 0x78, 0x57, 0x8e, 0x5a, 0x5d, 0x63, 0xcb, 0x06};

            PasswordDeriveBytes pdb = new PasswordDeriveBytes(
                password, salt, "SHA1", 1000);

            byte[] key = pdb.GetBytes(8);
            byte[] iv = pdb.GetBytes(8);

            Console.Out.Write("Key: ");
            foreach (byte b in key)
            {
                Console.Out.Write("{0:x} ", b);
            }
            Console.Out.WriteLine();

            Console.Out.Write("IV: ");
            foreach (byte b in iv)
            {
                Console.Out.Write("{0:x} ", b);
            }
            Console.Out.WriteLine();

this produces the very same output:

Key: dc 19 84 7e 5 c6 4d 2f
IV: af 10 eb fb 4a 3d 2a 20

QED

bonus explanation

Please don't do crypto if you don't know exactly what you're doing. Even after you get the PHP implementaiton correct, your posted C# code has some serious problems. You are mixing byte arrays with stirng representing hex dumps, you use a hard coded IV instead of deriving it from the passphrase and salt, is just overall plain wrong. Please use an off-the shelf encryption scheme, like SSL or S-MIME and do not re-invent your own. You will get it wrong.

Leid answered 17/8, 2010 at 18:15 Comment(9)
Thanks for the reply, I think I understand what you are trying to say here, but not enough to actually put it into use. Is there possible fully coded example?Digestive
This is the PHP function I have come up with for the PBKDF1 $HashPassPhrase = "passpharse"; $HashSalt = "saltvalue"; $HashIterations = 2; $devkeylength = 32; $devkey = PBKDF1($HashPassPhrase,$Hashsalt,$HashIterations,$devkeylength); function PBKDF1($pass,$salt,$count,$dklen) { $t = sha1($pass.$salt); for($i=1; $i < $count; $i++) { $t = sha1($t); } $t = substr($t,0,$dklen-1); return $t; } But I don't think I am getting the right results.Digestive
// warning: no checking on $dklen, must be 0..20 function PBKDF1($pass,$salt,$count,$dklen) { $t = $pass.$salt;; for($i=0; $i < $count; $i++) { $t = sha1($t, true); } return substr($t,0,$dklen); } Beaird
@RemusRusanu maybe, but the function takes offset, lengthBeaird
Checked my PBKDF2 code against test vectors, it will of course still fail if the character encoding is e.g. 2 bytes while the other party uses 1 byte encoding.Beaird
@owlstead: You're right, actually my very own example shows the PHP output as ending in 4a3d2a while the C# example ends in 4a 3d 2a 20... Fixed it. I guess I was carried away by the dklen-1 in the RFC and shut my brain..Leid
If I got a nickel for each "off by one" mistake I made... :)Beaird
IMPORTANT NOTE: 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). See here for more information.Beaird
IMPORTANT NOTE #2: it will even repeat bytes, which means your IV is leaking key information.Beaird
R
4

It looks like your main problem is that you're using PHP's hash() in place of the PasswordDeriveBytes() step on the C# side. Those two methods are not equivalent. The latter implements the PBKDF1 password derivation algorithm, while hash() is just a hash. It looks like PEAR might have a PBKDF1 implementation, but otherwise you might have to write it yourself.

You also need to make sure your text encoding is consistent on both sides, if you haven't already.

Finally, you should consider not doing what you're doing because cryptography is harder than it looks. Since you're using HTTP, you can make use of the SSL protocol in lieu of writing your own. This will net you far better security and less hassle on low-level details like keeping incremental IVs in sync and whatnot.

Ruby answered 17/8, 2010 at 18:16 Comment(3)
Thanks for this reply. Yes I realized the main problem is with the PasswordDeriveBytes(). Are you saying, that passing a clear text password over HTTPS with SSL is more secure than encoding it on one side, and decoding it on the other?Digestive
@Derek Armstrong: yes! SSL is designed to protect things like plaintext passwords, just as your scheme is. The main difference is that SSL has been reviewed and revised by the best cryptographers on the planet for the last 15 years. I reckon it'll also be a lot easier to use, especially since you're already talking over HTTP.Ruby
I've added a note to Remus answer as well, PasswordDeriveBytes is an inconsistent, undocumented proprietary scheme for all bytes requested from it over the maximum output of PBKDF1 (20 bytes).Beaird
T
0

Is there a good reason why you can't just use http://php.net/manual/en/function.mcrypt-module-open.php and use rijndael-256 as the algorithm????

Tragic answered 17/8, 2010 at 18:21 Comment(2)
That module is even more low-level than the one he's already got!Ruby
This function has been DEPRECATED as of PHP 7.1.0 and REMOVED as of PHP 7.2.0Sturdivant
H
0

Check OpenSSL routines in PHP, they should be able to handle what you need to do.

Hallett answered 23/8, 2010 at 15:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.