Link to home
Start Free TrialLog in
Avatar of Jeremy Leys
Jeremy LeysFlag for New Zealand

asked on

Encrypting in .Net decrypting in php

Hi,

I am trying to use mcrypt in PHP to decrypt (Rijndael/AES). I am integrating with a company that is using .Net. From the look of this .Net class they are using Rijndael/AES in CBC mode and 8 password iterations. I am looking for a good, easy to use PHP class that will do the equivalent of what this .Net class is doing for encrypting and decrypting.

Here is what I have experimented with in PHP naturally it will not give the same result as the .Net class as it is not using password iterations.

My PHP test:
<?php
function AESDecrypt($plain_txt){
	$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
	$key256 = '3De4gYipL2Fa4jnuIOPdb9CqWLnuoJcv';
	$iv =  'bf3TwIPaY2nXqe2k';

	mcrypt_generic_init($cipher, $key256, $iv);
	$encrypted = mcrypt_generic($cipher, $plain_txt);

	mcrypt_generic_init($cipher, $key256, $iv);
	$decrypted = mdecrypt_generic($cipher, $encrypted);
		
	echo "<br /><br />Encrypted: ".base64_encode($encrypted);
	echo "<br /><br />Decrypted: $decrypted";
}

AESDecrypt("testing, testing 123");

?>

Open in new window


For security reasons I have not given the actual Pass phrase and Initial vector but 32 character and 16 character place holders which should suffice. The output of the .Net class when encrypting looks something like this: BbhHf5vkOwj7PGOmbiDCkVBo3KcYr+gcK9swRaDmYwznj2VbNZYGGB+h7KN+yKuRZ8S+p0+p/H6PFHQNHD9KB6zsxYZlAE8xF2QiMRZbUH20lV1isfoBP/PmL39LuoOYw8EiSmXv9hZmMVc4QpcrxA==

