Wednesday, April 22, 2009

Decrypting OpenSSL AES files in C#

Many operating systems are equipped with OpenSSL, making it fairly easy to deal with the otherwise complicated issue of encryption. Windows doesn't come with OpenSSL, but it does come with good ecryption libraries. The trick is getting the two to play nicely.

In this post I'll demonstrate the following:

  • How to encrypt/decrypt a file via AES using OpenSSL on command line

  • How to decrypt in Cocoa

  • How OpenSSL generates the Key and IV from a password

  • How to parse the salt from an OpenSSL encrypted AES file

  • How to use C# to decrypt an OpenSSL encrypted AES file


Let's start by encrypting a file. From the command line OpenSSL is fairly easy to use. (Although it can be daunting sometimes because it can do so many things, and there are so many options.) Here's how we can encrypt the file using AES, and a password of "secret":

$ openssl enc -aes-128-cbc -pass pass:secret -in file.txt -out file.txt.aes

And if we want to decrypt the file, it's almost the same command plus a "-d" (for decrypt) option:

$ openssl enc -aes-128-cbc -d -pass pass:secret -in file.txt.aes

Note: You can install OpenSSL on Windows by downloading the binary from here. This will allow you use OpenSSL from the command line just like in Linux/Mac/etc.

We can decrypt the file programmatically in Mac OS X using SSCrypto. It's child's play:
NSData *fileData = [NSData dataWithContentsOfFile:filePath];

NSString *passwd = @"secret";
NSData *passwdData = [passwd dataUsingEncoding:NSUTF8StringEncoding];

SSCrypto *sscrypto = [[SSCrypto alloc] initWithSymmetricKey:passwordData];
[sscrypto setCipherText:fileData];

NSData *clearData = [sscrypto decrypt:@"aes-128-cbc"];
NSLog(@"clearData: %@", clearData);
NSLog(@"clearText: %@", [sscrypto clearTextAsString]);

[sscrypto release];

Moving on to C#, we don't see an AES class, but there is a Rijndael class we can use since the two are almost the same. But looking at the documentation we find that there's no way to set the password. The RijndaelManaged class wants something called a Key and IV...

So is this an incompatibility? No, not at all. Let's go back to those OpenSSL commands really quick and add a "-p" option:

$ openssl enc -aes-128-cbc -d -p -pass pass:secret -in file.txt.aes
salt=A5307A8D9856664F
key=F7FAA9274A2BAD72554DE543BC2731FD
iv =5D82ECF3D3435CB30106EF21640B19F0


You can also add the "-p" option when encrypting to get the same output.

So it looks like OpenSSL generates a Key and IV from our password. But how? Internally it uses the EVP_BytesToKey method. In a nutshell, this means OpenSSL does the following:

Key = MD5(password + salt)
IV = MD5(Key + password + salt)


That's it! That's all there is to it!

But wait... How do we get the salt?

You can tell OpenSSL whether or not to use a salt by passing the "-salt" or "-nosalt" options explicitly. By default it will use a salt for better security. If a salt is used, the resulting AES file will begin with the word "Salted__" followed by an 8 byte salt. If you hexdump the AES file you'll notice the salt on the end of the first line:

$ hexdump file.txt.aes
0000000 53 61 6c 74 65 64 5f 5f a5 30 7a 8d 98 56 66 4f
0000010 17 3f 0e 03 5d 40 f2 ee e4 2a 60 43 6e 86 90 92
0000020


We're now ready to write some C# code.
// Read in the file and check to see if its salted
byte[] fileData = ReadFile("file.txt.aes");

bool isSalted = false;
byte[] salt = null;

if (fileData.Length > 16)
{
byte[] salted = Encoding.UTF8.GetBytes("Salted__");

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

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

// Remove salt from file data if necessary
byte[] aesData;

if (isSalted)
{
Console.WriteLine("Salt: {0}", ToHexString(salt));

int aesDataLength = fileData.Length - 16;

aesData = new byte[aesDataLength];
Buffer.BlockCopy(fileData, 16, aesData, 0, aesDataLength);
}
else
{
salt = new byte[0];
aesData = fileData;
}

// Create Key and IV from password
byte[] password = Encoding.UTF8.GetBytes("secret");
Console.WriteLine("password: {0}", ToHexString(password));

MD5 md5 = MD5.Create();

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

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

byte[] key = md5.ComputeHash(preKey);
Console.WriteLine("key: {0}", ToHexString(key));

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);
Console.WriteLine("iv: {0}", ToHexString(iv));

md5.Clear();
md5 = null;

// Decrypt using AES
RijndaelManaged rijndael = new RijndaelManaged();
rijndael.Mode = CipherMode.CBC;
rijndael.Padding = PaddingMode.PKCS7;
rijndael.KeySize = 128;
rijndael.BlockSize = 128;
rijndael.Key = key;
rijndael.IV = iv;

ICryptoTransform rijndaelDecryptor = rijndael.CreateDecryptor();

byte[] clearData = rijndaelDecryptor.TransformFinalBlock(aesData, 0, aesData.Length);
Console.WriteLine("clearData: {0}", ToHexString(clearData));

String clearText = Encoding.UTF8.GetString(clearData);
Console.WriteLine("clearText: {0}", clearText);


Download Decrypt AES project

 

14 comments:

Anonymous said...

I used your exact code and got it to work but then I changed it from using aes-128-cbc to des3 and RijndaelManaged to TripleDESCryptoServiceProvider and I got the following exception, "Specified initialization vector (IV) does not match the block size for this algorithm"

Sachin Sawant said...

Hey Deusty,
First of all thanks a lot for this post. Its very helpful. I had no clue of how to do this earlier.

Right now I working on AES -256 bit encryption and found that key size in AES 256 is 32 byte instead of 16. Looking further I also found that this key is concatenation of hashes of key(128 bit version)and IV(128 bit version). Thus,
Key(for Aes 256) = Key(we get in 128 bit version) + Iv(we get in 128 bit version)

The problem is that I am not getting how IV is generated which is nothing but a hash(16 byte)

Do u have any idea about it

REgards

Anonymous said...

Hi Deusty,

Can you please spare some of your brain cycles?

I have been trying to decrypt EFS files that have been encrypted but do not have the encrypted attribute on windows... (Yup! I managed to do it..) So, when I right click to decrypt, I dont see the checkbox, because the files are already decrypted. But, they are encrypted.

When I open one of those txt files, I get something like this: ...áQp4ž¶l}Éó›ÄÙÅÿ •é%挸š2û1Ú/ƒ,6...

Good that I have the pfx (with the private key) that encrypted those files but the files are on a different 2008 server now. So, I imported that pfx to the new server.

I installed many utils but no luck. I used certutil cmdlet in PowerShell to dump private key and also converted the pfx to pem. So, I have the private key.

I would like to decrpyt them with the private key I have, but I cannot. I tried openssl rsautil and aes-256... (btw, which version do I use cbc, ebc...?) But after two days, no luck! I am probably using them with the wrong switches.

Thanks,

By the way, I have my wedding videos/images that are in this state as well... If my wife finds out... HELP!!!

palanivel said...

Hello Deusty,

I used your exact code do not work in mac os aes-128-cbc. Terminal code execute perfect but objective c coding do not work.

Anthony Thyssen said...

For aes-256 you need 32 bytes for both key and the IV. The manual for EVP_BytesToKey() definae how this is done.

As such the actual sequence is...

For aes-256

Hash0 = ''
Hash1 = MD5(Hash0 + Password + Salt)
Hash2 = MD5(Hash1 + Password + Salt)
Hash3 = MD5(Hash2 + Password + Salt)
Hash4 = MD5(Hash3 + Password + Salt)
...

Key = Hash1 + Hash2
IV = Hash3 + Hash4

Basically hashes of pervious hashes are continued until you have enough bits for both Key and IV


ASIDE: this is very very fast to calculate. For better protection of encrypted data the password to key hashing function should be made slower, so that you need perhaps a second on a fast machine. Current methods involve the use of iterative hashing, where the hashing is performed tens-of-thousands of times. OpenSSL current does not provide such a hashing function :-(

Sachin Sawant said...

Thanks Anthony,
I will give it a try n let u know..

thanks again

nick-haddow said...

I need to do the opposite, that is, encrpyt a string with AES on the .Net side that can be decrypted by OpenSSL (which runs in our PeopleSoft environment). I am currently attempting to do this with the OpenSSL.Net C# wrapper which I found out on SourceForge, but am having little luck.

Perhaps I don't need the wrapper, do you have an example of how to do the AES string encryption within the Security.Crytpography framework, so that decrypting can be handled correctly by OpenSSL?

Or, perhaps you have used the wrapper, and can share of an example of that technique as well?

Thanks in advance for your assistance,

-Nick

Sachin Sawant said...

Hi nick,
Given below is the code for AES-128 in C#. I havent tested if fully but it used to work for decryption in OpenSSL in C++. Hope this helps..

C# Code :



public class Aes128
{
private String _password = "";

public Aes128(String password)
{
_password = password;
}

public void SetPassword(String password)
{
_password = password;
}

public Boolean Decrypt(MemoryStream stream)
{
stream.Seek(0, SeekOrigin.Begin);

byte[] aesData = new byte[stream.Length];
stream.Read(aesData, 0, (int)stream.Length);

//Get key and iv
byte[] key = null;
byte[] iv = null;

GetKeyAndIvFromPassword(_password, ref key, ref iv);
if (key == null || iv == null)
{
return false;
}

// Decrypt using AES
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Key = key;
aes.IV = iv;

ICryptoTransform aesDecryptor = aes.CreateDecryptor();
byte[] clearData = aesDecryptor.TransformFinalBlock(aesData, 0, aesData.Length);

//Update stream
stream.SetLength(0);
stream.Position = 0;
stream.Write(clearData, 0, clearData.Length);
stream.Position = 0;
return true;
}

public Boolean Encrypt(MemoryStream stream)
{
stream.Seek(0, SeekOrigin.Begin);

byte[] aesData = new byte[stream.Length];
stream.Read(aesData, 0, (int)stream.Length);

//Get key and iv
byte[] key = null;
byte[] iv = null;

GetKeyAndIvFromPassword(_password, ref key, ref iv);
if (key == null || iv == null)
{
return false;
}

// Encrypt using AES
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Key = key;
aes.IV = iv;

ICryptoTransform aesEncryptor = aes.CreateEncryptor();
byte[] EncData = aesEncryptor.TransformFinalBlock(aesData, 0, aesData.Length);

//Update stream
stream.SetLength(0);
stream.Position = 0;
stream.Write(EncData, 0, EncData.Length);
stream.Position = 0;

return true;
}

private void GetKeyAndIvFromPassword(String pass, ref byte[] key, ref byte[] iv)
{
//Password
byte[] password = Encoding.UTF8.GetBytes(pass);

MD5 md5 = MD5.Create();
byte[] aesKey;
byte[] aesIv;

//Generate key
int preKeyLength = password.Length;
byte[] preKey = new byte[preKeyLength];
Buffer.BlockCopy(password, 0, preKey, 0, password.Length);
aesKey = md5.ComputeHash(preKey);

//Generate Iv
int preIVLength = aesKey.Length + preKeyLength;
byte[] preIV = new byte[preIVLength];
Buffer.BlockCopy(aesKey, 0, preIV, 0, aesKey.Length);
Buffer.BlockCopy(preKey, 0, preIV, aesKey.Length, preKey.Length);

aesIv = md5.ComputeHash(preIV);

md5.Clear();
md5 = null;

key = aesKey;
iv = aesIv;
}
}

Anonymous said...

From the bottom of my heart: Thank you! And a special thanks to Anthony as well! I have been struggling with how to encrypt properly for an OpenSSL-decryption-endpoint in Java for 2 days now.

Although I now this is not a Java "area" I take the liberty to post my code - maybe someone else can take advantage of it:

public class AES {

private static MessageDigest md5 = null;

private static byte[] add(byte[] b1, byte[] b2) {
byte[] b = new byte[b1.length + b2.length];
System.arraycopy(b1, 0, b, 0, b1.length);
System.arraycopy(b2, 0, b, b1.length, b2.length);
return b;
}

private static byte[] hash(byte[] prevHash, byte[] password, byte[] salt) throws NoSuchAlgorithmException {
if (prevHash != null && prevHash.length > 0)
return md5.digest(add(add(prevHash, password), salt));
else
return md5.digest(add(password, salt));
}

public static byte[] encryptImpl(String message, String passwordStr) throws Exception {
md5 = MessageDigest.getInstance("MD5");
byte[] password = passwordStr.getBytes("UTF-8");
byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
byte[] hash0 = null;
byte[] hash1 = hash(hash0, password, salt);
byte[] hash2 = hash(hash1, password, salt);
byte[] hash3 = hash(hash2, password, salt);

byte[] key = add(hash1, hash2);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(hash3);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(message.getBytes());
return add(add("Salted__".getBytes("UTF-8"), salt), encrypted);
}
}

Anonymous said...

Can you please leave an example of aes encoding in c++? I'm working on ubuntu with openssl lib and I wouldlike an aes 256 with cbc and password encryption/decryption. The padding is pkcs. Need help. I have to do the aes256 ob ubuntu and .net c# too. THX

chamroeun said...

Hi Deusty,

Glad to see your code!
I have code that is encrypted using this Cocoa AES:
Cocoa AES

And I need to decrypt it using C#.

I try your code but it failed.

Here's my sample data:
x4sYXzvN1HZK7LNYySvJv0vPaxms2n3oLtM2IpmKWsU=
passkey:cbvde432Y12rqdazxs


Can you or anyone help me?

Rob said...

First, thanks a lot Deusty. I needed this analysis before I went deep diving through the confusing docs and code...

@chamroeun, the "Cocoa AES" you reference is a really non-standard (though widely used), highly insecure use of AES. It doesn't use anything like the OpenSSL approach (which itself isn't great, but still much better than the one you reference).

