Skip to content

Instantly share code, notes, and snippets.

@scottlowe
Created November 30, 2011 23:38
Show Gist options
  • Select an option

  • Save scottlowe/1411917 to your computer and use it in GitHub Desktop.

Select an option

Save scottlowe/1411917 to your computer and use it in GitHub Desktop.
OpenSSL AES CBC 256 in .NET for interop with Ruby
require 'rubygems'
require 'bundler'
require 'gibberish'
password = '181cc0b200124dc748c96d8fdefe2cb3f21b48212898c2e3dd705a4c8415a5abb15b3c43ccb9e9db27e6c9ad1df1b927377c8dd6bb3d07746bc3cec7c67ca016'
cipher = Gibberish::AES.new(password)
cypher_text = cipher.encrypt("Hello from Ruby!")
plain_text = cipher.decrypt('U2FsdGVkX18GXWJnusOQi55IhmvfdVyjTLjAmmtcAmg=')
print "Encrypted string is: "
p cypher_text
puts "Decrypted string is: '#{plain_text}'"
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace dotnet_aes
{
public class OpenSslAes
{
public static string Encrypt(string plainText, string passphrase)
{
byte[] key, iv;
var salt = new byte[8];
new RNGCryptoServiceProvider().GetNonZeroBytes(salt);
EvpBytesToKey(passphrase, salt, out key, out iv);
byte[] encryptedBytes = AesEncrypt(plainText, key, iv);
var encryptedBytesWithSalt = CombineSaltAndEncryptedData(encryptedBytes, salt);
return Convert.ToBase64String(encryptedBytesWithSalt);
}
// OpenSSL prefixes the combined encrypted data and salt with "Salted__"
private static byte[] CombineSaltAndEncryptedData(byte[] encryptedData, byte[] salt)
{
var encryptedBytesWithSalt = new byte[salt.Length + encryptedData.Length + 8];
Buffer.BlockCopy(Encoding.ASCII.GetBytes("Salted__"), 0, encryptedBytesWithSalt, 0, 8);
Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 8, salt.Length);
Buffer.BlockCopy(encryptedData, 0, encryptedBytesWithSalt, salt.Length + 8, encryptedData.Length);
return encryptedBytesWithSalt;
}
public static string Decrypt(string encrypted, string passphrase)
{
byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted);
var salt = ExtractSalt(encryptedBytesWithSalt);
var encryptedBytes = ExtractEncryptedData(salt, encryptedBytesWithSalt);
byte[] key, iv;
EvpBytesToKey(passphrase, salt, out key, out iv);
return AesDecrypt(encryptedBytes, key, iv);
}
// Pull the data out from the combined salt and data
private static byte[] ExtractEncryptedData(byte[] salt, byte[] encryptedBytesWithSalt)
{
var encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length - 8];
Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + 8, encryptedBytes, 0, encryptedBytes.Length);
return encryptedBytes;
}
// The salt is located in the first 8 bytes of the combined encrypted data and salt bytes
private static byte[] ExtractSalt(byte[] encryptedBytesWithSalt)
{
var salt = new byte[8];
Buffer.BlockCopy(encryptedBytesWithSalt, 8, salt, 0, salt.Length);
return salt;
}
// Key derivation algorithm used by OpenSSL
//
// Derives a key and IV from the passphrase and salt using a hash algorithm (in this case, MD5).
//
// Refer to http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
private static void EvpBytesToKey(string passphrase, byte[] salt, out byte[] key, out byte[] iv)
{
var concatenatedHashes = new List<byte>(48);
byte[] password = Encoding.UTF8.GetBytes(passphrase);
byte[] currentHash = new byte[0];
MD5 md5 = MD5.Create();
bool enoughBytesForKey = false;
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[] AesEncrypt(string plainText, byte[] key, byte[] iv)
{
MemoryStream memoryStream;
RijndaelManaged aesAlgorithm = null;
try
{
aesAlgorithm = new RijndaelManaged
{
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 128,
Key = key,
IV = iv
};
var cryptoTransform = aesAlgorithm.CreateEncryptor(aesAlgorithm.Key, aesAlgorithm.IV);
memoryStream = new MemoryStream();
using (var cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
{
using (var streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write(plainText);
streamWriter.Flush();
streamWriter.Close();
}
}
}
finally
{
if (aesAlgorithm != null) aesAlgorithm.Clear();
}
return memoryStream.ToArray();
}
static string AesDecrypt(byte[] cipherText, byte[] key, byte[] iv)
{
RijndaelManaged aesAlgorithm = null;
string plaintext;
try
{
aesAlgorithm = new RijndaelManaged
{
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 128,
Key = key,
IV = iv
};
ICryptoTransform decryptor = aesAlgorithm.CreateDecryptor(aesAlgorithm.Key, aesAlgorithm.IV);
using (var memoryStream = new MemoryStream(cipherText))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (var streamReader = new StreamReader(cryptoStream))
{
plaintext = streamReader.ReadToEnd();
streamReader.Close();
}
}
}
}
finally
{
if (aesAlgorithm != null) aesAlgorithm.Clear();
}
return plaintext;
}
}
}
using System;
namespace dotnet_aes
{
class Program
{
private const string DotNetPlainText = "Hello from .NET";
private const string CipherBase64 = "U2FsdGVkX1+wqU8tchOuA2G2I5ceNzad86pb7p2371vKJulvMCuBPQTKo5vG\ncEF9\n";
private const string Password = "181cc0b200124dc748c96d8fdefe2cb3f21b48212898c2e3dd705a4c8415a5abb15b3c43ccb9e9db27e6c9ad1df1b927377c8dd6bb3d07746bc3cec7c67ca016";
static void Main()
{
DemoDecryption();
DemoEncryption();
Console.ReadLine();
}
private static void DemoDecryption()
{
var plainText = OpenSslAes.Decrypt(CipherBase64, Password);
Console.WriteLine("Decrypted string is: '{0}'", plainText);
}
private static void DemoEncryption()
{
var plainText = OpenSslAes.Encrypt(DotNetPlainText, Password);
Console.WriteLine("Encrypted string is: '{0}'", plainText);
}
}
}
@onyxraven

Copy link
Copy Markdown

Scott, I'm planning on basing some code off your work here, as its super useful. Any problems using it and doing some code redistribution to private parties? I can give you attribution :-)

@vijaymchauhan

Copy link
Copy Markdown

Thanks , this is very usefull to me.

@scottlowe

Copy link
Copy Markdown
Author

@onyxraven Hi there.

I'm sorry that I've only just seen your message after all this time. You don't need to attribute me... this is public, after all. I don't remember the details concerning the writing of this, but I suspect that I patched it together from various snippets of code lying about the web, anyway!

@DaveJKing

Copy link
Copy Markdown

Hi Scott, exactly what I was looking. Works on the ruby and .net sides. However, if I change the password it encrypts OK but on the .Net side I get an exception (same password of course)
password = '7B932AFA6AA07F75D2F3F6997C5809ECBB845FCD619265D52AE6F3FA29911C1E834AFAC5E06D3AA568FE8D9321CBB27FB9916620EC9B5756A20C97DE1DB2B5D7'

Private Const as String = "7B932AFA6AA07F75D2F3F6997C5809ECBB845FCD619265D52AE6F3FA29911C1E834AFAC5E06D3AA568FE8D9321CBB27FB9916620EC9B5756A20C97DE1DB2B5D7"

Any ideas ??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment