Triple DES encryption on C# and PHP yields different results
Asked Answered
M

1

3

I'm writing a simple encryption system for logging in but I've got small issue. C# encrypt function:

public static string EncryptString(string Message, string Passphrase)
{
    byte[] Results;
    System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

    // Step 1. We hash the passphrase using MD5
    // We use the MD5 hash generator as the result is a 128 bit byte array
    // which is a valid length for the TripleDES encoder we use below

    MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
    byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));

    // Step 2. Create a new TripleDESCryptoServiceProvider object
    TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

    // Step 3. Setup the encoder
    TDESAlgorithm.Key = TDESKey;
    TDESAlgorithm.Mode = CipherMode.ECB;
    TDESAlgorithm.Padding = PaddingMode.PKCS7;

    // Step 4. Convert the input string to a byte[]
    byte[] DataToEncrypt = UTF8.GetBytes(Message);

    // Step 5. Attempt to encrypt the string
    try
    {
        ICryptoTransform Encryptor = TDESAlgorithm.CreateEncryptor();
        Results = Encryptor.TransformFinalBlock(DataToEncrypt, 0, DataToEncrypt.Length);
    }
    finally
    {
        // Clear the TripleDes and Hashprovider services of any sensitive information
        TDESAlgorithm.Clear();
        HashProvider.Clear();
    }

    // Step 6. Return the encrypted string as a base64 encoded string
    return Convert.ToBase64String(Results);
}

EncryptString("test", "123456") returns "Yjaqhc7RFds=".

The same code in php:

  <?php
    $key = "123456";
    function pkcs7_pad($text, $blocksize)
    {
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }

    $input = pkcs7_pad("test", 16);
    $key = md5(utf8_encode($key), true);
    $td = mcrypt_module_open('tripledes', '', 'ecb', '');
    $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
    mcrypt_generic_init($td, $key, $iv);
    $encrypted_data = mcrypt_generic($td, $input);
    mcrypt_generic_deinit($td);
    mcrypt_module_close($td);

    echo base64_encode($encrypted_data);
?>

returns "dybhiZYdKG8pNCgCFkbV6g=="? What am I doing wrong?

Melise answered 15/11, 2012 at 17:21 Comment(13)
Wait a minute, why are you creating an IV if you are using ECB? And what exactly is the purpose of this? Please don't tell me you're using it to encrypt passwords.Suggestion
I'm using it to encrypt paswords, but it is only part of many-steps crypting. If it is possible, I don't want to change C# code, I prefere making changes in php.Melise
If I may ask, why are you encrypting your passwords instead of hashing them?Suggestion
Cause I'm planning to encrypt the whole data (login,password,some extra infos) into one decryptable messageMelise
Which would mean that you (and by extension, anyone who manages to hack you) would have access to the plaintext password, right?Suggestion
only user of the app would have access to plaintext, but that mean that crackers too.Melise
Do users of the app really need access to plaintext?Suggestion
they must log in... I belive that blowfish + 3DES + some more will be enought to protect the app for a few weeksMelise
Are they logging into your application? If so, you don't need the plaintext password.Suggestion
The password/login isn't needed to be well protect, but extra infos are needed to. The sever response also should by encrypted in the same system (perhaps with another keys)Melise
It sounds like you are re-inventing a secure transport layer. What's wrong with just using SSL?Suggestion
You don't need to purchase SSL certificates. You can generate them yourself. The only drawback is that you aren't able to use the truststores that come with e.g. browsers and operating systems to perform the certificate chain validation. You may just use self signed certifices or - if you are really into it - create your own certificate PKI.Anastase
Can you just answer the questions ? I'm googling this page, searching a way to encrypt and decrypt with C# on a side and PHP on another. The question is a technical one, why asking other questions ?Gavan
S
8

You're running into this problem because Triple DES's key size is 168 bits (21 bytes), but MD5 generates hashes that are only 16 bytes (128 bits) long.

This means the key will have to extended to 168 bits so Triple DES can work. It turns out that this derivation from 128 bits to 168 bits works differently in C# than it does in PHP, so the key that's effectively used is different, resulting in different encrypted data.

Now you have two options:


Option 1: Pick a cipher that supports 128-bit keys outright

You can avoid all the problems related to key size differences if you use a cipher that supports 128-bit keys. This would require minimal changes to your code. You can use Rijndael (AES), for example.

C#: Change TripleDESCryptoServiceProvider to RijndaelManaged.
Everything else can stay pretty much the same. (Demo)

PHP: Use MCRYPT_RIJNDAEL_128 instead of tripledes (Demo):

function encrypt_pkcs7($str, $key)
{
    $key = md5(utf8_encode($key), true);
    $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
    $pad = $block - (strlen($str) % $block);
    $str .= str_repeat(chr($pad), $pad);

    $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
    return base64_encode($ciphertext);
}

echo encrypt_pkcs7('test', '123456');

Note that the effective key size of AES is larger than Triple DES. Although Triple DES' key is 168-bit long, it only offers 112 bits of security. I would pick this option if I were you.


Option 2: Use a 192-bit key instead of a 128-bit key

If you use a larger key than Triple DES actually uses, C# and PHP seem to agree on how to reduce it to 168 bits. You can do this by using a hash function like SHA-256, which generates a 256-bit hash and trim it to 192 bits (24 bytes):

C#: Use SHA256CryptoServiceProvider and Array.Copy to get a 192-bit key, and use that with the rest of your program: (Demo)

SHA256CryptoServiceProvider HashProvider = new SHA256CryptoServiceProvider();
byte[] temp = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
byte[] key = new byte[24];
Array.Copy(temp, key, 24);

PHP: Use hash() with SHA-256 and substr() to get the 192-bit key

function encrypt_pkcs7($str, $key)
{
    // derive 192-bit key using SHA-256
    $key = substr(hash('sha256', $key, true), 0, 24);
    $block = mcrypt_get_block_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
    $pad = $block - (strlen($str) % $block);
    $str .= str_repeat(chr($pad), $pad);

    $ciphertext = mcrypt_encrypt(MCRYPT_3DES, $key, $str, MCRYPT_MODE_ECB);
    return base64_encode($ciphertext);
}

echo encrypt_pkcs7('test', '123456');

It doesn't look like I can supply a 168-bit key to TripleDESCryptoServiceProvider, I don't know why.


Other considerations:

  • Consider using SSL; even if it's a self-signed cert. It beats re-inventing the wheel, a particularly dangerous task when cryptography is involved.

  • Consider using a mode of operation other than ECB (eg: CBC). Using ECB increases security risks. Read the Wikipedia article on block cipher modes of operation.

  • Unless you absolutely need to have a plaintext password (rarely the case), you should hash your passwords. Read this article on securing passwords.

  • Consider using a proper password-based key derivation function, like PBKDF2 instead of a general purpose hash function like MD5 or the SHA family. This will make cracking the key harder. For more information, read the article in the previous bullet point.

Suggestion answered 15/11, 2012 at 19:45 Comment(2)
Thank you very much. The best response I've ever seen on SO ;)Melise
@CharlieHopperson You're welcome. Now I'm off to figure out how C# extends its keys.Suggestion

© 2022 - 2024 — McMap. All rights reserved.