locked
Encryption/Decryption C# <--> PHP

    Question

  • Hi,

     

    I need to send/receive encrypted text to/from a webpage using WebClient with a url like:

    mydomain.com/system.php?val=encrypted_text

     

    1) My C# app creates the encrypted text like this:

     

    string s = "Hello World!";
    string salt = "salt";
    string key = "test";

    byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(s); PasswordDeriveBytes pdb = new PasswordDeriveBytes(key, salt); byte[] encryptedData = Encrypt(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16)); string encrypted_text = Convert.ToBase64String(encryptedData);

     

     

    2) The webpage gets the encrypted_text and decrypts it before processing:

    $key = 'test';
    // $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB), MCRYPT_RAND); // len = 32
    $iv = '12345678901234567890123456789012'; // hard coded version for testing

    function Decrypt( $encrypted, $key, $iv ) { return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CFB, $iv); }

    $encrypted = $_GET['val'];
    $s = Decrypt( $encrypted, $key, $iv ); // $s = should be "Hello World!"

     

    3) This webpage outputs some encrypted text:

     

    $s = 'Good job!';
    
    function Encrypt( $s, $key, $iv )
    {
    	return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $s, MCRYPT_MODE_CFB, $iv);
    }
    echo Encrypt( $s, $key, $iv );

     


    4) And my C# app would get it and decrypt it:

     

    string encryptedText = GetEncryptedTextFromWeb();
    
    byte[] cipherBytes = Convert.FromBase64String(encryptedText);
    PasswordDeriveBytes pdb = new PasswordDeriveBytes(key, salt);
    byte[] decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));
    string decryptedText = System.Text.Encoding.Unicode.GetString(decryptedData);

     

    I've read a lot on the web, and I'm even more confused than before I started.

    * I know I'm mixing encryption methods, but I have no idea which ones and/or which ones to use.

    * My php has an iv. How do I put that in my C#?

     

    Or do you have any suggestion?

     

    Thanks


    Thanks, Andy
    Wednesday, July 06, 2011 1:35 PM