Working .Net class code:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace CCare.Security
{
    public class Encryption
    {
        #region Rijndael/AES Declaration - MMH AES Encryptions

        private static string Passphrase = "3De4gYipL2Fa4jnuIOPdb9CqWLnuoJcv";
        private static int Passworditerations = 8;
        private static string Initialvector = "bf3TwIPaY2nXqe2k";         

        #endregion


        #region Rijndael/AES MMH encryption
        /// <summary>
        /// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and 
        /// decrypt data. As long as encryption and decryption routines use the same
        /// parameters to generate the keys, the keys are guaranteed to be the same.
        /// </summary>
        /// 
        public static string Encrypt(string plainText)
        {
            return AESEncrypt(plainText, Passphrase, "", Passworditerations, Initialvector);
        }

        public static string Decrypt(string cipherText)
        {

            return AESDecrypt(cipherText, Passphrase, "", Passworditerations, Initialvector);

        }
        public static string Encrypt(string plainText, string PasswordPhrase, string InitializationVector)
        {
            return AESEncrypt(plainText, PasswordPhrase, "", Passworditerations, InitializationVector);
        }

        public static string Decrypt(string cipherText, string PasswordPhrase, string InitializationVector)
        {

            return AESDecrypt(cipherText, PasswordPhrase, "", Passworditerations, InitializationVector);

        }

        /// <summary>
        /// Encrypts specified plaintext using Rijndael symmetric key algorithm
        /// and returns a base64-encoded result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be encrypted.
        /// </param>
        /// <param name="passPhrase">
        /// Passphrase from which a pseudo-random password will be derived. The
        /// derived password will be used to generate the encryption key.
        /// </param>
        /// <param name="saltValue">
        /// Salt value used along with passphrase to generate password.
        /// </param>
        /// <param name="passwordIterations">
        /// Number of iterations used to generate password. One or two iterations
        /// should be enough.
        /// </param>
        /// <param name="initVector">
        /// Initialization vector (or IV). This value is required to encrypt the
        /// first block of plaintext data. For RijndaelManaged class IV must be 
        /// exactly 16 ASCII characters long.
        /// </param>
        /// <returns>
        /// Encrypted value formatted as a base64-encoded string.
        /// </returns>
        private static string AESEncrypt(string plainText, string passPhrase, string saltValue, int passwordIterations, string initVector)
        {
            try
            {
                // Convert strings into byte arrays.
                // Let us assume that strings only contain ASCII codes.
                // If strings include Unicode characters, use Unicode, UTF7, or UTF8 
                // encoding.
                byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);

                // Convert our plaintext into a byte array.
                // Let us assume that plaintext contains UTF8-encoded characters.
                byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

                // Create uninitialized Rijndael encryption object.
                RijndaelManaged symmetricKey = new RijndaelManaged();

                // It is reasonable to set encryption mode to Cipher Block Chaining
                // (CBC). Use default options for other symmetric key parameters.
                symmetricKey.Mode = CipherMode.CBC;

                // Generate encryptor from the existing key bytes and initialization 
                // vector. Key size will be defined based on the number of the key 
                // bytes.
                ICryptoTransform encryptor = symmetricKey.CreateEncryptor(getKeyBytes(passPhrase, saltValue, passwordIterations), initVectorBytes);

                // Define memory stream which will be used to hold encrypted data.
                MemoryStream memoryStream = new MemoryStream();

                // Define cryptographic stream (always use Write mode for encryption).
                CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
                // Start encrypting.
                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);

                // Finish encrypting.
                cryptoStream.FlushFinalBlock();

                // Convert our encrypted data from a memory stream into a byte array.
                byte[] cipherTextBytes = memoryStream.ToArray();

                // Close both streams.
                memoryStream.Close();
                cryptoStream.Close();

                // Convert encrypted data into a base64-encoded string.
                string cipherText = Convert.ToBase64String(cipherTextBytes);

                // Return encrypted string.
                return cipherText;
            }
            catch //(Exception ex)
            {
                // ErrorLog(ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
        /// </summary>
        /// <param name="cipherText">
        /// Base64-formatted ciphertext value.
        /// </param>
        /// <param name="passPhrase">
        /// Passphrase from which a pseudo-random password will be derived. The
        /// derived password will be used to generate the encryption key.
        /// </param>
        /// <param name="saltValue">
        /// Salt value used along with passphrase to generate password.
        /// </param>
        /// <param name="passwordIterations">
        /// Number of iterations used to generate password. One or two iterations
        /// should be enough.
        /// </param>
        /// <param name="initVector">
        /// Initialization vector (or IV). This value is required to encrypt the
        /// first block of plaintext data. For RijndaelManaged class IV must be
        /// exactly 16 ASCII characters long.
        /// </param>
        /// <returns>
        /// Decrypted string value.
        /// </returns>
        /// <remarks>
        /// Most of the logic in this function is similar to the Encrypt
        /// logic. In order for decryption to work, all parameters of this function
        /// - except cipherText value - must match the corresponding parameters of
        /// the Encrypt function which was called to generate the
        /// ciphertext.
        /// </remarks>
        private static string AESDecrypt(string cipherText, string passPhrase, string saltValue, int passwordIterations, string initVector)
        {
            try
            {
                // Convert strings defining encryption key characteristics into byte
                // arrays. Let us assume that strings only contain ASCII codes.
                // If strings include Unicode characters, use Unicode, UTF7, or UTF8
                // encoding.
                byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);

                // Convert our ciphertext into a byte array.
                byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

                // Create uninitialized Rijndael encryption object.
                RijndaelManaged symmetricKey = new RijndaelManaged();

                // It is reasonable to set encryption mode to Cipher Block Chaining
                // (CBC). Use default options for other symmetric key parameters.
                symmetricKey.Mode = CipherMode.CBC;

                // Generate decryptor from the existing key bytes and initialization 
                // vector. Key size will be defined based on the number of the key 
                // bytes.
                ICryptoTransform decryptor = symmetricKey.CreateDecryptor(getKeyBytes(passPhrase, saltValue, passwordIterations), initVectorBytes);

                // Define memory stream which will be used to hold encrypted data.
                MemoryStream memoryStream = new MemoryStream(cipherTextBytes);

                // Define cryptographic stream (always use Read mode for encryption).
                CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);

                // Since at this point we don't know what the size of decrypted data
                // will be, allocate the buffer long enough to hold ciphertext;
                // plaintext is never longer than ciphertext.
                byte[] plainTextBytes = new byte[cipherTextBytes.Length];

                // Start decrypting.
                int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);

                // Close both streams.
                memoryStream.Close();
                cryptoStream.Close();

                // Convert decrypted data into a string. 
                // Let us assume that the original plaintext string was UTF8-encoded.
                string plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);

                // Return decrypted string.   
                return plainText;
            }
            catch //(Exception ex)
            {
                // ErrorLog(ex);
                return string.Empty;
            }
        }

        private static byte[] getKeyBytes(string passPhrase, string saltValue, int passwordIterations)
        {
            byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

            // First, we must create a password, from which the key will be derived.
            // This password will be generated from the specified passphrase and 
            // salt value. The password will be created using the specified hash 
            // algorithm. Password creation can be done in several iterations.
            PasswordDeriveBytes password = new PasswordDeriveBytes(
                                                            passPhrase,
                                                            saltValueBytes,
                                                            "SHA1",
                                                            passwordIterations);

            // Use the password to generate pseudo-random bytes for the encryption
            // key. Specify the size of the key in bytes (instead of bits).
            byte[] keyBytes = password.GetBytes(256 / 8);

            return keyBytes;
        }

        #endregion


    }
}

