Solved

Can't decrypt AES RIJNDAEL 256 In PHP?

Posted on 2014-09-08
17
1,759 Views
Last Modified: 2014-09-10
I am struggling to decrypt  AES RIJNDAEL 256 in PHP mycrypt

Output of code below:
1õ`d1„ÆÛ»›iE¸j † ÐG”œ$ è&“pÕÁ·sÿʤ)hç_×@û"9ص¸O„ªNºZÎÏ@•J׈²ï9ïx×Íç¥Ý’?ÝÙ-T´\Ü6«Á)ðVNÓ”¨[“Fÿ}qÐ`ò¤Q+³6ÞN

$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$key256 = '7RFZEZJ76rwiTzF3C0esOGKbiLVyutRp';
$iv =  'yfdmRJKZh6p2VkAs';

$token = "BbhHf5vkOwj7PGOmbiDCkVBo3KcYr+gcK9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA==";

mcrypt_generic_init($cipher, $key256, $iv);
$decrypted = mdecrypt_generic($cipher, base64_decode($token));

echo $decrypted;

Open in new window


This is the .Net class that produced the encrypted base64 string:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace CCare.Security
{
    public class Encryption
    {
        #region Rijndael/AES Declaration - MMH AES Encryptions

        private static string Passphrase = "7RFZEZJ76rwiTzF3C0esOGKbiLVyutRp";
        private static int Passworditerations = 8;
        private static string Initialvector = "yfdmRJKZh6p2VkAs";         

        #endregion


        #region Rijndael/AES MMH encryption
        /// <summary>
        /// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and 
        /// decrypt data. As long as encryption and decryption routines use the same
        /// parameters to generate the keys, the keys are guaranteed to be the same.
        /// </summary>
        /// 
        public static string Encrypt(string plainText)
        {
            return AESEncrypt(plainText, Passphrase, "", Passworditerations, Initialvector);
        }

        public static string Decrypt(string cipherText)
        {

            return AESDecrypt(cipherText, Passphrase, "", Passworditerations, Initialvector);

        }
        public static string Encrypt(string plainText, string PasswordPhrase, string InitializationVector)
        {
            return AESEncrypt(plainText, PasswordPhrase, "", Passworditerations, InitializationVector);
        }

        public static string Decrypt(string cipherText, string PasswordPhrase, string InitializationVector)
        {

            return AESDecrypt(cipherText, PasswordPhrase, "", Passworditerations, InitializationVector);

        }

        /// <summary>
        /// Encrypts specified plaintext using Rijndael symmetric key algorithm
        /// and returns a base64-encoded result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be encrypted.
        /// </param>
        /// <param name="passPhrase">
        /// Passphrase from which a pseudo-random password will be derived. The
        /// derived password will be used to generate the encryption key.
        /// </param>
        /// <param name="saltValue">
        /// Salt value used along with passphrase to generate password.
        /// </param>
        /// <param name="passwordIterations">
        /// Number of iterations used to generate password. One or two iterations
        /// should be enough.
        /// </param>
        /// <param name="initVector">
        /// Initialization vector (or IV). This value is required to encrypt the
        /// first block of plaintext data. For RijndaelManaged class IV must be 
        /// exactly 16 ASCII characters long.
        /// </param>
        /// <returns>
        /// Encrypted value formatted as a base64-encoded string.
        /// </returns>
        private static string AESEncrypt(string plainText, string passPhrase, string saltValue, int passwordIterations, string initVector)
        {
            try
            {
                // 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);

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

                // 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(getKeyBytes(passPhrase, saltValue, passwordIterations), 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;
            }
            catch //(Exception ex)
            {
                // ErrorLog(ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
        /// </summary>
        /// <param name="cipherText">
        /// Base64-formatted ciphertext value.
        /// </param>
        /// <param name="passPhrase">
        /// Passphrase from which a pseudo-random password will be derived. The
        /// derived password will be used to generate the encryption key.
        /// </param>
        /// <param name="saltValue">
        /// Salt value used along with passphrase to generate password.
        /// </param>
        /// <param name="passwordIterations">
        /// Number of iterations used to generate password. One or two iterations
        /// should be enough.
        /// </param>
        /// <param name="initVector">
        /// Initialization vector (or IV). This value is required to encrypt the
        /// first block of plaintext data. For RijndaelManaged class IV must be
        /// exactly 16 ASCII characters long.
        /// </param>
        /// <returns>
        /// Decrypted string value.
        /// </returns>
        /// <remarks>
        /// Most of the logic in this function is similar to the Encrypt
        /// logic. In order for decryption to work, all parameters of this function
        /// - except cipherText value - must match the corresponding parameters of
        /// the Encrypt function which was called to generate the
        /// ciphertext.
        /// </remarks>
        private static string AESDecrypt(string cipherText, string passPhrase, string saltValue, int passwordIterations, string initVector)
        {
            try
            {
                // Convert strings defining encryption key characteristics 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);

                // Convert our ciphertext into a byte array.
                byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

                // 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 decryptor from the existing key bytes and initialization 
                // vector. Key size will be defined based on the number of the key 
                // bytes.
                ICryptoTransform decryptor = symmetricKey.CreateDecryptor(getKeyBytes(passPhrase, saltValue, passwordIterations), initVectorBytes);

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

                // Define cryptographic stream (always use Read mode for encryption).
                CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);

                // Since at this point we don't know what the size of decrypted data
                // will be, allocate the buffer long enough to hold ciphertext;
                // plaintext is never longer than ciphertext.
                byte[] plainTextBytes = new byte[cipherTextBytes.Length];

                // Start decrypting.
                int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);

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

                // Convert decrypted data into a string. 
                // Let us assume that the original plaintext string was UTF8-encoded.
                string plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);

                // Return decrypted string.   
                return plainText;
            }
            catch //(Exception ex)
            {
                // ErrorLog(ex);
                return string.Empty;
            }
        }

        private static byte[] getKeyBytes(string passPhrase, string saltValue, int passwordIterations)
        {
            byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

            // 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,
                                                            "SHA1",
                                                            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(256 / 8);

            return keyBytes;
        }

        #endregion


    }
}

