Search code examples
c#phpencryptionrijndaelmanaged

Decrypt C# Encryption with PHP


Some time ago I implemented a C# web API to serve information.
This information is encrypted and is consumed by other either C# or Classic ASP websites.
Tis is how I am doing the encryption / decryption using the methods EncryptRijndael and DecryptRijndael.

using System;
using System.Text;

namespace Encryption_Test
{
    using System.IO;
    using System.Security.Cryptography;
    using System.Text.RegularExpressions;
    using System.Windows.Forms;

    class RijndaelManagedEncryption
    {
        //http://www.codeproject.com/Tips/704372/How-to-use-Rijndael-ManagedEncryption-with-Csharp

        #region Rijndael Encryption

        /// <summary>
        /// Encrypt the given text and give the byte array back as a BASE64 string
        /// </summary>
        /// <param name="text" />The text to encrypt
        /// <param name="salt" />The pasword salt
        /// <returns>The encrypted text</returns>
        public static string EncryptRijndael(string text, string salt, string inputKey)
        {
            if (string.IsNullOrEmpty(text))
                throw new ArgumentNullException("text");

            var aesAlg = NewRijndaelManaged(salt, inputKey);

            var blockSize = aesAlg.BlockSize;

            var strK = System.Text.Encoding.ASCII.GetString(aesAlg.Key);
            string s = strK;

            var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
            var msEncrypt = new MemoryStream();
            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            using (var swEncrypt = new StreamWriter(csEncrypt))
            {
                swEncrypt.Write(text);
            }

            return Convert.ToBase64String(msEncrypt.ToArray());
        }
        #endregion

        #region Rijndael Dycryption
        /// <summary>
        /// Checks if a string is base64 encoded
        /// </summary>
        /// <param name="base64String" />The base64 encoded string
        /// <returns>
        public static bool IsBase64String(string base64String)
        {
            base64String = base64String.Trim();
            return (base64String.Length%4 == 0) &&
                   Regex.IsMatch(base64String, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);

        }

        /// <summary>
        /// Decrypts the given text
        /// </summary>
        /// <param name="cipherText" />The encrypted BASE64 text
        /// <param name="salt" />
        /// <param name="inputKey"></param>
        /// The pasword salt
        /// <returns>De gedecrypte text</returns>
        public static string DecryptRijndael(string cipherText, string salt, string inputKey)
        {
            if (string.IsNullOrEmpty(cipherText))
                throw new ArgumentNullException("cipherText");

            if (!IsBase64String(cipherText))
                throw new Exception("The cipherText input parameter is not base64 encoded");

            string text;

            var aesAlg = NewRijndaelManaged(salt, inputKey);
            var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
            var cipher = Convert.FromBase64String(cipherText);

            using (var msDecrypt = new MemoryStream(cipher))
            {
                using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (var srDecrypt = new StreamReader(csDecrypt))
                    {
                        text = srDecrypt.ReadToEnd();
                    }
                }
            }
            return text;
        }
        #endregion

        #region NewRijndaelManaged

        /// <summary>
        /// Create a new RijndaelManaged class and initialize it
        /// </summary>
        /// <param name="salt" />
        /// <param name="inputKey"></param>
        /// The pasword salt
        /// <returns>
        private static RijndaelManaged NewRijndaelManaged(string salt, string inputKey)
        {
            if (salt == null) throw new ArgumentNullException("salt");
            var saltBytes = Encoding.ASCII.GetBytes(salt);
            var key = new Rfc2898DeriveBytes(inputKey, saltBytes);

            var aesAlg = new RijndaelManaged();
            aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);  //256 / 8 = 32
            aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8); //128 / 8 = 16
            //string k = System.Text.Encoding.Default.GetString(aesAlg.Key);
            //string i = System.Text.Encoding.Default.GetString(aesAlg.IV);
            //string l = k + i;

            #region testPHP
            ///*
            // So it would seem the week point in the chain for PHP is the Rfc2898DeriveBytes
            // */
            //aesAlg.Key = Encoding.UTF8.GetBytes(inputKey);
            //aesAlg.IV = Encoding.UTF8.GetBytes(salt);

            //k = System.Text.Encoding.Default.GetString(aesAlg.Key);
            //i = System.Text.Encoding.Default.GetString(aesAlg.IV);
            //l = k + i;
            #endregion testPHP

            return aesAlg;
        }
        #endregion
    }
}

You can see the commented out when towards the end where I just set the Key and IV from the supplied parameters by just converting those to byte[]. That seems OK for PHP but I'd rather not omit the Rfc2898DeriveBytes.

It works just fine and the consuming sites are able to decrpt the information.

Now here is my (well someone else's problem but I want to help), a PHP site now needs to consume my Web API. They seem unable to do it. They site that it is due to the way the IV is created.

Now this makes me wonder if

  1. They are not up to the job
  2. My implementation has made it impossible for them to do it.

Now I know very little about PHP but can generally follow the flow of a block of its code. I'd appreciate it if anyone could firstly tell me if it SHOULD be possible to achieve the goal with PHP, and if yes, maybe some pointers on how to do so.

Note - this is utilizing Rfc2898DeriveBytes which I belie is the crux of the issue and DISTINGUISHES this question from others similar.

An example

  • String to Encrypt : Co-operation is the key to success!
  • Salt: This_is_the_password_salt
  • Input key: This_is_the_input_key
  • Encrypted string: pLgIEjhNGDMfI0IynoAdbey3NKbOJzgUzYAlU14OWOpuZy7/lr7HRtFhiRKfjbZz

Solution

  • Well - after finding a sandbox that could take hash_hmac I seem to have sussed it out spurred on by you guys and your comments......

    Using this site.

    and the following code in it (I just hope it behaves the same in a real situation)

    <?php
    
    class Foo {
    
    public function decrypt_full($key, $iv, $encrypted)
    {
    $dev = $this->pbkdf2("sha1", $key, $iv, 1000, 48, true);
    $derived_key = substr($dev, 0, 32); //Keylength: 32
    $derived_iv = substr($dev, 32, 16); // IV-length: 16
    return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $derived_key, base64_decode($encrypted), MCRYPT_MODE_CBC, $derived_iv);
    }
    
    private function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
    {
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
    die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
    die('PBKDF2 ERROR: Invalid parameters.');
    
    $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;
    }
    return substr($output, 0, $key_length);
    }
    
    }
    //###########################################################################################
    $encrypted = "pLgIEjhNGDMfI0IynoAdbey3NKbOJzgUzYAlU14OWOpuZy7/lr7HRtFhiRKfjbZz";
    $iv = "This_is_the_password_salt";
    $key = "This_is_the_input_key";
    
    $foo = new foo;
    echo "<br/>";
    echo "Encrypted String: ".$encrypted."<br/>";
    echo "Decrypted string: ".$foo->decrypt_full($key, $iv, $encrypted )."<br/>";
    ?>
    

    The output is...

    Key: .g���13f^sI>M��j$\�+�od�mY# �!
    IV: �2]��&y�q� WJ��
    Decrypted: Co-operation is the key to success!
    

    Can't wait to tell the PHP guys ;)