Encrypt data with .NET, decrypt with OpenSSL

By | April 6, 2011

Here is a C# helper class that encrypts and decrypts data using the AES 128-bit algorithm. It can be used (after through testing, of course) to pass data between a .NET application and any other application using OpenSSL. Most of the credit belongs to Deusty blog. I have added a compatible Encrypt method. When encrypting, the method generates the key using the given password together with random ‘salt’, encrypts the data and finally prepends the salt value to the result block in a manner compatible with OpenSSL. Decrypting is pretty much the opposite – salt is extracted from the data buffer, and is used to generate the key the same way OpenSSL does.

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

namespace Tutorial
{
    ///
<summary> /// Utility class for AES-128 encryption
 /// Encryption is compatible with openssl, can be encrypted/decrypted with the following openssl commands:
 /// openssl enc [-a] -aes-128-cbc -pass pass:mypassword -in plaintextfile -out encryptedfile
 /// openssl enc [-a] -d -aes-128-cbc -pass pass:mypassword -in encryptedfile -out plaintextfile
 /// Inspired by:
 /// http://deusty.blogspot.com/2009/04/decrypting-openssl-aes-files-in-c.html
 /// http://www.gutgames.com/post/AES-Encryption-in-C.aspx
 /// </summary>
    public static class AESEncryption
    {
        private static byte[] randomBytes(int size)
        {
            byte[] array = new byte[size];
            new Random( ).NextBytes( array );
            return array;
        }

        ///
<summary> /// Encrypt a string
 /// </summary>
        ///Text to be encrypted
        ///Password to encrypt with
        /// An encrypted string encoded with Base64
        public static string Encrypt(string plainText, string password)
        {
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            byte [] encryptedBytes = Encrypt( plainTextBytes, password );
            return Convert.ToBase64String( encryptedBytes );
        }

        public static byte[] Encrypt(byte[] plainTextBytes, string password)
        {
            byte[] salt = randomBytes( 8 );
            // if salt is same during every encryption, same key is used. May be useful for testing, must not be used in production code
            //salt = new byte[8]; //set {0,0...}

            byte[] passwordBytes = Encoding.UTF8.GetBytes( password );

            MD5 md5 = MD5.Create( );

            int preKeyLength = password.Length + salt.Length;
            byte[] preKey = new byte[preKeyLength];

            Buffer.BlockCopy( passwordBytes, 0, preKey, 0, passwordBytes.Length );
            Buffer.BlockCopy( salt, 0, preKey, passwordBytes.Length, salt.Length );

            byte[] key = md5.ComputeHash( preKey );

            int preIVLength = key.Length + preKeyLength;
            byte[] preIV = new byte[preIVLength];

            Buffer.BlockCopy( key, 0, preIV, 0, key.Length );
            Buffer.BlockCopy( preKey, 0, preIV, key.Length, preKey.Length );

            byte[] iv = md5.ComputeHash( preIV );

            md5.Clear( );
            md5 = null;

            AesManaged aes = new AesManaged( );
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 128;
            aes.BlockSize = 128;
            aes.Key = key;
            aes.IV = iv;

            byte[] encrypted = null;

            using(ICryptoTransform Encryptor = aes.CreateEncryptor( ))
            {
                using(MemoryStream MemStream = new MemoryStream( ))
                {
                    using(CryptoStream CryptoStream = new CryptoStream( MemStream, Encryptor, CryptoStreamMode.Write ))
                    {
                        CryptoStream.Write( plainTextBytes, 0, plainTextBytes.Length );
                        CryptoStream.FlushFinalBlock( );

                        encrypted = MemStream.ToArray( );
                        CryptoStream.Close( );
                    }
                    MemStream.Close( );
                }
            }
            aes.Clear( );

            int resultLength = encrypted.Length + 8 + 8;
            byte[] salted = Encoding.UTF8.GetBytes( "Salted__" );
            byte[] result = new byte[resultLength];

            Buffer.BlockCopy( salted, 0, result, 0, salted.Length );
            Buffer.BlockCopy( salt, 0, result, 8, salt.Length );
            Buffer.BlockCopy( encrypted, 0, result, 16, encrypted.Length );

            return result;
        }

        private static bool IsDataEqual(byte[] a, int a_offset, byte[] b, int b_offset, int length)
        {
            if(a.Length - a_offset < length)
            {
                return false;
            }
            if(b.Length - b_offset < length)
            {
                return false;
            }
            for(int i = 0; i < length; i++) {
                 if(a[i + a_offset] != b[i + b_offset]) {                     
                      return false;
                 }             
            }
            return true; 
        }         
        
        public static string Decrypt(string encryptedBase64Text, string password) {
             byte[] encrypted = Convert.FromBase64String( encryptedBase64Text );             
             byte[] plaintext = Decrypt( encrypted, password );             
             return Encoding.UTF8.GetString( plaintext );         
        }

        public static byte [] Decrypt(byte [] encrypted, string password) {
            bool isSalted = false;             byte[] salt = null;                          
            if(encrypted.Length > 16)
            {
                byte[] salted = Encoding.UTF8.GetBytes( "Salted__" );

                if(IsDataEqual( encrypted, 0, salted, 0, 8 ))
                {
                    isSalted = true;

                    salt = new byte[8];
                    Buffer.BlockCopy( encrypted, 8, salt, 0, 8 );
                }
            }

            byte[] aesData;

            if(isSalted)
            {
                int aesDataLength = encrypted.Length - 16;
                aesData = new byte[aesDataLength];
                Buffer.BlockCopy( encrypted, 16, aesData, 0, aesDataLength );
            }
            else
            {
                salt = new byte[0];
                aesData = encrypted;
            }

            byte[] passwordBytes = Encoding.UTF8.GetBytes( password );

            MD5 md5 = MD5.Create( );

            int preKeyLength = passwordBytes.Length + salt.Length;
            byte[] preKey = new byte[preKeyLength];

            Buffer.BlockCopy( passwordBytes, 0, preKey, 0, passwordBytes.Length );
            Buffer.BlockCopy( salt, 0, preKey, passwordBytes.Length, salt.Length );

            byte[] key = md5.ComputeHash( preKey );

            int preIVLength = key.Length + preKeyLength;
            byte[] preIV = new byte[preIVLength];

            Buffer.BlockCopy( key, 0, preIV, 0, key.Length );
            Buffer.BlockCopy( preKey, 0, preIV, key.Length, preKey.Length );

            byte[] iv = md5.ComputeHash( preIV );

            md5.Clear( );
            md5 = null;

            AesManaged aes = new AesManaged( );
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 128;
            aes.BlockSize = 128;
            aes.Key = key;
            aes.IV = iv;

            byte[] clearData = null;

            using(ICryptoTransform Decryptor = aes.CreateDecryptor( ))
            {
                using(MemoryStream MemStream = new MemoryStream( ))
                {
                    using(CryptoStream CryptoStream = new CryptoStream( MemStream, Decryptor, CryptoStreamMode.Write ))
                    {
                        //Updated according to Tom's comment
                        //CryptoStream.Write( encrypted, 0, encrypted.Length );
                        CryptoStream.Write( aesData, 0, aesData.Length );
                        CryptoStream.FlushFinalBlock( );

                        clearData = MemStream.ToArray( );
                        CryptoStream.Close( );
                    }
                    MemStream.Close( );
                }
            }
            aes.Clear( );

            return clearData;
        }
    }
}

4 thoughts on “Encrypt data with .NET, decrypt with OpenSSL

  1. Tom

    There’s a bug in the Decrypt method. The variable ‘encrypted’ on this line should be ‘aesData’:

    CryptoStream.Write( encrypted, 0, encrypted.Length );

    Like this:

    CryptoStream.Write( aesData, 0, aesData.Length );

    Reply
  2. Chris

    To make this aes 256 instead of aes 128 would you only change the key size?

    Reply

Leave a Reply to Chris Cancel reply

Your email address will not be published.