Open in new window

0
Comment
Question by:jwleys
  • 11
  • 3
  • 2
  • +1
17 Comments
 

Author Comment

by:jwleys
ID: 40311152
Should produce a plain text string
0
 
LVL 15

Expert Comment

by:ChloesDad
ID: 40311341
For comparing passwords, you shouldn't try and decrypt the stored password, but encrypt the entered password and compare the two encrypted strings.
0
 

Author Comment

by:jwleys
ID: 40311506
Really struggling with this one, I have another related question:
http://www.experts-exchange.com/Security/Encryption/Q_28513422.html
0
 

Author Comment

by:jwleys
ID: 40311507
I will consolidate and post what I have tried so far
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 40311700
I notice that the question is captioned AES RIJNDAEL 256 but the first line of PHP code says this:
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');

I think you would want to be consistent about that 128 vs 256 thing.  Here is a little script that demonstrates the two processes on the PHP side.  Hopefully you can use it to generate some test data that compares to the .NET
http://iconoun.com/demo/encrypt_decrypt.php

<?php // demo/encrypt_decrypt.php
error_reporting(E_ALL);

// REF: http://php.net/manual/en/ref.mcrypt.php
// REF: http://php.net/manual/en/mcrypt.ciphers.php
// NOTE PARALLEL CONSTRUCTION IN THE mcrypt_XXcrypt() FUNCTIONS

class Encryption
{
    protected $key;

    public function __construct($key='quay')
    {
        // THE KEY MUST BE KNOWN TO BOTH PARTS OF THE ALGORITHM
        $this->key = $key;
    }

