Openssl Encryption Using .Net Classes

OpenSSL encryption using .NET classes

Finally figured this one out. In the event someone needs to integrate openssl and .NET without using the openssl wrappers, I'll share the results here.

1) The main issue with my original code (as in the question) is that you must initialize the BlockSize and KeySize on your RijndaelManaged instance BEFORE setting the key or IV.

2) I also had BlockSize set to 256 when it should only be 128

3) The remainder of my issue came to the fact that openssl puts and expects "Salted__" onto the front of the salt before appending the encrypted string and then base64 encoding it. (I saw this initially in the openssl documentation with respect to file encryption but didn't think it did it when doing it directly through commandline - Apparently I was wrong!! Note also the capitalization of the S in Salted!)

With that all in mind, here is my "fixed" code:

public class Protection
{
public string OpenSSLEncrypt(string plainText, string passphrase)
{
// generate salt
byte[] key, iv;
byte[] salt = new byte[8];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(salt);
DeriveKeyAndIV(passphrase, salt, out key, out iv);
// encrypt bytes
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 string OpenSSLDecrypt(string encrypted, string passphrase)
{
// base 64 decode
byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted);
// extract salt (first 8 bytes of encrypted)
byte[] salt = new byte[8];
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
List<byte> concatenatedHashes = new List<byte>(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;
}
}

replicating openssl encryption in .NET?

OpenSSL is basically some dlls with and executable over them. There is an old project OpenSSL.Net on sourceforge to bind them in C# maybe you could start there.

OpenSSL encryption failing to decrypt C#

You openssl command is wrong, I assume it should be:

openssl enc -base64 -aes-256-cbc -k 12345678901234567890123456789012 -in test.txt -out cbc64.txt

reading your code I see you made a few wrong assumptions:

  • You are using the password as a key and as IV (where did you get that?)
    Password is not key
  • you've assumed the output is pure ciphertext

try to add -p parameter to the openssl enc command and it will display as well salt and derived key and iv

So lets fix that:

  1. OpenSSL generates random 8 bytes salt
  2. OpenSSL uses its "magic" function EVP_BytesToKey to derive the key and IV from the passsword and salt, example impl in c#
  3. OpenSSL output looks like: base64(Salted_{salt}{ciphertext})

It is possible to decrypt AES password protected file in C# / dotNet 6 encrypted by openssl enc -k?

The code from the 10 year old question you linked actully still works with minor modifications. First note that by default OpenSSL now uses SHA256 as a hash function and not MD5, we can easily fix that. Then, that answer assumes you provide "-base64" option to openssl and get result in base64 and not strange format used by OpenSSL by default, but that's also easy to fix. Just read target file as bytes, then strip ascii-encoded "SALTED__" string from its beginning:

var input = File.ReadAllBytes(@"your encrypted file");
input = input.Skip(Encoding.ASCII.GetBytes("SALTED__").Length).ToArray();

Now adjust how it extracts salt and encrypted data from there, and use PKCS7 padding, and it'll work. Full code copied from the answer above with mentioned fixes:

public class Protection
{
public string OpenSSLDecrypt(byte[] encryptedBytesWithSalt, string passphrase)
{
// extract salt (first 8 bytes of encrypted)
byte[] salt = new byte[8];
byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length];
Buffer.BlockCopy(encryptedBytesWithSalt, 0, salt, 0, salt.Length);
Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length, 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
List<byte> concatenatedHashes = new List<byte>(48);

byte[] password = Encoding.UTF8.GetBytes(passphrase);
byte[] currentHash = new byte[0];
var md5 = SHA256.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 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, Padding = PaddingMode.PKCS7};

// 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;
}
}

Then just:

var input = File.ReadAllBytes(@"path to your encrypted file");
input = input.Skip(Encoding.ASCII.GetBytes("SALTED__").Length).ToArray();
var decrypted= new Protection().OpenSSLDecrypt(input, "123123");

If you decrypt non-string data, change DecryptStringFromBytesAes like that:

static byte[] 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;

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, Padding = PaddingMode.PKCS7 };

// 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 (var output = new MemoryStream()) {
csDecrypt.CopyTo(output);
return output.ToArray();
}
}
}
}
finally {
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
}

Decrypt string in C# that was encrypted with PHP openssl_encrypt

Well this was fun to work out and required jumping into the PHP source code with some interesting results. Firstly PHP doesn't even use a key derivation algorithm it just takes the bytes of the passphrase and pads it out with zero's to the required length. That means the entire DeriveKeyAndIV method isn't necessary.

Because of the above that means the IV that is being used is a 16 length byte array containing zeros.

Finally the only other thing wrong with your code is that the source you copied it from used a salt in their implementation of encrypt which then had to be removed, PHP nor you are doing this so removing the salt bytes is incorrect.

So the all of this put together means you need to change the OpenSSLDecrypt method to this.

public static string OpenSSLDecrypt(string encrypted, string passphrase)
{
//get the key bytes (not sure if UTF8 or ASCII should be used here doesn't matter if no extended chars in passphrase)
var key = Encoding.UTF8.GetBytes(passphrase);

//pad key out to 32 bytes (256bits) if its too short
if (key.Length < 32)
{
var paddedkey = new byte[32];
Buffer.BlockCopy(key, 0, paddedkey, 0, key.Length);
key = paddedkey;
}

//setup an empty iv
var iv = new byte[16];

//get the encrypted data and decrypt
byte[] encryptedBytes = Convert.FromBase64String(encrypted);
return DecryptStringFromBytesAes(encryptedBytes, key, iv);
}

And very finally the resulting string has some extra chars at the end namely a set of 3 of the ETX char but these should be easy enough to filter out. I actually can't figure out where these are coming from.

Thanks to @neubert for pointing out the padding is a part of the standard PKCS padding if you want the framework to remove this just specify that as the padding mode when instantiating the RijndaelManaged object.

new RijndaelManaged { Padding = PaddingMode.PKCS7 };

How to use SMIME OpenSSL.NET encryption

This can be done with the OpenSslSmime class from the commercial product OpenSSL Library for .NET:

DidiSoft.OpenSsl.Cms.OpenSslSmime smime = new DidiSoft.OpenSsl.Cms.OpenSslSmime();
smime.EncryptFile("plaintestFile.txt", "certificate.pem", @"encryptedFile.txt", CmsCipherAlgorithm.Des3);

Disclaimer: I work for DidiSoft



Related Topics



Leave a reply



Submit