In the code you reference, the key is just the password string, padded with 0s or truncated to make it 32 bytes. There is no hashing. There is no IV (which is to say the IV is 16 bytes of 0). This dramatically shrinks the keyspace, which is why this code is so insecure, but that's how it works.

If you want more discussion of the issues, see http://robnapier.net/blog/aes-commoncrypto-564. If you want to see the replacement I've been working on for it, see http://github.com/rnapier/RNCryptor

Cary Lewis said...

First of all thanks, this is a great post.

I have the following problem:

I have a C# application that is using the Rijndael class to encrypt some data and then base64 encode, and I would like to decrypt it using openssl.

I have a file that contains the password, but when i try to decrypt I keep getting bad magic number.

I think the issue is that the way that C# is generating the key and iv is somehow different from openssl.

Does anyone have a suggestion on how to proceed?

Jim said...

Deusty,
Great blog. Very helpful to learn how OpenSSL computes Key and IV for AES in CBC mode.

I want to add to the knowledge here. First of all, if you use the "-pass pass:filename" option in OpenSSL, it only seems to use the first 1023 bytes of filename. Everything after is discarded.

Next, if you use OpenSSL in FIPS mode, it uses the SHA1 algorithm rather than MD5. SHA1 produces 20-byte hashes rather than 16-byte hashes. After you compute Hash1, Hash2, etc as Anthony Thyssen indicates, just concatenate all the Hashes together into a single byte string (call it hashbytes).

For AES-128:
- Key = hashbytes[0:16]
- IV = hashbytes[16:32]

For AES-256:
- Key = hashbytes[0:32]
- IV = hashbytes[32:48]