EDIT: Added my hash code to the bottom of this.
I am having some problems creating a message integrity key for a solution I am creating. In order for this to be correct I need to use the following settings.
Mode: ECB
KeySize: 256
BlockSize: 128
Padding: PKCS7
I am using a 32 byte key which is generated from a file and also a blank IV as I understand ECB does not require one.
My problem I am expecting a 48 byte output from this before the encoding however I am receiving a 64 byte output.
I have shown some code below about how am I am trying to achieve this but I am not having much success.
public static string Encrypt(string hash) {
// Create a new instance of the AesManaged
// class. This generates a new key and initialization
// vector (IV).
using (AesManaged myAes = new AesManaged()) {
myAes.Key = File.ReadAllBytes("keyfile");
myAes.Mode = CipherMode.ECB;
myAes.IV = new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
myAes.KeySize = 256;
myAes.BlockSize = 128;
myAes.Padding = PaddingMode.PKCS7;
// Encrypt the string to an array of bytes.
byte[] encrypted = EncryptStringToBytes_Aes(hash, myAes.Key, myAes.IV);
// Decrypt the bytes to a string.
string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);
//Display the original data and the decrypted data.
Console.WriteLine("Original: {0}", hash);
Console.WriteLine("Round Trip: {0}", roundtrip);
// Encode
string encoded = Convert.ToBase64String(encrypted);
Console.WriteLine("Encoded: {0}", encoded);
return encoded;
}
}
static byte[] EncryptStringToBytes_Aes(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("Key");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged()) {
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream 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);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
static string DecryptStringFromBytes_Aes(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("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.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();
}
}
}
}
return plaintext;
}
public static string getHashSha256(string text) {
byte[] bytes = Encoding.UTF8.GetBytes(text);
SHA256Managed hashstring = new SHA256Managed();
byte[] hash = hashstring.ComputeHash(bytes);
string hashString = string.Empty;
foreach (byte x in hash) {
hashString += String.Format("{0:x2}", x);
}
return hashString;
}
PKCS #7 padding is defined such that padding is added in all cases. When the plaintext is a multiple of the block size, a whole block of padding is added. This is why the ciphertext is 64 bytes long when the plaintext is 48 bytes long.
Related
I need to decrypt a file coming from an linux box, password protected with Openssl and AES.
The encryption is done with
openssl enc -aes-256-cbc -k <pwd>
Currently, I get it properly decrypted with the following script on Windows:
"openssl.exe" enc -d -aes-256-cbc -k <pwd> -in <inputFile> -out <output>
So far, I included the openssl exe and 2 dll with my project to do so.
However, I would like to get rid of those dependencies and decode it directly in C#.
What is the C# equivalent of the openssl enc -d as above?
It is anyway possible?
I read from https://security.stackexchange.com/questions/20628/where-is-the-salt-on-the-openssl-aes-encryption that openssl enc is kind of non standard and is using a random salt from the given password.
Inspired by a few other similar topics, my current method always get the "padding invalid" issue as e.g. this other question AES-256-CBC Decrypt Error Stating Padding is invalid and cannot be removed
This 10-years old thread OpenSSL encryption using .NET classes proposed a solution, even more complex to retrieve the salt and IV, but this is not working anymore. I also get the "padding invalid" issue.
(original code with Rfc2898DeriveBytes object for the pwd removed, openssl does not use this Rfc2898DeriveBytes stuff). See working code in the accepted answer.
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();
}
}
Inspired from the response above, and with my comments to them, this is the class I am using for my .NET 6 projects in VS 2022.
public static class OpenSslUtils
{
public static byte[] OpenSSLDecrypt(byte[] encryptedBytesWithSalt, string passphrase)
{
// remove the SALTED prefix
byte[] input = encryptedBytesWithSalt.Skip(Encoding.ASCII.GetBytes("Salted__").Length).ToArray();
// extract salt (first 8 bytes of encrypted)
byte[] salt = new byte[8];
byte[] encryptedBytes = new byte[input.Length - salt.Length];
Buffer.BlockCopy(input, 0, salt, 0, salt.Length);
Buffer.BlockCopy(input, salt.Length, encryptedBytes, 0, encryptedBytes.Length);
// get key and iv
DeriveKeyAndIV(passphrase, salt, out byte[] key, out byte[] iv);
return DecryptFromBytesAes(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(48);
byte[] password = Encoding.UTF8.GetBytes(passphrase);
byte[] currentHash = Array.Empty<byte>();
var hash = 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 = hash.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);
hash.Dispose();
}
private static byte[] DecryptFromBytesAes(byte[] cipherText, byte[] key, byte[] iv)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException(nameof(cipherText));
if (key == null || key.Length <= 0)
throw new ArgumentNullException(nameof(key));
if (iv == null || iv.Length <= 0)
throw new ArgumentNullException(nameof(iv));
// Declare the Aes object used to decrypt the data.
Aes? aesAlg = null;
// Declare the byte[] used to hold the decrypted text.
byte[]? decryptedOutput = null;
try
{
// Create an AES object
// with the specified key and IV.
aesAlg = Aes.Create();
aesAlg.Mode = CipherMode.CBC;
aesAlg.KeySize = 256;
aesAlg.BlockSize = 128;
aesAlg.Key = key;
aesAlg.IV = iv;
aesAlg.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(cipherText);
using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
using MemoryStream output = new();
csDecrypt.CopyTo(output);
decryptedOutput = output.ToArray();
}
finally
{
// Clear the object.
if (aesAlg != null)
{
aesAlg.Dispose();
}
}
return decryptedOutput;
}
}
I am trying to encrypt my data using the AES algorith.I got the functions from the MSDN site encryption decryption. What i am doing is i'm encrypting the data and storing it as a string using the following method
byte[] encrypted = EncryptStringToBytes_Aes(response, myAes.Key, myAes.IV);
string saveresponse = Convert.ToBase64String(encrypted);
and then i save it in IsolatedStorageSettings
settings.Add(merchantId, saveresponse);
But the problem i am facing is when the user comes after sometime and hits my page i check first in the IsolatedStorageSettings object if the data is present i pass that data to decrypt and process further.The step i use to decrypt is as follows
byte[] temp = Convert.FromBase64String(response);
response = DecryptStringFromBytes_Aes(temp, myAes.Key, myAes.IV);
But the above line gives me error "Value can not be null.
Parameter name: inputBuffer"
I am unable to find where i am going wrong.Can u guys please let me know what steps should be taken to make it up and running.
Here is the Encryption Code
static byte[] EncryptStringToBytes_Aes(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("Key");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream 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);
}
encrypted = msEncrypt.ToArray();
}
}
}
return encrypted;
}
and here is the decryption code
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
{
try
{
// 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("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.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();
}
}
}
}
return plaintext;
}
catch (Exception ex) {
return "Error";
}
}
and on button1 click i call the encryption method
using (AesManaged myAes = new AesManaged())
{
byte[] encrypted = EncryptStringToBytes_Aes(response, myAes.Key, myAes.IV);
string saveresponse = Convert.ToBase64String(encrypted);
}
and on button 2 i call decryption method
using (AesManaged myAes = new AesManaged())
{
byte[] temp= Convert.FromBase64String(response)
response = DecryptStringFromBytes_Aes(temp, myAes.Key, myAes.IV);
}
The problem was the using (AesManaged myAes = new AesManaged()){} block what it does it generates the new key and IV for encryption and decryption.So while decryption the key and IV doesnt match hence the error generates.Just remove the using block and declare the Aesmanaged myAes object at global level and the problem is solved. So the final code would look like
AesManaged myAes = new AesManaged();
On button one click to encrypt;
byte[] encrypted = EncryptStringToBytes_Aes(response, myAes.Key, myAes.IV);
string saveresponse = Convert.ToBase64String(encrypted);
and button2 click to decrypt
byte[] temp = Convert.FromBase64String(response);
response = DecryptStringFromBytes_Aes(temp, myAes.Key, myAes.IV);
Thats it, happpy coding.
Hi I have tried a few different options, but I cannot seem to correct this problem.
here is my Decrypt code ...
public string DecryptStringAES(string cipherText, int AccountID = 0)
{
string sharedSecret = "";
AccountID = (AccountID > 0) ? AccountID : SessionVars.Current.varAccountID;
sharedSecret = "#c3x%" + AccountID + "n^/]R";
if (string.IsNullOrEmpty(cipherText))
throw new ArgumentNullException("cipherText");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret");
// Declare the RijndaelManaged object
// used to decrypt the data.
RijndaelManaged aesAlg = null;
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
try
{
// generate the key from the shared secret and the salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);
// Create the streams used for decryption.
string dummyData = cipherText.Trim().Replace(" ", "+");
if (dummyData.Length % 4 > 0)
dummyData = dummyData.PadRight(dummyData.Length + 4 - dummyData.Length % 4, '=');
byte[] bytes = Convert.FromBase64String(dummyData);
using (MemoryStream msDecrypt = new MemoryStream(bytes))
{
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged();
aesAlg.BlockSize = 128;
aesAlg.KeySize = 256;
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
aesAlg.Padding = PaddingMode.PKCS7;
// Get the initialization vector from the encrypted stream
aesAlg.IV = ReadByteArray(msDecrypt);
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
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();
}
}
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
return plaintext;
}
private static byte[] ReadByteArray(Stream s)
{
byte[] rawLength = new byte[sizeof(int)];
if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
{
throw new SystemException("Stream did not contain properly formatted byte array");
}
byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)];
if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
{
throw new SystemException("Did not read byte array properly");
}
return buffer;
}
I have used this example as a base, to read and write an encrypted cookie. Problem is that the decrypted string that gets returned contains invalid characters. i.e. the cookie value is
'MyValue'
and what gets returned is
Z!������3z�^��
This is the code I use:
protected void Button1_Click(object sender, EventArgs e)
{
HttpCookie myCookie = new HttpCookie("MyCookie");
string valString = "MyValue";
string keyAsString = "BJF8hXsXce7dhCWjGICNrnq1Gc8mWyMlODbiYvXTXCo=";
byte[] myKey = Convert.FromBase64String(keyAsString);
// Create a new instance of the AesManaged
// class. This generates a new key and initialization
// vector (IV).
using (AesManaged myAes = new AesManaged())
{
//Set default values as padding mode and ciphermode not supported in Silverlight
byte[] encrypted = EncryptStringToBytes_Aes(valString, myKey, myAes.IV);
myCookie.Value = Convert.ToBase64String(encrypted, 0, (int)encrypted.Length);
string roundtrip = DecryptStringFromBytes_Aes(Convert.FromBase64String(myCookie.Value), myAes.Key, myAes.IV);
}
//
myCookie.Expires = DateTime.Now.AddDays(1d);
Response.Cookies.Add(myCookie);
}
And the encryption/decryption functions are:
static byte[] EncryptStringToBytes_Aes(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("Key");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Mode = CipherMode.ECB;
// Create a decryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream 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);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
static string DecryptStringFromBytes_Aes(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("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.None;
aesAlg.Mode = CipherMode.ECB;
// 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();
}
}
}
}
return plaintext;
}
Looks like some sort of encoding issue. At first I thought the problem was reading the cookie value itself. But even if I try to decrypt the value just encrypted i.e.
string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key,
myAes.IV);
I still get the same issue.
Not sure if this will help but the main goal is to just READ the cookie. Initially, the cookie will get created from a 3rd party PHP app using a shared key (hence I used the ECB CipherMode). This code is just a sample to make sure that I can read encrypted cookies.
I'm looking to create a class that uses the .NET libraries that is compatible with OpenSSL. I'm aware there is an OpenSSL.Net wrapper, but I would prefer to avoid referencing 3rd party\unmanaged code. I'm not looking for a discussion of whether this is the right choice, but there are reasons for it.
Currently I have the following, which I believe should be compatible with OpenSSL - it effectively does what I believe OpenSSL does from the OpenSSL documentation. However even when just using this class to do both the encryption and decryption, I'm getting the following error:
[CryptographicException] Padding is invalid and cannot be removed.
I have stepped through the code and verified that the salt\key\iv are all the same during the encryption and decryption process.
See below for sample class and call to do encrypt decrypt. Any ideas or pointers would be welcome.
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];
Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 0, salt.Length);
Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length, 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];
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];
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 { Key = key, IV = iv, Mode = CipherMode.CBC, KeySize = 256, BlockSize = 256 };
// 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 { Key = key, IV = iv, Mode = CipherMode.CBC, KeySize = 256, BlockSize = 256};
// 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;
}
}
I then call this to test it:
Protection protection = new Protection();
const string passphrase = "<passphrase>";
string encrypted = protection.OpenSSLEncrypt(jobid, passphrase);
string decrypted = protection.OpenSSLDecrypt(encrypted, passphrase);
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;
}
}
Afraid there are issues with this latest code as well as OpenSSLDecrypt results in an error:
Padding is invalid and cannot be removed.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for >more information about the error and where it originated in the code.
Exception Details: System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.
It occurs at the closen paren of this code:
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))" in 'static string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv)
I had no idea it would be this hard to encrypt a blob of text from one computer, then send it for storage and decryption to another.
10 years after, I opened a similar question
It is possible to decrypt AES password protected file in C# / dotNet 5 encrypted by openssl enc -k?
and Evk did some minor changes of the above answer (SHA256 instead of MD5, deal with the Salted__string, use byte[]) to make it work as a replacement of openSSL 1.1.1 (a to l)