    public function encrypt($text)
    {
        // ENCRYPT THE DATA
        $data = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->key, $text, MCRYPT_MODE_ECB);

        // MAKE IT base64() STRING SAFE FOR STORAGE AND TRANSMISSION
        return base64_encode($data);
    }

    public function decrypt($text)
    {
        // DECODE THE DATA INTO THE BINARY ENCRYPTED STRING
        $text = base64_decode($text);

        // DECRYPT THE STRING
        $data = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->key, $text, MCRYPT_MODE_ECB);

        // DECLOP NUL-BYTES BEFORE THE RETURN
        return trim($data);
    }
}

// INSTANTIATE AN ENCRYPTION OBJECT FROM THE CLASS
$c = new Encryption();

// INITIALIZE VARS FOR LATER USE IN THE HTML FORM
$encoded = $decoded = NULL;

// IF ANYTHING WAS POSTED SHOW THE DATA
if (!empty($_POST["clearstring"]))
{
    $encoded = $c->encrypt($_POST["clearstring"]);
    echo "<br/>{$_POST["clearstring"]} YIELDS ENCODED ";
    var_dump($encoded);
}

if (!empty($_POST["cryptstring"]))
{
    $decoded = $c->decrypt($_POST["cryptstring"]);
    echo "<br/>{$_POST["cryptstring"]} YIELDS DECODED ";
    var_dump($decoded);
}

// CREATE THE FORM USING HEREDOC NOTATION
$form = <<<FORM
<form method="post">
<input name="clearstring" value="$decoded" />
<input type="submit" value="ENCRYPT" />
<br/>
<input name="cryptstring" value="$encoded" />
<input type="submit" value="DECRYPT" />
</form>
FORM;

echo $form;

Open in new window

0
 

Author Comment

by:jwleys
ID: 40311893
Thanks Ray what you have provided is very helpful, this is my latest attempt closer but still not quite working:

Can't produce or decrypt:
BbhHf5vkOwj7PGOmbiDCkVBo3KcYr+gcK9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA==

<?php
error_reporting(0);
?>
<form action="trigger2.php" method="post" id="mainform" name="mainform" accept-charset="utf-8">
Text to encrypt (or key to decrypt):<input type="text" size="80" name="key" value="<?php if (isset($_POST['key'])) echo $_POST['key']; else echo '&un=medtech&pw=thvbxz4&fhpi=19BKBP&phpi=15BABA&gp=SamEntwistle&nzmc=A88984-3'; ?>"></br>
Passphrase:<input type="text" size="80" name="passphrase" value="<?php if (isset($_POST['passphrase'])) echo $_POST['passphrase']; else echo '7RFZEZJ76rwiTzF3C0esOGKbiLVyutRp'; ?>"></br>
Salt:<input type="text" size="80" name="salt" value="<?php if (isset($_POST['salt'])) echo $_POST['salt']; else echo ''; ?>"></br>
Iterations:<input type="text" size="80" name="iterations" value="<?php if (isset($_POST['iterations'])) echo $_POST['iterations']; else echo '8'; ?>"></br>
Init Vector:<input type="text" size="80" name="initvector" value="<?php if (isset($_POST['initvector'])) echo $_POST['initvector']; else echo 'yfdmRJKZh6p2VkAs'; ?>"></br>
Key Size:<input type="text" size="80" name="keysize" value="<?php if (isset($_POST['keysize'])) echo $_POST['keysize']; else echo '32'; ?>"></br>
<input type="submit" name="Encrypt" value="Encrypt"> <input type="submit" name="Decrypt" value="Decrypt">
</form>
<?php
function pbkdf2( $p, $s, $c, $kl, $a = 'sha1' ) {
$hl = strlen(hash($a, null, true)); # Hash length
$kb = ceil($kl / $hl); # Key blocks to compute
$dk = ''; # Derived key
# Create key
for ( $block = 1; $block <= $kb; $block ++ ) {
# Initial hash for this block
$ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);
# Perform block iterations
for ( $i = 1; $i < $c; $i ++ )
# XOR each iterate
$ib ^= ($b = hash_hmac($a, $b, $p, true));
$dk .= $ib; # Append iterated block
}
# Return derived key of correct length
return substr($dk, 0, $kl);
}
if (isset($_POST['key'])) {
// Make sure salt is 8 bytes length
$key = pbkdf2($_POST['passphrase'],$_POST['salt'], $_POST['iterations'], $_POST['keysize']);
//$text = "yamnuska"; // test plain text
$text = $_POST['key'];
$iv = $_POST['initvector'];
if(isset($_POST['Decrypt'])) {
// Use the output from above. This also works with Windows encrypted output and strips the padded characters
$encrypted = $text;
echo "Decrypted: <input type=\"text\" size=\"60\" value=\"".rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0")."\"><br/>";
} else {
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, $iv);
echo "Encrypted: <input type=\"text\" size=\"60\" value=\"".base64_encode($crypttext)."\"><br/>";
}
}
?>

Open in new window

0
 

Author Comment

by:jwleys
ID: 40311896
Found this GITHUB project which I have found helpful:
https://github.com/dchymko/.NET--PHP-encryption
0
 

Author Comment

by:jwleys
ID: 40311913
I am thinking that maybe what I am lacking is the SALT value which I need to confirm with the company I am trying to integrate with. As far as I can see from the .Net class example I have given the SALT value is simply an empty string ""
0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 
LVL 108

Expert Comment

by:Ray Paseur
ID: 40311987
What's the input (clear-text) string you're using for test data?
0
 
LVL 34

Accepted Solution

by:
gr8gonzo earned 500 total points
ID: 40312665
There are several things wrong here, but the fault is primarily in your C# implementation.

1. You shouldn't use the PasswordDeriveBytes class in C#. It's been obsolete for a while now, because it relies on insecure components. Instead, use Rfc2898DeriveBytes, which is the next generation of that class, and uses PBKDF2. PHP 5.5 has built-in support for PBKDF2, but if you run a lower version, you can always use the wrapper here:

https://defuse.ca/php-pbkdf2.htm

2. You're using a constant salt and IV. This is very insecure. Instead, generate a random IV and salt and pass it along in the message. This results in a random string every time, even if you encrypt the same message over and over again.

Here's a slightly different version of your C# implementation with some better practices:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace CCare.Security
{
    public class Encryption
    {
        #region Rijndael/AES Declaration - MMH AES Encryptions

        private static string Passphrase = "7RFZEZJ76rwiTzF3C0esOGKbiLVyutRp";
        private static int Passworditerations = 8;

        #endregion


        #region Rijndael/AES MMH encryption
        /// <summary>
        /// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and 
        /// decrypt data. As long as encryption and decryption routines use the same
        /// parameters to generate the keys, the keys are guaranteed to be the same.
        /// </summary>
        /// 
        public static string Encrypt(string plainText)
        {
            return AESEncrypt(plainText, Passphrase, Passworditerations);
        }

        public static string Decrypt(string cipherText)
        {

            return AESDecrypt(cipherText, Passphrase, Passworditerations);

        }
        public static string Encrypt(string plainText, string PasswordPhrase)
        {
            return AESEncrypt(plainText, PasswordPhrase,  Passworditerations);
        }

        public static string Decrypt(string cipherText, string PasswordPhrase)
        {

            return AESDecrypt(cipherText, PasswordPhrase, Passworditerations);

        }

        /// <summary>
        /// Encrypts specified plaintext using Rijndael symmetric key algorithm
        /// and returns a base64-encoded result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be encrypted.
        /// </param>
        /// <param name="passPhrase">
        /// Passphrase from which a pseudo-random password will be derived. The
        /// derived password will be used to generate the encryption key.
        /// </param>
        /// <param name="passwordIterations">
        /// Number of iterations used to generate password. One or two iterations
        /// should be enough.
        /// </param>
        /// <returns>
        /// Encrypted value formatted as a base64-encoded string.
        /// </returns>
        private static string AESEncrypt(string plainText, string passPhrase, int passwordIterations)
        {
            Console.WriteLine("===== ENCRYPT ======");
            try
            {
                // Set up AES-256 cipher
                RijndaelManaged myRijndael = new RijndaelManaged();
                myRijndael.BlockSize = 128;
                myRijndael.KeySize = 256;
                myRijndael.Mode = CipherMode.CBC;
                myRijndael.GenerateIV();

                // Generate a random, 8-byte salt
                byte[] randomSalt = new byte[8];
                RNGCryptoServiceProvider saltGen = new RNGCryptoServiceProvider();
                saltGen.GetBytes(randomSalt);

                // Set key
                Rfc2898DeriveBytes keygen = new Rfc2898DeriveBytes(passPhrase, randomSalt,passwordIterations);
                myRijndael.Key = keygen.GetBytes(32);

                // Encrypt and encode
                string cipherText = "";
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, myRijndael.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        using (StreamWriter sw = new StreamWriter(cs))
                        {
                            sw.Write(plainText);
                        }
                    }

                    // Create a block of data of IV + Salt + Encrypted Data
                    byte[] encData = ms.ToArray();
                    byte[] encFinal = new byte[myRijndael.IV.Length + randomSalt.Length + encData.Length];
                    Buffer.BlockCopy(myRijndael.IV, 0, encFinal, 0, myRijndael.IV.Length);
                    Buffer.BlockCopy(randomSalt, 0, encFinal, myRijndael.IV.Length, randomSalt.Length);
                    Buffer.BlockCopy(encData, 0, encFinal, myRijndael.IV.Length+randomSalt.Length, encData.Length);

                    // Just for debugging
                    Console.WriteLine("     IV = " + Convert.ToBase64String(myRijndael.IV));
                    Console.WriteLine("   Salt = " + Convert.ToBase64String(randomSalt));
                    Console.WriteLine("    Key = " + Convert.ToBase64String(myRijndael.Key));
                    Console.WriteLine("Message = " + Convert.ToBase64String(encData));

                    // Return message
                    cipherText = Convert.ToBase64String(encFinal);
                }

                // Return the encoded version
                return cipherText;
            }
            catch //(Exception ex)
            {
                // ErrorLog(ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
        /// </summary>
        /// <param name="cipherText">
        /// Base64-formatted ciphertext value.
        /// </param>
        /// <param name="passPhrase">
        /// Passphrase from which a pseudo-random password will be derived. The
        /// derived password will be used to generate the encryption key.
        /// </param>
        /// <param name="passwordIterations">
        /// Number of iterations used to generate password. One or two iterations
        /// should be enough.
        /// </param>
        /// <returns>
        /// Decrypted string value.
        /// </returns>
        /// <remarks>
        /// Most of the logic in this function is similar to the Encrypt
        /// logic. In order for decryption to work, all parameters of this function
        /// - except cipherText value - must match the corresponding parameters of
        /// the Encrypt function which was called to generate the
        /// ciphertext.
        /// </remarks>
        private static string AESDecrypt(string cipherText, string passPhrase, int passwordIterations)
        {
            Console.WriteLine("===== DECRYPT ======");
            try
            {
                // Convert our ciphertext into a byte array.
                byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

                // Set up AES-256 cipher
                RijndaelManaged myRijndael = new RijndaelManaged();
                myRijndael.BlockSize = 128;
                myRijndael.KeySize = 256;
                myRijndael.Mode = CipherMode.CBC;

                // Extract IV
                byte[] IV = new byte[16];
                Buffer.BlockCopy(cipherTextBytes, 0, IV, 0, 16);
                myRijndael.IV = IV;

                // Extract salt
                byte[] randomSalt = new byte[8];
                Buffer.BlockCopy(cipherTextBytes, 16, randomSalt, 0, 8);

                // Extract message
                byte[] encMessage = new byte[cipherTextBytes.Length - 24];
                Buffer.BlockCopy(cipherTextBytes, 24, encMessage, 0, encMessage.Length);

                // Set key
                Rfc2898DeriveBytes keygen = new Rfc2898DeriveBytes(passPhrase, randomSalt, passwordIterations);
                myRijndael.Key = keygen.GetBytes(32);

                // Just for debugging
                Console.WriteLine("     IV = " + Convert.ToBase64String(myRijndael.IV));
                Console.WriteLine("   Salt = " + Convert.ToBase64String(randomSalt));
                Console.WriteLine("    Key = " + Convert.ToBase64String(myRijndael.Key));
                Console.WriteLine("Message = " + Convert.ToBase64String(encMessage));

                // Encrypt and encode
                byte[] decMessage = new byte[encMessage.Length];
                int decBytes = 0;
                using (MemoryStream ms = new MemoryStream(encMessage))
                {
                    using (CryptoStream cs = new CryptoStream(ms, myRijndael.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        // Decrypt and get # of bytes decrypted
                        decBytes = cs.Read(decMessage, 0, encMessage.Length);
                    }

                    // Convert to a string
                    cipherText = Encoding.UTF8.GetString(decMessage, 0, decBytes);
                }

                // Return the encoded version
                return cipherText;
            }
            catch //(Exception ex)
            {
                // ErrorLog(ex);
                return string.Empty;
            }
        }


        #endregion


    }
}

Open in new window


And an accompanying PHP snippet:
<?php
// Passphrase and iterations, and the encoded message from C#
$passPhrase = "7RFZEZJ76rwiTzF3C0esOGKbiLVyutRp";
$iterations = 8;
$encoded = "ub5csOUOaOWawP6jp5y8AJDoTQ2OJvWlm0049LHQLxpbnpQyy3N5WA==";

// Decode message and extract IV, Salt, and Encrypted Message
$decoded = base64_decode($encoded);
$iv = substr($decoded,0,16);
$salt = substr($decoded,16,8);
$message = substr($decoded,24);

// Re-generate the key
$key = pbkdf2("sha1",$passPhrase, $salt, $iterations, 32, true);

// For debugging
echo "     IV = " . base64_encode($iv) . " (".strlen($iv).")\n";
echo "   Salt = " . base64_encode($salt) . " (".strlen($salt).")\n";
echo "    Key = " . base64_encode($key) . " (".strlen($key).")\n";
echo "Message = " . base64_encode($message) . " (".strlen($message).")\n";

// Decrypt
echo "Decrypted = " . mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $message, MCRYPT_MODE_CBC, $iv);

// From https://defuse.ca/php-pbkdf2.htm
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
    if($count <= 0 || $key_length <= 0)
        trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);

    if (function_exists("hash_pbkdf2")) {
        // The output length is in NIBBLES (4-bits) if $raw_output is false!
        if (!$raw_output) {
            $key_length = $key_length * 2;
        }
        return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
    }

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}
?>

Open in new window

0
 

Author Comment

by:jwleys
ID: 40313194
Here is the string I am trying to encrypt:
$cleartext = '&un=medtech&pw=thvbxz4&fhpi=19BKBP&phpi=15BABA&gp=SamEntwistle&nzmc=A88984-3';
It is basically a string of URL params.
0
 

Author Comment

by:jwleys
ID: 40313305
Thanks gr8gonzo,

I tried to use your sample to decrypt the Base64 encrypted string I have been provided:
BbhHf5vkOwj7PGOmbiDCkVBo3KcYr+gcK9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA==

This is the output I get:
IV = BbhHf5vkOwj7PGOmbiDCkQ== (16) Salt = UGjcpxiv6Bw= (8) Key = Ds4HOD1hd0DH29KBau3Tcbo6efmy0Itm3CuniHcwfAY= (32) Message = K9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA== (88) Decrypted = ± À”Oê™U¼ö'Ÿ8YÀY}*UùžÁK¦ù€OûÕ¥7“ÆÝ\ÓX_±Íýš:ÓùG§De€Òcê#ô· Ô˜£™Áø7Hý³´•Ë¿èõ1üÐ ê@°‰
0
 

