Producing OpenSSL AES Encrypted String from C#Often we use OpenSSL to encrypt a string. This page creates the same output using C#. With OpenSSL we can provide a passphrase and an eight byte salt value, and then derive the Key and IV (Initalisation Vector). This is then used to encrypt the data stream. Also in OpenSSL we have the phrase "Salted__$" at the start of the ciphered stream, followed by the salt, then the cipher stream. The key is then 32 bytes (256 bits) long and the IV is 16 bytes (128 bits) long. |
Theory
In this case we will used 256-bit CBC with a salt value (in hex) and a passphrase in order to generate the key and IV. Here are some tests:
$ echo -n Hello | openssl enc -aes-256-cbc -pass pass:"qwerty" -e -base64 -S 241fa86763b85341 U2FsdGVkX18kH6hnY7hTQZAGxV2faF01w6uhO+X6+9Q= $ echo -n hello | openssl enc -aes-256-cbc -pass pass:"qwerty" -e -base64 -S 241fa86763b85341 U2FsdGVkX18kH6hnY7hTQQvZbphR8WXJbOkwYSCKfY0=
And the reverse to check:
$ echo "U2FsdGVkX18kH6hnY7hTQZAGxV2faF01w6uhO+X6+9Q=" | openssl enc -d -aes-256-cbc -base64 -pass pass:"qwerty" -S 241fa86763b85341 Hello
A sample run proves these:
Text: Hello Pass phrase: qwerty ------------------------ IV: 6BE952EBC17EED10411EAA9892F19124 Key: 33A5820536F9EEB709D88AF3B40FDBB100C04327C71B5ACCF48424C8EB40C3F9 ------------------------ Cipher: U2FsdGVkX18kH6hnY7hTQZAGxV2faF01w6uhO+X6+9Q= Decrypt: Hello
and:
Text: hello Pass phrase: qwerty ------------------------ IV: 6BE952EBC17EED10411EAA9892F19124 Key: 33A5820536F9EEB709D88AF3B40FDBB100C04327C71B5ACCF48424C8EB40C3F9 ------------------------ Cipher: U2FsdGVkX18kH6hnY7hTQQvZbphR8WXJbOkwYSCKfY0= Decrypt: hello
The code used is:
using System.Security.Cryptography; using System.IO; using System; using System.Collections.Generic; using System.Text; // Code derived from https://stackoverflow.com/questions/5452422/openssl-encryption-using-net-classes/5454692 namespace Aes { class Program { static void Main(string[] args) { string cipher,dec; string text,passphrase,salt; text="Hello"; passphrase="qwerty"; salt="241fa86763b85341"; if (args.Length>0) text = args[0]; if (args.Length>1) passphrase = args[1]; if (args.Length>2) salt = args[2]; Console.WriteLine("Text:\t\t"+text); Console.WriteLine("Pass phrase:\t"+passphrase); Console.WriteLine("------------------------\n"); cipher=OpenSSLEncrypt(text,passphrase,salt); dec=OpenSSLDecrypt(cipher,passphrase,salt); Console.WriteLine("------------------------\n"); Console.WriteLine("Cipher:\t\t"+cipher); Console.WriteLine(" Hex: "+ByteArrayToHexString(Convert.FromBase64String(cipher))); Console.WriteLine(" ASCII: "+Encoding.ASCII.GetString((Convert.FromBase64String(cipher)))); Console.WriteLine("Decrypt:\t"+dec); } public static string OpenSSLEncrypt(string plainText, string passphrase, string s) { // generate salt byte[] key, iv; byte[] salt = new byte[8]; salt=StringToByteArray(s); DeriveKeyAndIV(passphrase, salt, out key, out iv); // encrypt bytes Console.WriteLine("IV:\t"+ByteArrayToHexString(iv)); Console.WriteLine("Key:\t"+ByteArrayToHexString(key)); byte[] encryptedBytes = EncryptStringToBytesAes(plainText, key, iv); // add salt as first 8 bytes byte[] encryptedBytesWithSalt = new byte[salt.Length + encryptedBytes.Length + 8]; Buffer.BlockCopy(Encoding.ASCII.GetBytes("Salted__"), 0, encryptedBytesWithSalt, 0, 8); Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 8, salt.Length); Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length + 8, encryptedBytes.Length); // base64 encode return Convert.ToBase64String(encryptedBytesWithSalt); } public static string OpenSSLDecrypt(string encrypted, string passphrase, string s) { // base 64 decode byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted); // extract salt (first 8 bytes of encrypted) byte[] salt = new byte[8]; salt=StringToByteArray(s); byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length - 8]; Buffer.BlockCopy(encryptedBytesWithSalt, 8, salt, 0, salt.Length); Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + 8, encryptedBytes, 0, encryptedBytes.Length); // get key and iv byte[] key, iv; DeriveKeyAndIV(passphrase, salt, out key, out iv); return DecryptStringFromBytesAes(encryptedBytes, key, iv); } private static void DeriveKeyAndIV(string passphrase, byte[] salt, out byte[] key, out byte[] iv) { // generate key and iv ListconcatenatedHashes = new List (48); byte[] password = Encoding.UTF8.GetBytes(passphrase); byte[] currentHash = new byte[0]; MD5 md5 = MD5.Create(); bool enoughBytesForKey = false; // See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM while (!enoughBytesForKey) { int preHashLength = currentHash.Length + password.Length + salt.Length; byte[] preHash = new byte[preHashLength]; Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length); Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length); Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length); currentHash = md5.ComputeHash(preHash); concatenatedHashes.AddRange(currentHash); if (concatenatedHashes.Count >= 48) enoughBytesForKey = true; } key = new byte[32]; iv = new byte[16]; concatenatedHashes.CopyTo(0, key, 0, 32); concatenatedHashes.CopyTo(32, iv, 0, 16); md5.Clear(); md5 = null; } static byte[] EncryptStringToBytesAes(string plainText, byte[] key, byte[] iv) { // Check arguments. if (plainText == null || plainText.Length <= 0) throw new ArgumentNullException("plainText"); if (key == null || key.Length <= 0) throw new ArgumentNullException("key"); if (iv == null || iv.Length <= 0) throw new ArgumentNullException("iv"); // Declare the stream used to encrypt to an in memory // array of bytes. MemoryStream msEncrypt; // Declare the RijndaelManaged object // used to encrypt the data. RijndaelManaged aesAlg = null; try { // Create a RijndaelManaged object // with the specified key and IV. aesAlg = new RijndaelManaged { Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv }; // Create an encryptor to perform the stream transform. ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); // Create the streams used for encryption. msEncrypt = new MemoryStream(); using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { //Write all data to the stream. swEncrypt.Write(plainText); swEncrypt.Flush(); swEncrypt.Close(); } } } finally { // Clear the RijndaelManaged object. if (aesAlg != null) aesAlg.Clear(); } // Return the encrypted bytes from the memory stream. return msEncrypt.ToArray(); } static string DecryptStringFromBytesAes(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("iv"); // Declare the RijndaelManaged object // used to decrypt the data. RijndaelManaged aesAlg = null; // Declare the string used to hold // the decrypted text. string plaintext; try { // Create a RijndaelManaged object // with the specified key and IV. aesAlg = new RijndaelManaged {Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv}; // Create a decrytor to perform the stream transform. ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); // Create the streams used for decryption. using (MemoryStream msDecrypt = new MemoryStream(cipherText)) { using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { using (StreamReader srDecrypt = new StreamReader(csDecrypt)) { // Read the decrypted bytes from the decrypting stream // and place them in a string. plaintext = srDecrypt.ReadToEnd(); srDecrypt.Close(); } } } } finally { // Clear the RijndaelManaged object. if (aesAlg != null) aesAlg.Clear(); } return plaintext; } public static byte[] StringToByteArray(String hex) { int NumberChars = hex.Length; byte[] bytes = new byte[NumberChars / 2]; for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); return bytes; } public static string ByteArrayToHexString(byte[] Bytes) { StringBuilder Result = new StringBuilder(Bytes.Length * 2); string HexAlphabet = "0123456789ABCDEF"; foreach (byte B in Bytes) { Result.Append(HexAlphabet[(int)(B >> 4)]); Result.Append(HexAlphabet[(int)(B & 0xF)]); } return Result.ToString(); } } }
The following is the coding: