1

I am trying to convert a php aes encryption logic to c# but not able to make it work as per the server endpoint.

Can any one help in conversion on below php code to c#?

I have tried php to.net migration assistant but it failed near the encryption function.

PHP code:

    class Security
{
    public static function encrypt($input, $key)
    {
        $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
        $input = Security::pkcs5_pad($input, $size);
        $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
        $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        mcrypt_generic_init($td, $key, $iv);
        $data = mcrypt_generic($td, $input);
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        // $data = utf8_encode(base64_encode($data));

        $data = base64_encode($data);
        return $data;
    }


        private static function pkcs5_pad ($text, $blocksize)
    {
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }

    public static function decrypt($sStr, $sKey)
    {
        $decrypted= mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $sKey, base64_decode(str_replace(" ","+",$sStr)), MCRYPT_MODE_ECB);
        $dec_s = strlen($decrypted);
        $padding = ord($decrypted[$dec_s-1]);
        $decrypted = substr($decrypted, 0, -$padding);
        return $decrypted;
    }
}

c# code

public static String EncryptJava(String plainText, String key)
        {
            UTF8Encoding UTF8 = new UTF8Encoding();
            AesManaged tdes = new AesManaged();
            tdes.Key = UTF8.GetBytes(key);
            tdes.Mode = CipherMode.ECB;
            tdes.Padding = PaddingMode.PKCS7;
            ICryptoTransform crypt = tdes.CreateEncryptor();
            byte[] plain = Encoding.UTF8.GetBytes(plainText);
            byte[] cipher = crypt.TransformFinalBlock(plain, 0, plain.Length);
            return Convert.ToBase64String(cipher);
        }
2
  • There are tonnes of questions on StackOverflow that already outline the solution to this problem. mcrypt doesn't support PKCS5 padding. It's also been deprecated for years. The encryption code you have is very insecure, also. I wouldn't recommend using it in production. In some cases, it will be trivial to break and retrieve the plaintext. Commented Jul 11, 2018 at 8:30
  • @LukeJoshuaPark, yes i have checked similar solutions but the encryption output is not accepted by the api server. If you can point to a specific link that would help. I have actually multiple versions of the c# encode but none of them worked. The php code is actually provided by the solution provider. I have the java version also but conversion from that also did not help Commented Jul 11, 2018 at 9:05

2 Answers 2

2

The code you want should be:

public static string Encrypt(string plainText, string key)
{
    using (var enc = new RijndaelManaged())
    {
        byte[] key2 = Encoding.UTF8.GetBytes(key);
        // Rijndael supports keys of 16, 24, 32 byte long
        Array.Resize(ref key2, key2.Length <= 16 ? 16 : key2.Length <= 24 ? 24 : 32);
        enc.Key = key2; 
        enc.Mode = CipherMode.ECB;
        enc.Padding = PaddingMode.PKCS7;

        using (ICryptoTransform crypt = enc.CreateEncryptor())
        {
            byte[] plain = Encoding.UTF8.GetBytes(plainText);
            byte[] cipher = crypt.TransformFinalBlock(plain, 0, plain.Length);
            return Convert.ToBase64String(cipher);
        }
    }
}

Note that PHP will select the exact type of Rijnadael (128, 192, 256) based on the key size, padding with \0 the key to length 16, 24, 32.

To decrypt:

public static string Decrypt(string cipherText, string key)
{
    using (var enc = new RijndaelManaged())
    {
        byte[] key2 = Encoding.UTF8.GetBytes(key);
        // Rijndael supports keys of 16, 24, 32 byte long
        Array.Resize(ref key2, key2.Length <= 16 ? 16 : key2.Length <= 24 ? 24 : 32);
        enc.Key = key2;
        enc.Mode = CipherMode.ECB;
        enc.Padding = PaddingMode.PKCS7;

        using (ICryptoTransform crypt = enc.CreateDecryptor())
        {
            byte[] cipher = Convert.FromBase64String(cipherText);
            byte[] plain = crypt.TransformFinalBlock(cipher, 0, cipher.Length);
            return Encoding.UTF8.GetString(plain);
        }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

No luck with this also. Server still not accepting.
@Satyajit Did various tests, with various keys/data and always obtained the same result. Are you using text (string in c#) or binary data (byte[] in C#) for $input and $key? While it isn't important in PHP, it is important in .NET. If you can produce a $input and $key that have different result between PHP and C# then I can try to fix it.
You are right, the encryption output is matching now. Server is not accepting for some other reason i guess. Will look into that part next
Can you include the conversion for decoding from the php code? Tried decoding and got Padding is invalid and cannot be removed error
@Satyajit Added the decryptor
0

In C# you can use Streams (basically) and CryptoServiceProviders. This also adds the functionallity of PKCS7 padding mode.

Maybe this AES cryptographer class of mine will help you out.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Security;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.IO;

namespace BerndoJLib.Cryptography
{
    /// <summary>
    /// 
    /// A helper class that controls security encrypting and decrypting of the database.
    /// Contains the methods to encrypt and decrypt byte arrays.
    /// 
    /// </summary>
    /// <remarks>
    /// Author: berndoJ / Copyright 2018 Johannes Berndorfer
    /// Created: 13.03.2018 11:20:56
    /// </remarks>
    public class Cryptographer
    {
        #region Public Objects

        /// <summary>
        /// Sets the options of this cryptographer / the algorithm options
        /// </summary>
        public EncryptionOptions AlgorithmOptions { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor of this class.
        /// </summary>
        /// <param name="AlgorithmOptions">The options of this cryptographer instance</param>
        public Cryptographer(EncryptionOptions AlgorithmOptions)
        {
            this.AlgorithmOptions = AlgorithmOptions;
        }

        #endregion

        #region Methods

        /// <summary>
        /// A function to generate a new sequence to use as a salt for one encryption.
        /// The byte-length of this sequence is set by the <see cref="AlgorithmOptions"/> property.
        /// The random byte generator <see cref="RNGCryptoServiceProvider"/> is a good option to use in this case.
        /// </summary>
        /// <returns>The sequence of bytes in an array</returns>
        public byte[] GenerateNewSaltSequence()
        {
            byte[] SaltSequence = new byte[this.AlgorithmOptions.SaltByteLength];
            RNGCryptoServiceProvider RNGCsp = new RNGCryptoServiceProvider();
            RNGCsp.GetNonZeroBytes(SaltSequence);
            return SaltSequence;
        }

        /// <summary>
        /// This function derives a specified byte array (length spec) from a string key entered. The key does not have to be from a certain length.
        /// For maintaining security, the byte[] returned by this function should be set to zero if not used anymore.
        /// The method used to achieve this process is made available by the <see cref="Rfc2898DeriveBytes"/> class.
        /// The count of iterations of this process is set by the <see cref="AlgorithmOptions"/> property.
        /// </summary>
        /// <param name="Key">The key in the form of a SecureString</param>
        /// <param name="KeySize">The size (in bits) of the key generated</param>
        /// <param name="SaltSequence">The salt sequence used to hash the key</param>
        /// <returns>A key in form of a byte array derived from the string counterpart</returns>
        public byte[] DeriveKeyFromString(SecureString Key, int KeySize, byte[] SaltSequence)
        {
            /*DeriveBytes DrvBytes = new Rfc2898DeriveBytes(Key, SaltSequence, this.AlgorithmOptions.KeyGenerationIterations);
            return DrvBytes.GetBytes(KeySize >> 3);*/ // The >> 3 just gets the floored value of bytes.
            IntPtr StrPtr = Marshal.SecureStringToBSTR(Key);
            byte[] PwdByteArray = null;
            try
            {
                int StrLength = Marshal.ReadInt32(StrPtr, -4);
                PwdByteArray = new byte[StrLength];
                GCHandle Handle = GCHandle.Alloc(PwdByteArray, GCHandleType.Pinned);
                try
                {
                    for (int i = 0; i < StrLength; i++)
                        PwdByteArray[i] = Marshal.ReadByte(StrPtr, i);
                    using (Rfc2898DeriveBytes DrvBytes = new Rfc2898DeriveBytes(PwdByteArray, SaltSequence, this.AlgorithmOptions.KeyGenerationIterations))
                        return DrvBytes.GetBytes(KeySize >> 3);
                }
                finally
                {
                    Array.Clear(PwdByteArray, 0, PwdByteArray.Length);
                    Handle.Free();
                }
            }
            finally
            {
                Marshal.ZeroFreeBSTR(StrPtr);
            }
        }

        /// <summary>
        /// Encrypts the given data (in form of a byte[]) with the Key given. All the options and so on are defined by the <see cref="AlgorithmOptions"/> property.
        /// </summary>
        /// <param name="DataIn">The data given to the function in form of a byte array</param>
        /// <param name="Key">The key to encrypt the data</param>
        /// <returns>An EncryptedData object which contains IV, Salt and EncryptedData</returns>
        public EncryptedData Encrypt(byte[] DataIn, SecureString Key)
        {
            // EncryptedData context init
            EncryptedData EncryptedDataContext = new EncryptedData();
            EncryptedDataContext.Salt = this.GenerateNewSaltSequence();
            // CSP init
            using(AesCryptoServiceProvider AESCsp = new AesCryptoServiceProvider())
            {
                // Generate the IV and store it in the EncryptedData context.
                AESCsp.GenerateIV();
                EncryptedDataContext.IV = AESCsp.IV;
                // Derive the byte[] key from the string key
                AESCsp.Key = this.DeriveKeyFromString(Key, AESCsp.KeySize, EncryptedDataContext.Salt);
                // Init the CSP options
                AESCsp.Mode = this.AlgorithmOptions.CipherModeUsed;
                AESCsp.Padding = this.AlgorithmOptions.PaddingModeUsed;
                // Encryption code
                using (MemoryStream MemStrm = new MemoryStream(DataIn.Length))
                {
                    using (ICryptoTransform EncryptionTransformer = AESCsp.CreateEncryptor())
                    {
                        using (CryptoStream CryptoStrm = new CryptoStream(MemStrm, EncryptionTransformer, CryptoStreamMode.Write))
                        {
                            CryptoStrm.Write(DataIn, 0, DataIn.Length);
                            CryptoStrm.FlushFinalBlock();
                            EncryptedDataContext.EncryptedDataContent = MemStrm.ToArray();
                        }
                    }
                }
                // Disposing the key object in the AESCsp to reduce chance of acidentially leaving it in memory after the using closed AESCsp.
                Array.Clear(AESCsp.Key, 0, AESCsp.Key.Length);
                // Returning the EncryptedData
                return EncryptedDataContext;
            }
        }

        /// <summary>
        /// Decrypts the given encrypted data with the use of the given key.
        /// <para>
        /// Returns a tuple which contains the following information: If the decryption was successful (If the key was the right one) and the decrypted data if the decrpyton was in fact successful.
        /// </para>
        /// </summary>
        /// <param name="EncryptedDataIn">The data given to the decryption method</param>
        /// <param name="Key">The key to decrypt the data</param>
        /// <returns>A tuple with: The successfulness of the decryption (is key correct); The decrypted data;</returns>
        public Tuple<bool, byte[]> Decrypt(EncryptedData EncryptedDataIn, SecureString Key)
        {
            // Create a return tuple, Item1 is the successfulness, Item2 the decrypted data if the decryption was successful.
            Tuple<bool, byte[]> DecryptionResult = new Tuple<bool, byte[]>(false, null);
            try
            {
                using (AesCryptoServiceProvider AESCsp = new AesCryptoServiceProvider())
                {
                    // Init the IV
                    AESCsp.IV = EncryptedDataIn.IV;
                    // Init the AESCsp key object
                    AESCsp.Key = this.DeriveKeyFromString(Key, AESCsp.KeySize, EncryptedDataIn.Salt);
                    // Init the CSP options
                    AESCsp.Mode = this.AlgorithmOptions.CipherModeUsed;
                    AESCsp.Padding = this.AlgorithmOptions.PaddingModeUsed;
                    // Decryption code
                    using (MemoryStream MemStrm = new MemoryStream(EncryptedDataIn.EncryptedDataContent))
                    {
                        using (ICryptoTransform DecryptionTransformer = AESCsp.CreateDecryptor())
                        {
                            using (CryptoStream CryptoStrm = new CryptoStream(MemStrm, DecryptionTransformer, CryptoStreamMode.Read))
                            {
                                using (MemoryStream SequenceMemStrm = new MemoryStream())
                                {
                                    // Define a buffer to read sequences.
                                    byte[] SequenceBuffer = new byte[2048];
                                    // Init a variable that keeps track of how many bytes have been written to the buffer
                                    int ReadBytes;
                                    // Loop to read all the bytes in the CryptoStrm
                                    while((ReadBytes = CryptoStrm.Read(SequenceBuffer, 0, SequenceBuffer.Length)) > 0)
                                    {
                                        SequenceMemStrm.Write(SequenceBuffer, 0, ReadBytes);
                                    }
                                    // Set the DecryptionResult to: successful; Data in SequenceMemStrm;
                                    DecryptionResult = new Tuple<bool, byte[]>(true, SequenceMemStrm.ToArray());
                                }
                            }
                        }
                    }
                    // Disposing the key object in the AESCsp to reduce chance of acidentially leaving it in memory after the using closed AESCsp.
                    Array.Clear(AESCsp.Key, 0, AESCsp.Key.Length);
                    // Returning the DecryptionResult
                    return DecryptionResult;
                }
            }
            catch (Exception)
            {
                // The decryption failed, the key is not the right one.
                DecryptionResult = new Tuple<bool, byte[]>(false, null);
                return DecryptionResult;
            }
        }

        #endregion
    }

    /// <summary>
    /// 
    /// A struct obhject that defines a data set containing information of the salt, iv and the encrypted content.
    /// 
    /// </summary>
    /// <remarks>
    /// Author: berndoJ / Copyright 2018 Johannes Berndorfer
    /// Created: 13.03.2018 11:20:56
    /// </remarks>
    public struct EncryptedData
    {
        /// <summary>
        /// The initialization vector of the encryption
        /// </summary>
        public byte[] IV { get; set; }

        /// <summary>
        /// The salt used to genereate a key from a password string
        /// </summary>
        public byte[] Salt { get; set; }

        /// <summary>
        /// The encrypted data content of this struct
        /// </summary>
        public byte[] EncryptedDataContent { get; set; }
    }

    /// <summary>
    /// 
    /// A class that contains some options used for the encryption/decryption process.
    /// The objects in the class are readonly, set only when instanciating the class.
    /// 
    /// </summary>
    /// <remarks>
    /// Author: berndoJ / Copyright 2018 Johannes Berndorfer
    /// Created: 13.03.2018 11:20:56
    /// </remarks>
    public class EncryptionOptions
    {
        #region Presets

        /// <summary>
        /// The default encryption options for the cryptographer.
        /// </summary>
        public static readonly EncryptionOptions DEFAULT_OPTS = new EncryptionOptions(3000, 128, CipherMode.CBC, PaddingMode.PKCS7);

        #endregion

        #region Public Objects

        /// <summary>
        /// Defines how many iterations should be done when generating a key from a string password
        /// Standart is 3000 / Minimum is 1000
        /// </summary>
        public int KeyGenerationIterations { get; private set; }

        /// <summary>
        /// Defines the byte length of a standart generated salt.
        /// The minimum value is 64. Standart is 128
        /// </summary>
        public int SaltByteLength { get; private set; }

        /// <summary>
        /// Defines the cipher mode used.
        /// Standard is <see cref="CipherMode.CBC"/>
        /// </summary>
        public CipherMode CipherModeUsed { get; private set; }

        /// <summary>
        /// Defines the padding mode used in the process.
        /// Standard is <see cref="PaddingMode.PKCS7"/>
        /// </summary>
        public PaddingMode PaddingModeUsed { get; private set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor of this class.
        /// </summary>
        /// <param name="SaltGenerationIterations">Iterations when generating salt.</param>
        /// <param name="CipherModeUsed">The cipher mode used in the process.</param>
        /// <param name="PaddingModeUsed">The padding mode used in the process.</param>
        public EncryptionOptions(int KeyGenerationIterations, int SaltByteLength, CipherMode CipherModeUsed, PaddingMode PaddingModeUsed)
        {
            if (KeyGenerationIterations < 1000) throw new InvalidOperationException();
            if (SaltByteLength < 64) throw new InvalidOperationException();
            this.KeyGenerationIterations = KeyGenerationIterations;
            this.SaltByteLength = SaltByteLength;
            this.CipherModeUsed = CipherModeUsed;
            this.PaddingModeUsed = PaddingModeUsed;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the serialized version of an instance of this class.
        /// </summary>
        /// <returns>The serialized version of this class.</returns>
        public string GetSerialized()
        {
            // Init of SerializedString
            string SerializedString = "";
            // Key gen iterations
            SerializedString += $"KeyGenIterations:{this.KeyGenerationIterations.ToString()}";
            // Salt byte length
            SerializedString += $";SaltByteLength:{this.SaltByteLength.ToString()}";
            // Cipher mode
            string CipherModeStr = "";
            switch (this.CipherModeUsed)
            {
                case CipherMode.CBC:
                    CipherModeStr = "CBC";
                    break;
                case CipherMode.CFB:
                    CipherModeStr = "CFB";
                    break;
                case CipherMode.CTS:
                    CipherModeStr = "CTS";
                    break;
                case CipherMode.ECB:
                    CipherModeStr = "ECB";
                    break;
                case CipherMode.OFB:
                    CipherModeStr = "OFB";
                    break;
                default:
                    CipherModeStr = "CBC";
                    break;
            }
            SerializedString += $";CipherMode:{CipherModeStr}";
            // Padding mode
            string PaddingModeStr = "";
            switch (this.PaddingModeUsed)
            {
                case PaddingMode.ANSIX923:
                    PaddingModeStr = "ANSIX923";
                    break;
                case PaddingMode.ISO10126:
                    PaddingModeStr = "ISO10126";
                    break;
                case PaddingMode.None:
                    PaddingModeStr = "None";
                    break;
                case PaddingMode.PKCS7:
                    PaddingModeStr = "PKCS7";
                    break;
                case PaddingMode.Zeros:
                    PaddingModeStr = "Zeros";
                    break;
                default:
                    PaddingModeStr = "PKCS7";
                    break;
            }
            SerializedString += $";PaddingMode:{PaddingModeStr}";
            // Return
            return SerializedString;
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Deserializes a string to this class
        /// </summary>
        /// <param name="SerializedString">The serialized class</param>
        /// <returns>The class</returns>
        public static EncryptionOptions Deserialize(string SerializedString)
        {
            // Init of fields
            int KeyGenIterations = 3000;
            int SaltByteLength = 128;
            CipherMode CipherModeUsed = CipherMode.CBC;
            PaddingMode PaddingModeUsed = PaddingMode.PKCS7;
            // Init of strings
            try
            {
                string[] Components = SerializedString.Split(';');
                if (Components.Length == 4)
                {
                    // Key gen iterations
                    string Comp1 = Components[0];
                    string[] Comp1Vals = Comp1.Split(':');
                    KeyGenIterations = int.Parse(Comp1Vals[1]);
                    // Salt byte length
                    string Comp2 = Components[1];
                    string[] Comp2Vals = Comp2.Split(':');
                    SaltByteLength = int.Parse(Comp2Vals[1]);
                    // Cipher mode
                    string Comp3 = Components[2];
                    string[] Comp3Vals = Comp3.Split(':');
                    switch (Comp3Vals[1])
                    {
                        case "CBC":
                            CipherModeUsed = CipherMode.CBC;
                            break;
                        case "CFB":
                            CipherModeUsed = CipherMode.CFB;
                            break;
                        case "CTS":
                            CipherModeUsed = CipherMode.CTS;
                            break;
                        case "ECB":
                            CipherModeUsed = CipherMode.ECB;
                            break;
                        case "OFB":
                            CipherModeUsed = CipherMode.OFB;
                            break;
                        default:
                            CipherModeUsed = CipherMode.CBC;
                            break;
                    }
                    // Padding mode
                    string Comp4 = Components[3];
                    string[] Comp4Vals = Comp4.Split(':');
                    switch (Comp4Vals[1])
                    {
                        case "ANSIX923":
                            PaddingModeUsed = PaddingMode.ANSIX923;
                            break;
                        case "ISO10126":
                            PaddingModeUsed = PaddingMode.ISO10126;
                            break;
                        case "None":
                            PaddingModeUsed = PaddingMode.None;
                            break;
                        case "PKCS7":
                            PaddingModeUsed = PaddingMode.PKCS7;
                            break;
                        case "Zeros":
                            PaddingModeUsed = PaddingMode.Zeros;
                            break;
                        default:
                            PaddingModeUsed = PaddingMode.PKCS7;
                            break;
                    }
                }
            }
            catch (Exception) { }
            return new EncryptionOptions(KeyGenIterations, SaltByteLength, CipherModeUsed, PaddingModeUsed);
        }

        #endregion
    }
}

You can of course copy the cryptography part out of the class and modify it for your needs.

3 Comments

var encryptedData = new Cryptographer(new EncryptionOptions(1024, 128, System.Security.Cryptography.CipherMode.ECB, System.Security.Cryptography.PaddingMode.PKCS7)).Encrypt(System.Text.Encoding.UTF8.GetBytes(data), new NetworkCredential("", aesKey).SecurePassword); Is these parameters correct? Server did not accept this also
Parameters are correct. Also, if the NetworkCredential.SecurePassword gives a SecureString, this should work (tested it). What exactly did the server not accept? The code? The encrypted data? Please clarify
The encrypted data was not accepted by the third party api server

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.