Author Comment

by:jwleys
ID: 40313311
gr8gonzo thank you for suggestions about the C# class, unfortunately I have no control over the .Net implementation of this which is done by a 3rd party that we are trying to integrate with. But I will definitely relay your instructions on how to improve the C# class.
0
 
LVL 34

Expert Comment

by:gr8gonzo
ID: 40313350
Unfortunately, the .NET implementation uses PBKDF1 (PasswordDeriveBytes) with a SHA-1 algorithm and tries to return 32 bytes. The problem here is that a SHA-1 hash is 20 bytes long. So if you need 32 bytes, the PasswordDeriveBytes class basically runs an additional hash with a flipped bit in order to get the "extra" 12 bytes.

I don't know of a good way to do this in PHP. There might be someone out there who's figured out a pure PHP implementation of it. I know of a way to get the first 20 bytes (the keygen algorithm for PBKDF1 is pretty straightforward), but not entirely sure about the last 12 bytes.

Alternatively, if you can't change the C# code at all, then you -could- just hardcode the final resulting key, since it'll be the same every single time.
0
 
LVL 34

Expert Comment

by:gr8gonzo
ID: 40313363
For the record, if you're certain that's the exact code used by the 3rd party, I would suggest that you look for alternatives and tell them you're looking for alternatives due to that code. (In other words, put pressure on them to fix it ASAP).

