Created
November 30, 2011 23:38
-
-
Save scottlowe/1411917 to your computer and use it in GitHub Desktop.
OpenSSL AES CBC 256 in .NET for interop with Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}'" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
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
@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!