Open in new window

Avatar of Jeremy Leys
Jeremy Leys
Flag of New Zealand image

ASKER

Useful article on PHP manual talking about differences in .Net vs mycrypt (PHP)
http://php.net//manual/en/function.mcrypt-encrypt.php#47973
ASKER CERTIFIED SOLUTION
Avatar of btan
btan

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of btan
btan

Probably on seeing it with details here is another explanation..similar to prev posting on the fields prior to mcrypt decrypt function
http://www.codingvision.net/security/c-php-compatible-encryption-aes256

others suggested...such as "PhpSeclib <-> BouncyCastle RSA" and as of "PHP equivalent to Rijndael AES encryption/decryption in .Net?" it is not really advocated but thought of sharing the quote below
PHP offers two extensions that can, in principle, do the job. OpenSSL suffers from not being documented beyond function prototypes, and Mcrypt suffers from being an absolute minefield if you don't happen to know exactly what you're doing. I wouldn't use either if I could possibly get away with it.

If you do attempt this, you will need to implement authentication yourself. You will need to implement padding yourself. If you screw up, you will get no indication even if the library knows perfectly well it's been asked to do something absurd, for the most part it will (silently!) guess at what you meant and continue on (patches for much of this are available, but not yet in mainline).
Taking a look at that now thanks, I will keep you posted :)
This is helpful btan:
http://lukieb.blogspot.sg/2013/04/making-aes256-encryption-work-same-in.html

Unfortunately, the .NET implementation uses PBKDF1 (PasswordDeriveBytes) with a SHA-1 algorithm and tries to return 32 bytes. I am looking for examples of how to perform PBKDF1 in PHP.
pretty tough into the details as the crypto required the consistency in use of the padding byte (depends on pkcs5 standard v1/1.5/2 as well) , block size, key size, iv etc. And making it tougher using different lang and implementation libraries which you are not savvy of its internal - always good to make sure the encrypt and decrypt work for the same lang first before the interoperability testing btw different lang.

for pbkdf1 in php some consideration (pkdf1 - should be using pkcs5v1.5 as padding)
http://forums.devnetwork.net/viewtopic.php?f=1&t=120070

I saw separately in forum on discussing the interoperability too..see the part on "PBKDF1" - you may also try google "how-do-i-convert-this-c-sharp-rijndael-encryption-to-php"
http://forums.devnetwork.net/viewtopic.php?f=1&t=120060
I have updated my question here:
https://www.experts-exchange.com/questions/28514171/Can't-decrypt-AES-RIJNDAEL-256-In-PHP.html

I can now decrypt and I am trying to encrypt the same way as the .Net class
Thank you, I have found this to be extremely challenging and you have been a huge help. I ended up convincing my boss to move to pbkdf2.

Thank you so much time, this has been a steep learning curve me :)
noted thanks for sharing