Bad code can sometimes represent the quality of the rest of the coding, and when it comes to security, you should be careful about dealing with 3rd parties that cannot properly create secure code. That may be an indication that they don't have sufficient security elsewhere and could put your own data / information at risk and even lead to an attack on you.
0
 

Author Comment

by:jwleys
ID: 40316022
I am aware that this is a very hacky implementation and I have relayed that to my boss but she wants me to continue anyway :(

I am now able to decrypt :), the issue I am having now is producing an identical encrypted base64 encoded string the same as the .Net class. I believed it was a padding issue but I have outputted the padding of both the the string to be encrypted and the string that has been decrypted and they are the same.

Sample output:
String to be encrypted padding: 13
Decripted string padding: 13
Enc: 05b8477f9be43b08fb3c63a66e20c2915068dca718afe81c2bdb3045a0e6630ce78f655b359606181fa1eca37ec8ab9167c4bea74fa9fc7e8f14740d1c3f4a07acecc58665004f3117642231165b507db4955d62b1fa013ff3e62f7f4bba8398c3c1224a65eff6166631573842972bc4
Dec: &un=medtech&pw=thvbxz4&fhpi=19BKBP&phpi=15BABA&gp=SamEntwistle&nzmc=A88984-3&ts=2014-07-28:13:19:04
Encrypt base64: BbhHf5vkOwj7PGOmbiDCkVBo3KcYr+gcK9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA==
Re-Encrypt: ETeA7Ur0fzRDzwWpVOYpgkpfsvV6Ha5kS+vkZ9aWfnYH5UlfpcDgajP7gMG4/urGJfY4napsa5orL29jHjoAeMFHJad3Jh982QzfdrTjQCHZKwyz7u2pdh0pNYekmL4WbD+w67upvylcnArBwXQYDQ==

My very dirty code that I will be amending and cleaning up:

<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<?php
$plainTxt = "&un=medtech&pw=thvbxz4&fhpi=19BKBP&phpi=15BABA&gp=SamEntwistle&nzmc=A88984-3&ts=2014-07-28:13:19:04";
$encb64 = "BbhHf5vkOwj7PGOmbiDCkVBo3KcYr+gcK9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA==";

$pwd = "7RFZEZJ76rwiTzF3C0esOGKbiLVyutRp";
$salt = "";

$enc = base64_decode($encb64);
$decpad = Decrypt($enc, $pwd, $salt);
$plainTxt = padtxt($plainTxt);
$reenc = Encrypt($plainTxt, $pwd, $salt);
// Remove the padding
$pad = ord($decpad[($len = strlen($decpad)) - 1]);
echo "Decripted string padding: $pad<br />";
$dec = substr($decpad, 0, strlen($decpad) - $pad);

echo "Enc: " . bin2hex($enc) . "<br />";
echo "Dec: " . $dec . "<br />";
echo "Encrypt base64: " . $encb64 . "<br />";
echo "Re-Encrypt: " . $reenc = base64_encode(Encrypt($decpad, $pwd, $salt)) . "<br />";

function padtxt($value){
    $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $len = strlen($value);
    $padding = $block - (($len % $block));
    echo "String to be encrypted padding: $padding<br />";
    $value .= str_repeat(chr($padding),$padding);
    return $value;
}

function Decrypt($ciphertext, $password, $salt)
{
  $key = PBKDF1($password, $salt, 8, 32);
  $iv = 'yfdmRJKZh6p2VkAs'; //PBKDF1($password, $salt, 8, 16);

  // NB: Need 128 not 256 and CBC mode to be compatible
  return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext, MCRYPT_MODE_CBC, $iv);
}

function Encrypt($plainTxt, $password, $salt)
{
  $key = PBKDF1($password, $salt, 8, 32);
  $iv = 'yfdmRJKZh6p2VkAs'; //PBKDF1($password, $salt, 8, 16);
  return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plainTxt, MCRYPT_MODE_CBC, $iv);
}

?>

<?php
function PBKDF1($pass, $salt, $count, $cb)
{
  // This is very approximately the way that the Microsoft version of 
  // PasswordDeriveBytes works.

  ///
  /// !!!WARNING!!!
  ///
  // This is a BAD function!
  // Irrespective of the fact that the use of PBKDF1 is not recommended anyway.
  //
  // This really should be put into a class with a constructor taking the 
  // $pass, $salt and $count.
  // Then there should be a Reset() method to start from scratch each time a new pwd/salt is used.
  // And there should be a GetBytes(int) method to get the required info.
  // But for the sake of simplicity we are assuming the same pwd and salt for each call to 
  // this function. This will not stand up to any scrutiny!

  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 = "";

  // Check if we have any bytes left over from a previous iteration.
  // This is the way MS appears to do it. To me it looks very badly wrong
  // in the line: "$result = substr($extra, $rlen, $rlen);"
  // I'm sure it should be more like "$result = substr($extra, $extracount, $rlen);"
  // Mono have provided what looks like a fixed version at
  // https://github.com/mono/mono/blob/master/mcs/class/corlib/System.Security.Cryptography/PasswordDeriveBytes.cs
  // But I'm no cryptographer so I might be wrong.
  // But this seems to work for low values of $hashno and seems to work
  // with C# implementations.

  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; 
}
?>
</body>
</html>

Open in new window

0
 

Author Closing Comment

by:jwleys
ID: 40316210
I managed to convince my boss to change tactics and go with your suggested solution. The company we are integrating with have done the same and now it is working like a charm.

Thank you so much for all your help!!! :)
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

When the confidentiality and security of your data is a must, trust the highly encrypted cloud fax portfolio used by 12 million businesses worldwide, including nearly half of the Fortune 500.
Envision that you are chipping away at another e-business site with a team of pundit developers and designers. Everything seems, by all accounts, to be going easily.
The viewer will learn how to dynamically set the form action using jQuery.
The viewer will learn how to create and use a small PHP class to apply a watermark to an image. This video shows the viewer the setup for the PHP watermark as well as important coding language. Continue to Part 2 to learn the core code used in creat…

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now