Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I decrypt an encrypted MCRYPT_RIJNDAEL_256 value in C#, that was encrypted by mcrypt in PHP?

I am trying to read a Base64-Encoded value from a Database table managed on the Linux side. In that table there is a column called first_name. On the Linux side I can decrypt this easily by using the following command in PHP:

$data = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, "patient_fn_salt",
                       base64_decode("H6XmkH+VWvdD88THCliKJjLisGZIBk3CTNvyQMLnhpo="),
                       MCRYPT_MODE_ECB);

However I try as much as I can to duplicate this logic on the C# side and all I get is gibberish.

My C# code is below, i hope you have some suggestions because I ran out of ideas :(

byte [] cipherText =
         Convert.FromBase64String("H6XmkH+VWvdD88THCliKJjLisGZIBk3CTNvyQMLnhpo=");
byte [] key = Encoding.UTF8.GetBytes("patient_fn_salt");
Array.Resize(ref key, 32);
byte [] iv = new byte[32];

string fname = Utilities.Decrypt(cipherText, key, iv);


public static string Decrypt(byte[] cipherText, byte[] Key, byte[] IV)
  {
   // Check arguments.
   if (cipherText == null || cipherText.Length <= 0)
    throw new ArgumentNullException("cipherText");
   if (Key == null || Key.Length <= 0)
    throw new ArgumentNullException("Key");
   if (IV == null || IV.Length <= 0)
    throw new ArgumentNullException("Key");

   // TDeclare the streams used
   // to decrypt to an in memory
   // array of bytes.
   MemoryStream msDecrypt = null;
   CryptoStream csDecrypt = null;
   StreamReader srDecrypt = null;

   // Declare the AesManaged object
   // used to decrypt the data.
   RijndaelManaged rj = new RijndaelManaged();

   // Declare the string used to hold
   // the decrypted text.
   string plaintext = null;

   try
   {
    // Create an AesManaged object
    // with the specified key and IV.

    rj.Mode = CipherMode.ECB;
    rj.BlockSize = 256;
    rj.KeySize = 256;
    rj.Padding = PaddingMode.Zeros;

    rj.Key = Key;
    rj.GenerateIV();
    //rj.IV = IV;


    // Create a decrytor to perform the stream transform.
    ICryptoTransform decryptor = rj.CreateDecryptor(rj.Key, rj.IV);

    // Create the streams used for decryption.
    msDecrypt = new MemoryStream(cipherText);
    csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
    srDecrypt = new StreamReader(csDecrypt);

    // Read the decrypted bytes from the decrypting stream
    // and place them in a string.
    plaintext = srDecrypt.ReadToEnd();
   }
   finally
   {
    // Clean things up.

    // Close the streams.
    if (srDecrypt != null)
     srDecrypt.Close();
    if (csDecrypt != null)
     csDecrypt.Close();
    if (msDecrypt != null)
     msDecrypt.Close();

    // Clear the AesManaged object.
    if (rj != null)
     rj.Clear();
   }
   return plaintext;
  }
 }
like image 462
Chad Gladue Avatar asked Aug 16 '11 18:08

Chad Gladue


2 Answers

Post is old, but this might help somebody in a future. This function encrypt exactly like mcrypt_encrypt with parameters MCRYPT_RIJNDAEL_256 and MCRYPT_MODE_ECB

    static byte[] EncryptStringToBytes(string plainText, byte[] key)
    {
        if (plainText == null || plainText.Length <= 0)
            throw new ArgumentNullException("plainText");
        if (key == null || key.Length <= 0)
            throw new ArgumentNullException("key");

        byte[] encrypted;
        using (var rijAlg = new RijndaelManaged())
        {
            rijAlg.BlockSize = 256;
            rijAlg.Key = key;
            rijAlg.Mode = CipherMode.ECB;
            rijAlg.Padding = PaddingMode.Zeros;
            rijAlg.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
            using (var msEncrypt = new MemoryStream())
                using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (var swEncrypt = new StreamWriter(csEncrypt))
                        swEncrypt.Write(plainText);
                    encrypted = msEncrypt.ToArray();
                }
        }
        return encrypted;
    }

And here is function to decrypt it

     static string DecryptStringFromBytes(byte[] cipherText, byte[] key)
     {
        if (cipherText == null || cipherText.Length <= 0)
            throw new ArgumentNullException("cipherText");
        if (key == null || key.Length <= 0)
            throw new ArgumentNullException("key");

        string plaintext;
        using (var rijAlg = new RijndaelManaged())
        {
            rijAlg.BlockSize = 256;
            rijAlg.Key = key;
            rijAlg.Mode = CipherMode.ECB;
            rijAlg.Padding = PaddingMode.Zeros;
            rijAlg.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
            using (var msDecrypt = new MemoryStream(cipherText))
                using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    using (var srDecrypt = new StreamReader(csDecrypt))
                        plaintext = srDecrypt.ReadToEnd();
        }
        return plaintext;
    }
like image 100
Uriil Avatar answered Sep 20 '22 18:09

Uriil


As Paŭlo says, ECB mode does not use an IV. If C# insists on one then use all zero bytes.

The key "patient_fn_salt" is 15 characters, 120 bits. Your decryption function is expecting 256 bits of key. You need to be very sure that the extra bits are identical in both systems and are being added in the same place in both systems. Even a single bit wrong will result in garbage decryption. Read the PHP documentation very carefully to determine exactly how "patient_fn_salt" is expanded to a 256 bit key. In particular check if the actual key is SHA256("patient_fn_salt").

As an aside, ECB mode is insecure. Use either CTR mode or CBC mode in preference. CTR mode does not require padding so will probably mean less cyphertext to store.

ETA: on another read through I notice that the C# side is padding with zeros. What padding is the PHP side using? Zero padding is not a good idea, as it cannot recognise a faulty decryption. PKCS7 padding has a much better chance of recognising a faulty output. Best to explicitly specify the padding on both ends rather than rely on defaults.

like image 36
rossum Avatar answered Sep 22 '22 18:09

rossum