Answers

  • And the exception is ? Please never talk about an exception without telling what it is...

    It's unclear if the encryption/decyption part is correct and you have now a WebcClient issue or if you are just mixing unrelated issues.

    If the encryption/decryption part is still what you are working on, my approach would be likely :
    - to encrypt/decrypt a test string using PHP to verify it works fine
    - to encrypt the same string using C# and see If I get the same value (seems you won't need this part but this is mainly to check that the algo gives the same encrypted string including a possible base64 conversion than the PHP encryption)
    - once the above is ok, decryption should be quite easy

    Do that just using a console or Windows app for example. The goal is to make sure that encryption/Decrption works before adding WebClient to that.

    Possible issues I see are  that PHP uses the CFB mode (http://msdn.microsoft.com/en-us/library/system.security.cryptography.ciphermode.aspx) and I'm not sure if the resulting string is already byte64 encoded or if you need do that explicitely. If you want someone else to test you could tell which base64 encrypted string you have and what is its decrypted value with the test key and iv you showed us.

    If this is a webclient issue, it's likely knowing the exception you get could help.

     

     


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    • Marked as answer by Andy in tw Friday, July 08, 2011 3:54 PM
    Wednesday, July 06, 2011 5:24 PM

All replies

  • Hi,

    According to the documentation it seems that PasswordDerivedBytes is based on a "PBKDF1" algorithm. As decryption is done using "rijndael" it is likely you'll have to use rather http://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged.aspx to perform the C# encryption.

    You could likely start by encrypting a test string using PHP page that does the encryption and then you'll be able to check on the C# side wether you get the same source string when you try to decrypt it...


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".
    Wednesday, July 06, 2011 2:10 PM
  • Thanks Patrice,

    1) Yes, I copied the wrong C# code, sorry.

    I've checked out your link, Patrice, and it works. Great! Thank you.

     

    If, I parse "s" to be encrypted into bytes, and then I parse those encrypted bytes to be decrypted back into a string:

    byte[] encrypted = EncryptBytes(s);
    string roundtrip = DecryptBytes(encrypted);
    

    Success!

     

    However, the WebClient receives and gets a string so I need to convert the bytes to a string:

    string encrypted = Encrypt(s, true);
    string roundtrip = Decrypt(encrypted, true);
    
    
    public string Encrypt(string s, bool rijndael)
    {
      if (s == "") return "";
      byte[] encrypted = EncryptBytes(s);
      Return Encoding.UTF8.GetString(encrypted);
    }
    
     public string Decrypt(string encryptedText, bool rijndael)
    {
      UTF8Encoding enc = new UTF8Encoding();
      byte[] encryptedBytes = enc.GetBytes(encryptedText); 
      return DecryptBytes(encryptedBytes);
    }

     

    However, I get a "A first chance exception of type 'System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll" when sr.ReadToEnd:

    rm = new RijndaelManaged();
    rm.Key = key; // global var and are set
    rm.IV = iv; // global var and are set
    ICryptoTransform decryptor = rm.CreateDecryptor(rm.Key, rm.IV);
    MemoryStream ms = new MemoryStream(encryptedBytes);
    CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
    StreamReader sr = new StreamReader(cs);
    sr.ReadToEnd();

    // EndOfStream = 'sr.EndOfStream' threw an exception of type 'System.Security.Cryptography.CryptographicException'

    Q) How do I convert the encrypted bytes to a string?

    Q) How can I decrypt a string?

    NB: My strings may contain Chinese characters.

     

    2) Yes again, my first step is to decrypt the PHP page with my C#.

     

    Any clues?


    Thanks, Andy
    Wednesday, July 06, 2011 3:24 PM
  • I've found how to convert bytes to/from a string correctly:

     

        public string Encrypt(string s, bool rijndael)
        {
          if (s == "") return "";
          byte[] encryptedBytes = EncryptBytes(s);
          return Convert.ToBase64String(encryptedBytes); // bytes to string
        }
    
        public string Decrypt(string encryptedText, bool rijndael)
        {
          if (encryptedText == "") return "";
          byte[] encryptedBytes = Convert.FromBase64String(encryptedText); 
          return DecryptBytes(encryptedBytes);
        }
    
    

     

    Now, I'm working out how to set the key and IV to match my PHP...


    Thanks, Andy
    Wednesday, July 06, 2011 4:02 PM
  • It seems I have my C# working well, and I can manually set the Key and IV parameters:

     

      public string IV
      {
       set { iv = ByteArray(value, 16); }
      }
      public string Key
      {
       set { key = ByteArray(value, 32); }
      }
      private byte[] ByteArray(string value, int size)
      {
       byte[] a = Convert.FromBase64String(value);
    
       if (a.Length != size)
       {
        byte[] aNew = new byte[size];
        int len = (a.Length > size ? size : a.Length);
        for (int i = 0; i < len; i++)
         aNew[i] = a[i];
        a = aNew;
       }
    
       return a;
      }
    
    

     


    However, reading an encrypted string from the web using WebClient.DownloadString(url) causes an excpetion. Here's the PHP that creates the text:

     

    $s = 'My text';
    
    $key = 'test';
    // $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB), MCRYPT_RAND); // len = 32
    $iv = '12345678901234567890123456789012'; // I need to set my own IV to match C#
    
    function Encrypt( $s, $key, $iv )
    {
    	return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $s, MCRYPT_MODE_CFB, $iv);
    }
    
    $s = Encrypt( $s, $key, $iv );
    echo $s;
    
    

     


    What can I do to make the PHP compatible with my C#?

     

    Thanks for your time and efforts.

     


    Thanks, Andy
    Wednesday, July 06, 2011 4:48 PM
  • And the exception is ? Please never talk about an exception without telling what it is...

    It's unclear if the encryption/decyption part is correct and you have now a WebcClient issue or if you are just mixing unrelated issues.

    If the encryption/decryption part is still what you are working on, my approach would be likely :
    - to encrypt/decrypt a test string using PHP to verify it works fine
    - to encrypt the same string using C# and see If I get the same value (seems you won't need this part but this is mainly to check that the algo gives the same encrypted string including a possible base64 conversion than the PHP encryption)
    - once the above is ok, decryption should be quite easy

    Do that just using a console or Windows app for example. The goal is to make sure that encryption/Decrption works before adding WebClient to that.

    Possible issues I see are  that PHP uses the CFB mode (http://msdn.microsoft.com/en-us/library/system.security.cryptography.ciphermode.aspx) and I'm not sure if the resulting string is already byte64 encoded or if you need do that explicitely. If you want someone else to test you could tell which base64 encrypted string you have and what is its decrypted value with the test key and iv you showed us.

    If this is a webclient issue, it's likely knowing the exception you get could help.

     

     


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    • Marked as answer by Andy in tw Friday, July 08, 2011 3:54 PM
    Wednesday, July 06, 2011 5:24 PM
  • Thank you Patrice,

     

    1) Yes, my C# can encrypt/decrypt well

    2) Yes, my PHP can encrypt/decrypt well

    3) Yes, I'm testing on very simple apps to eliminate other potential issues.

    4) The exception was "System.FormatException" because the encrypted string from the web wasn't the correct type when decrypted by my C#.

    5) My problem: PHP cannot encrypt/decrypt C#'s strings and vice verse.

     

    PHP:

    $s = 'Hello World!';
    $iv = '12345678901234567890123456789012';
    $key = 'test';

    encrypted string output: DNSqpKvnKWVojT62

     

    C#:

    string s = "Hello World!";
    string iv = "12345678901234567890123456789012";
    string key = "test";

    encrypted string output: krcbii2H3h0oWsmsKnOrPg==

     

    As we can see, the two output string are different.

    How can I tweak my php or C# to get the same output?


    Thanks, Andy
    Wednesday, July 06, 2011 7:04 PM
  • I'm using CBC mode for each:

    C#:

    rm = new RijndaelManaged();
    rm.Mode = CipherMode.CBC;

     

    PHP*:

    function Encrypt( $s, $key, $iv )
    {
        return base64_encode( mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $s, MCRYPT_MODE_CBC, $iv) );
    }
    function Decrypt( $encrypted, $key, $iv )
    {
        return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
    }

     

    *Using CBC changes output to: azNmDeEPgs6w6C7yL1JID5jA9Tn83G7kiyqO5RLtVPY=

    C#'s output is the same: krcbii2H3h0oWsmsKnOrPg==


    Thanks, Andy
    Wednesday, July 06, 2011 7:22 PM
  • I'm starting to give this a look. A first thought is that it could be perhaps a single byte vs double byte per character issue (.NET uses unicode while it seems php uses single byte encoded characters ?).

    Ok, I see in one of the first post that you are using UTF8. It could helped to see a complete and as short as possible code sample to start exactly from what you are doing right now. Also on the PHP side is it really base64 encoded ? I don't see this ?

    No result so far. So it seems that mcrypt uses base64 encoded string parameters and return an already base64 encoded string ?


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    Thursday, July 07, 2011 4:39 PM
  • I greatly appreciate your time Patrice,

     

    I've just got something working: using TripleDES. I've read that TripleDES isn't strong.

    Q1) Is Rijndael is better?

     

    Key points so far:

    * Your clue before about base64 in PHP was very helpful. This helped me get a string from PHP that looked like C#'s. From there, I could tweak my Key and IV  so both returned the same values.

    * An other issue is the decrypted string, in both PHP and C#, needs to have the \0 trimmed.

    C#: return s.Trim('\0');

    PHP: return rtrim($s, "\0");

    * I need UTF8 because I'll be passing Chinese characters to/from my PHP

    * PHP replaces '+' with ' ' [space] from the string when using $_GET['val];

     

    Q2) Now I have a new problem:

    TripleDES can create its own Key and IV when it's initialized. However, I have no idea how to get one of those as a string so I can pass it to the PHP. [I'll encrypt the Key before it's sent].

     

    I've tried:

    TripleDES tDes = TripleDES.Create();

    string iv = Encoding.ASCII.GetString(tDes.IV); // also tried with UTF8 and Unicode

    string iv = Convert.ToBase64String(tDes.IV); // this gives a nice string, but doesn't work.

    NB: My code always works [with either of the conversions above] if I set the Key and IV myself with a random string.

     

    Or is this a bad idea?

     

     

    C#:

     

     // Before passing the string to my url, I encode it like this:
    byte[] urlEncodedBytes = System.Text.Encoding.UTF8.GetBytes(val);
    string urlEncodedStr = Convert.ToBase64String(urlEncodedBytes);
    val = HttpUtility.UrlEncode(urlEncodedStr);

    // I did this because *sometimes* it didn't work. I read that we should Base64 our strings.<br/>
    // However, I later found that "+" are changed to " " [spaces]
    // when using $_GET to read the vars.
    // So, I probably don't need to encode to Base64?
    // My main Encrypting/Decrypting Code:

    // Encrypting
    tDes = TripleDES.Create();
    tDes.Mode = CipherMode.CBC;
    tDes.Padding = PaddingMode.Zeros;
    tDes.Key = keyBytes;
    tDes.IV = ivBytes;
    ms = new MemoryStream();
    using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
    using (StreamWriter sw = new StreamWriter(cs))
    sw.Write(s);
    return ms.ToArray();

    // Decrypting
    rm = TripleDES.Create();
    rm.Mode = CipherMode.CBC;
    rm.Padding = PaddingMode.Zeros;
    rm.Key = keyBytes;
    rm.IV = ivBytes;
    using (MemoryStream ms = new MemoryStream(encryptedBytes))
    using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
    using (StreamReader sr = new StreamReader(cs))
    s = sr.ReadToEnd();
    return s.Trim('\0');
    NB: I've used try, exception and other features to make it robust.

     

    PHP:

    $val = $_GET['val'];<br/>$val = urldecode($val);
    $val = base64_decode($val);
    ReplaceText( $val, ' ', '+' ); // sometimes '+' are replaced with ' '.<br/>Decrypt( $val );

    $cipher = mcrypt_module_open(MCRYPT_3DES, '', 'cbc', '');
    function Encrypt ( $s ) { if ($s == '') return ''; global $cipher, $key, $iv; // get the amount of bytes to pad $extra = 8 - (strlen($s) % 8); // add the zero padding if($extra > 0) for($i = 0; $i < $extra; $i++) $s .= "\0"; mcrypt_generic_init($cipher, $key, $iv); $encrypted = mcrypt_generic($cipher, $s); mcrypt_generic_deinit($cipher); return base64_encode($encrypted); } function Decrypt ( $encrypted ) { if ($encrypted == '') return ''; global $cipher, $key, $iv; mcrypt_generic_init($cipher, $key, $iv); $s = mdecrypt_generic($cipher, base64_decode($encrypted)); mcrypt_generic_deinit($cipher); return rtrim($s, "\0"); }


     


    Thanks, Andy


    Friday, July 08, 2011 8:57 AM
  • Not an expert but IMO it depends on which usage you have. In particular it seems that you'll "transmit the key". In this case you may want to use http://en.wikipedia.org/wiki/Public-key_cryptography. It allows to use a separate key for encyption/decryption (i.e. you don't have to transmit a key that could be used to do whatever an interceptor wants).

     


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".
    Friday, July 08, 2011 11:16 AM