-1

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
Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
AntDC
  • 1,807
  • 14
  • 23
  • No this is not a duplicate. The one you refer to is not utilising Rfc2898DeriveBytes. And it would seem that this piece in particular is the issue. – AntDC Sep 01 '16 at 10:58
  • @RiggsFolly - please reopen – AntDC Sep 01 '16 at 11:19
  • You do not give enough information about _How you actually encrypt data_ so helping is almost impossible. You are also not the person with the issue so we cannot see the other guys attempt at de-crypting your data. So this question is kind of **un answerable** – RiggsFolly Sep 01 '16 at 11:46
  • I've now inlcuded the code rather than a link. – AntDC Sep 01 '16 at 12:25
  • 2
    Tell you what! You encrypt a know string using your C# code. Post the string you started with and the resultant encrypted string, Someone here might write some PHP to unencrypt it. That would at least prove that it was possible. You can then return to your user and tell them how to do it. _Does that sound like a plan_ – RiggsFolly Sep 01 '16 at 12:33
  • In Encrypt (), after creating 'aesAlg', you (a couple lines later) access aesAlg.IV without setting it so....where/when does it get set: is it a constant array defined somewhere in the Algo. class..or possibly computed via your salt and key (?) or something else? Do you know what an IV is/what it does? What does msdn say for the Algo class & the IV? – ABuckau Sep 01 '16 at 13:21
  • @RiggsFolly - thanks for the advice. I have been trying on PHP sandboxes and have been reading a couple of articles. Unfortunately I'm yet to find a sandbox that allows me to use "hash_hmac". It seems there are equivilents to Rfc2898DeriveBytes in PHP which utilise that function. – AntDC Sep 01 '16 at 13:21
  • @ABuckau - It does that in NewRijndaelManaged The key is derived from the Rfc2898DeriveBytes in that method. – AntDC Sep 01 '16 at 13:26
  • It is best not to use mcrypt, it is abandonware, has not been updated in years and does not support standard PKCS#7 (née PKCS#5) padding, only non-standard null padding that can't even be used with binary data. mcrypt had many outstanding [bugs](https://sourceforge.net/p/mcrypt/bugs/) dating back to 2003. Instead consider using [defuse](https://github.com/defuse/php-encryption) or [RNCryptor](https://github.com/RNCryptor), they provide a complete solution and are being maintained and is correct. – zaph Sep 01 '16 at 16:00

1 Answers1

0

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

AntDC
  • 1,807
  • 14
  • 23
  • 2
    The reason the output is displayes gibberish is because encrypted data is not a string, it is an array of seemingly 8-bit btes including values that have no printable representation and are not even legal encodings in many encoding systems such as UTF-8. This is why hexadecimal is generally used to display data and Base64 for transmitting data as a string. – zaph Sep 01 '16 at 16:06
  • 1
    What you are calling an IV `$iv` is really a salt for `pbkdf2()`. – zaph Sep 01 '16 at 16:11
  • It amazes me that people can just copy and paste code from the internet that "seems to work" when dealing with the security of their applications. How do you sleep at night? – Luke Joshua Park Sep 01 '16 at 21:02
  • You'd do well to guess the password salt and key. So I do sleep well :) – AntDC Sep 02 '16 at 07:11
  • Cheers @Zaph - noted – AntDC Sep 02 '16 at 07:22
  • The fact that you think the salt is something worth hiding is the least concerning part of your comment. – Luke Joshua Park Sep 02 '16 at 08:22
  • That's as may be - it changes from transaction to transaction, as does the key. I obviously don't have as much knowledge of encryption as you.....I thank you for you magnanimity and non judgemental comments. They are really helpful. – AntDC Sep 02 '16 at 09:12
  • Sorry if I seemed short. I don't mean to pass judgement, my apologies. Just the idea of copy-pasted encryption code makes me shiver. I'm sure you will test it well. Apologies again. – Luke Joshua Park Sep 02 '16 at 09:30