Skip to content

Instantly share code, notes, and snippets.

@scottlowe
Created November 30, 2011 23:38
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save scottlowe/1411917 to your computer and use it in GitHub Desktop.
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

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

Thanks , this is very usefull to me.

@scottlowe
Copy link
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

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