We help IT Professionals succeed at work.

Converting PHP triple des decrypt to c#

Don VonderBurg
on
2,389 Views
Last Modified: 2017-04-12
I have the following code written for a PHP page. I send it a text key and text encryption string and I get the correct answer. But when I attempt it in c# function I get an error stating "Specified key is not a valid size for this algorithm." I am banging my head against the wall on this. Any suggestions?

PHP code currently using that works:
function decrypt($str, $key)
{   
    $keysize = mcrypt_get_key_size(MCRYPT_3DES, MCRYPT_MODE_CFB);
    $enc = base64_decode($str);
    $k = hash(SHA256, $key, true);
    $iv_size = mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_CBC);
    $empty_iv = str_repeat(chr(0), $iv_size);
    $iv = mcrypt_encrypt(MCRYPT_3DES, substr($k, 0, $keysize), "", MCRYPT_MODE_CBC, $empty_iv);
    $dec = mcrypt_decrypt(MCRYPT_3DES, substr($k, 0, $keysize), $enc, MCRYPT_MODE_CFB, $iv);

    return $dec;

}

Open in new window


c# code that throws the error:
public static string Decrypt(string input, string key)
        {
            byte[] inputArray = Convert.FromBase64String(input);
            TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider();
            tripleDES.Key = UTF8Encoding.UTF8.GetBytes(key);
            tripleDES.Mode = CipherMode.CFB;
            tripleDES.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = tripleDES.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
            tripleDES.Clear();
            return UTF8Encoding.UTF8.GetString(resultArray);
        }

Open in new window

Comment
Watch Question

Most Valuable Expert 2011
Author of the Year 2014

Commented:
This is not really an answer, just an observation.  MCrypt has been efffectively dead for years.
http://php.net/manual/en/intro.mcrypt.php

Today an appropriate choice is OpenSSL.  Here is a PHP code snippet that shows both MCrypt and OpenSSL, in parallel concrete implementations of an encryption interface.
https://iconoun.com/demo/encrypt_decrypt_both.php
<?php // demo/encrypt_decrypt_both.php
/**
 * Show how to encrypt and decrypt information
 * with binary-safe (base64) transport over the internet
 *
 * http://php.net/manual/en/book.openssl.php
 *
 * https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
 * https://en.wikipedia.org/wiki/Base64
 *
 * https://bugs.php.net/bug.php?id=67304
 *
 * Parallel construction in the encrypt() decrypt() methods
 */
error_reporting(E_ALL);


/**
 * The Interface defines the two main data transformation activities
 */
Interface Encryption_Interface
{
    public function encrypt($text, $key);
    public function decrypt($text, $key);
}


class Mcrypt Implements Encryption_Interface
{
    protected $cipher;
    protected $mode;

    public function __construct($cipher = MCRYPT_RIJNDAEL_256, $mode = MCRYPT_MODE_ECB)
    {
        $this->cipher = $cipher;
        $this->mode   = $mode;
    }

    public function encrypt($text, $key)
    {
        $data = mcrypt_encrypt($this->cipher, $key, $text, $this->mode);
        return base64_encode($data);
    }

    public function decrypt($text, $key)
    {
        $text = base64_decode($text);
        $data = mcrypt_decrypt($this->cipher, $key, $text, $this->mode);
        return $data;
    }
}


class SSLCrypt Implements Encryption_Interface
{
    protected $cipher_algorithm;
    protected $digest_method;
    protected $vector_length;

    public function __construct($cipher_algorithm = 'aes-256-ctr', $digest_method = 'sha256')
    {
        $this->cipher_algorithm = $cipher_algorithm;
        $this->digest_method = $digest_method;

        if (!in_array($cipher_algorithm, openssl_get_cipher_methods(TRUE))) {
            throw new \Exception(__METHOD__ . " Unknown cipher $cipher_algorithm");
        }

        if (!in_array($digest_method, openssl_get_md_methods(TRUE))) {
            throw new \Exception(__METHOD__ . " Unknown digest $digest_method");
        }

        $this->ivector_length = openssl_cipher_iv_length($cipher_algorithm);
    }

    public function encrypt($text, $key)
    {
        $keyhash = openssl_digest($key, $this->digest_method, TRUE);
        $ivector = mcrypt_create_iv($this->ivector_length, MCRYPT_DEV_URANDOM);
        $crypted = openssl_encrypt($text, $this->cipher_algorithm, $keyhash, OPENSSL_RAW_DATA, $ivector);
        if ($crypted === FALSE) {
            throw new \Exception(__METHOD__ . ' Failed: ' . openssl_error_string());
        }

        // RETURN THE IV AND THE ENCRYPTED DATA
        return base64_encode($ivector . $crypted);
    }

    public function decrypt($text, $key)
    {
        $keyhash = openssl_digest($key, $this->digest_method, TRUE);
        $rawdata = base64_decode($text);
        if (strlen($rawdata) < $this->ivector_length) {
            throw new \Exception(__METHOD__ . ' Data is too short');
        }

        // SEPARATE THE IV AND THE ENCRYPTED DATA
        $ivector = substr($rawdata, 0, $this->ivector_length);
        $rawtext = substr($rawdata, $this->ivector_length);
        $decrypt = openssl_decrypt($rawtext, $this->cipher_algorithm, $keyhash, OPENSSL_RAW_DATA, $ivector);

        if ($decrypt === FALSE) {
            throw new \Exception(__METHOD__ . ' Failed: ' . openssl_error_string());
        }

        return $decrypt;
    }
}







// INSTANTIATE ENCRYPTION OBJECTS FROM THE CLASS
$m = new MCrypt;
$s = new SSLCrypt;

// INITIALIZE VARS FOR LATER USE IN THE HTML FORM
$m_encoded = $m_decoded = NULL;
$s_encoded = $s_decoded = NULL;
$m_secret  = !empty($_POST['m_secret']) ? $_POST['m_secret'] : NULL;
$s_secret  = !empty($_POST['s_secret']) ? $_POST['s_secret'] : NULL;

// IF ANYTHING WAS POSTED SHOW THE DATA
if (!empty($_POST))
{
    if ($_POST['choice'] == 'mcr')
    {
        if (!empty($_POST["m_clearstring"]))
        {
            $m_encoded = $m->encrypt($_POST["m_clearstring"], $_POST["m_secret"]);
            echo "<br/>{$_POST["m_clearstring"]} YIELDS MCrypt ENCODED ";
            var_dump($m_encoded);
        }

        if (!empty($_POST["m_cryptstring"]))
        {
            $m_decoded = $m->decrypt($_POST["m_cryptstring"], $_POST["m_secret"]);
            echo "<br/>{$_POST["m_cryptstring"]} YIELDS MCrypt DECODED ";
            var_dump($m_decoded);
        }
    }

    if ($_POST['choice'] == 'ssl')
    {
        if (!empty($_POST["s_clearstring"]))
        {
            $s_encoded = $s->encrypt($_POST["s_clearstring"], $_POST["s_secret"]);
            echo "<br/>{$_POST["s_clearstring"]} YIELDS OpenSSL ENCODED ";
            var_dump($s_encoded);
        }

        if (!empty($_POST["s_cryptstring"]))
        {
            $s_decoded = $s->decrypt($_POST["s_cryptstring"], $_POST["s_secret"]);
            echo "<br/>{$_POST["s_cryptstring"]} YIELDS OpenSSL DECODED ";
            var_dump($s_decoded);
        }
    }
}
// CREATE THE FORM USING HEREDOC NOTATION
$form = <<<FORM

<form method="post">
<h3>Using MCrypt</h3>
<input type="hidden" name="choice" value="mcr" />
Secret Password (Key):
<br>
<input name="m_secret" value="$m_secret" autocomplete="off" />
<br>
<textarea name="m_clearstring">$m_decoded</textarea>
<input type="submit" value="ENCRYPT MCrypt" />
<br>
<textarea name="m_cryptstring">$m_encoded</textarea>
<input type="submit" value="DECRYPT MCrypt" />
</form>

<form method="post">
<h3>Using OpenSSL</h3>
<input type="hidden" name="choice" value="ssl" />
Secret Password (Key):
<br>
<input name="s_secret" value="$s_secret" autocomplete="off" />
<br>
<textarea name="s_clearstring">$s_decoded</textarea>
<input type="submit" value="ENCRYPT OpenSSL" />
<br>
<textarea name="s_cryptstring">$s_encoded</textarea>
<input type="submit" value="DECRYPT OpenSSL" />
<br>
</form>

FORM;

echo $form;

Open in new window

gr8gonzoConsultant
CERTIFIED EXPERT

Commented:
The error is pretty straightforward. Basically this line:

tripleDES.Key = UTF8Encoding.UTF8.GetBytes(key);

...is resulting in a key that is probably too large. My guess is that the UTF-8 encoding isn't returning the expected results. Try using ASCII encoding for this particular task:

tripleDES.Key = Encoding.ASCII.GetBytes(key);

If that still doesn't work, you might want to attach a debugger to the code, add a breakpoint on this line, and then check the resulting length of that byte array (the result of the GetBytes() call). Maybe something unintentional is in your key, like a line break or something.
Don VonderBurgPresident/CEO

Author

Commented:
gr8gonzo - I have tried changing the line to ASCII and still get the same error. The key is the same one I use in the PHP code that works. I can hand off the key which is 5 characters long and encrypted text to the PHP that looks something like "Rah0BJEi39qwAeICy6La4gE9jGNBggfnkeVx" and it will work.
gr8gonzoConsultant
CERTIFIED EXPERT

Commented:
Aha - I missed that tidbit about the key size in your original PHP code. Your original code doesn't use a 5-character key. It hashes those 5 characters using SHA-256 (into raw byte form) and then uses the first 16 bytes of the raw hash as your key.

So you'd need to do the same steps in your C# code.
gr8gonzoConsultant
CERTIFIED EXPERT

Commented:
It's also worth noting that you don't handle the IV in your C# code either. Once you have your 16-byte key, you'll need to use it to generate the IV.

According to your PHP code, you'll need to create a byte array contains 8 null bytes (8 bytes is the block size for 3DES). Then encrypt a single space character (using 3DES, your key, and then the 8-null-byte-array as your IV) and use THAT 8-byte result as your decryption IV.

I think that approach gives you the same IV every time, though, which weakens your encryption. It's why most people just generate a random IV at encryption -time and prepend it to your encrypted result instead. Then at decryption time, you just shift the first 8 bytes off of the beginning of the encrypted bytes and you have your IV.
gr8gonzoConsultant
CERTIFIED EXPERT

Commented:
I haven't heard any updates on this in a while. If you're still struggling, some code to help:

To get the raw byte array for SHA-256 in C#, first add these "using" statements:
using System.Security.Cryptography;
using System.IO;

Open in new window

...then this code should help you fill in the blanks:
 // Generate the key from your 5-character string
string originalKey = "Hello!";
byte[] hashed_key = new SHA256Managed().ComputeHash(new MemoryStream(Encoding.UTF8.GetBytes(originalKey))).Take(16).ToArray();

// Build the IV
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
tdes.Key = hashed_key;
tdes.Mode = CipherMode.CBC;
tdes.IV = new byte[8];
byte[] encrypted_iv = tdes.CreateEncryptor().TransformFinalBlock(new byte[0], 0, 0);
tdes.Clear();

// Now when decrypting, set:
// tripleDES.IV = encrypted_iv;
// tripleDES.Key = hashed_key;

Open in new window

Don VonderBurgPresident/CEO

Author

Commented:
yes I am struggling still. Thanks for the code example. Now I am getting a different error.

"Length of the data to decrypt is invalid."

It throws at:

byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);

Open in new window


The weird thing is the inputArray is 27. So I get the same error when I change the inputArray.Length to 27 or any number less than that.

More head banging.
Consultant
CERTIFIED EXPERT
Commented:
This problem has been solved!
(Unlock this solution with a 7-day Free Trial)
UNLOCK SOLUTION
Don VonderBurgPresident/CEO

Author

Commented:
You led me in the right direction.

Also another 3rd party provided at /n software found a solution at the same time. Since I am a red carpet subscriber there I use many of their tools.  The IPWorks!Encrypt tool is what I wound up using.