I'm attempting to convert some working php code to c# in order to do aes decryption.
Working PHP code:
function convert_from_hex($h) {
$r="";
for ($i=0; $i<strlen($h); $i+=2)
if ((isset($h[$i])) && (isset($h[$i+1])))
$r.=chr(hexdec($h[$i].$h[$i+1]));
return $r;
}
function decryptAES($crypt_text, $key) {
$crypt_text=convert_from_hex($crypt_text); // convert from hex
$iv = substr($crypt_text, 0, 16); // extract iv
$crypt_text = substr($crypt_text, 16); // extract iv
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); // decrypt
#mcrypt_generic_init($td, $key, $iv);
$package = #mdecrypt_generic($td, $crypt_text);
mcrypt_generic_deinit($td); // close encryption
mcrypt_module_close($td);
$padqty=ord($package[strlen($package)-1]); // remove padding
return substr($package, 0, strlen($package)-$padqty);
}
Broken C# Code:
public string test()
{
string data = ConvertHex("149B56B7240DCFBE75B7B8B9452121B0E202A18286D4E8108C52DBB2149D820B980FFC7157470B9573AA660B2FAAB158E321023922191BCEA5D6E1376ABE6474");
string iv = data.Substring(0, 16);
string toDecrypt = data.Substring(16);
return AESEncryption.DecryptString(Encoding.Default.GetBytes(toDecrypt), Encoding.ASCII.GetBytes("C728DF944B666652"), Encoding.Default.GetBytes(iv));
}
static public string DecryptString(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;
byte[] binaryDecryptedData;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.KeySize = 128;
aesAlg.BlockSize = 128;
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 (MemoryStream srDecrypt = new MemoryStream())
{
var buffer = new byte[1024];
var read = csDecrypt.Read(buffer, 0, buffer.Length);
while (read > 0)
{
srDecrypt.Write(buffer, 0, read);
read = csDecrypt.Read(buffer, 0, buffer.Length);
}
csDecrypt.Flush();
binaryDecryptedData = srDecrypt.ToArray();
}
}
}
}
StringBuilder sb = new StringBuilder();
foreach (byte b in binaryDecryptedData)
sb.Append((char)b);
plaintext = sb.ToString();
return plaintext;
}
public string ConvertHex(String hexString)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexString.Length; i += 2)
{
string hs = hexString.Substring(i, 2);
sb.Append((char)Convert.ToUInt32(hs, 16));
}
return sb.ToString();
}
The correct output of the PHP code is:
Fail (1) Not a valid Request or Command.
The output of the C# code is:
²H,-§±uH¤¥±BÃrY¡|¡JJѾà`ªx"äommand
I'm guessing that I have some sort of encoding issue, although I've tried many different options without success. Both code snippets are running on a windows box, so I believe the default encoding is windows-1252.
Any suggestions would be appreciated.
Replacement for ConvertHex which fixed my issues (thanks to owlstead's help)
public static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
You are using the first 16 bytes of string data instead of 16 bytes of binary data. This is what is causing the main issue. You need first to convert hex to bytes and then strip off the first 16 bytes to use as IV. Your ConvertHex method (not shown) is broken, it should return a byte array. The fact that your decrypted plaintext does end correctly with "ommand" clearly indicates a problem with the IV value.
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 have been working on some cryptography and I need to create a new SHA-2 (sha256) system in which I generate new salt and hash , so the hash will be based upon the user typed password + newly created salt as I understand it. However, as I have downloaded a sample project, of all the sha-1 and sha-2 code that I have played around with, i have never seen the chinese symbols and so I'm a little concerned and confused
This is the code ,
// utilty function to convert byte[] to string
public static string GetString(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
incoming bytes do not have any strange characters so what is causing them?
screenshot
bytes parameter is byte[24] .... [0] = 20 , 1 = 101 etc.. just normal looking. So it seems to be occurring on this line
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
You just found some unicode characters. I far more often see--instead of unicode--UTF8 used, as so:
var toString = System.Text.Encoding.UTF8.GetString(yourByteArray);
var backToBytes = System.Text.Encoding.UTF8.GetBytes(toString);
EDIT
Here's an example console program that uses the AES encryption/decryption wrapper I coded after scouring the web for best-practice examples.
namespace Sandbox
{
class Program
{
static void Main(string[] args)
{
var originString = "This is some example text";
var originBytes = System.Text.Encoding.UTF8.GetBytes(originString);
var aes = new AesCryptoServiceProvider {KeySize = 256};
aes.GenerateIV();
aes.GenerateKey();
var vectorBytes = aes.IV;
var keyBytes = aes.Key;
//Not going to use these in the code, but here's how to get the values if you
//Want to save them off.
var vectorString = System.Text.Encoding.UTF8.GetString(vectorBytes);
var keyString = System.Text.Encoding.UTF8.GetString(keyBytes);
var encryptedBytes = EncryptionService.Encrypt(keyBytes, vectorBytes, originBytes);
var encyptedString = System.Text.Encoding.UTF8.GetString(encryptedBytes);
var decryptedBytes = EncryptionService.Decrypt(keyBytes, vectorBytes, encryptedBytes);
var decryptedString = System.Text.Encoding.UTF8.GetString(decryptedBytes);
Console.WriteLine($"Origin:\t\t {originString}");
Console.WriteLine($"Vector:\t\t {vectorString}");
Console.WriteLine($"Key:\t\t {keyString}");
Console.WriteLine($"Encrypted:\t {encyptedString}");
Console.WriteLine($"Decrypted:\t {decryptedString}");
Console.ReadLine();
}
}
public static class EncryptionService
{
public static byte[] Encrypt(byte[] key, byte[] vector, byte[] input)
{
if (key.Length == 0)
throw new ArgumentException("Cannot encrypt with empty key");
if (vector.Length == 0)
throw new ArgumentException("Cannot encrypt with empty vector");
if (input.Length == 0)
throw new ArgumentException("Cannot encrypt empty input");
var unencryptedBytes = input;
using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider { Key = key, IV = vector })
using (ICryptoTransform encryptor = aes.CreateEncryptor())
using (MemoryStream ms = new MemoryStream())
using (CryptoStream writer = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
writer.Write(unencryptedBytes, 0, unencryptedBytes.Length);
writer.FlushFinalBlock();
var byteArray = ms.ToArray();
if (byteArray.Length == 0)
throw new Exception("Attempted to encrypt but encryption resulted in a byte array of 0 length.");
return byteArray;
}
}
public static byte[] Decrypt(byte[] key, byte[] vector, byte[] encrypted)
{
if (key.Length == 0)
throw new ArgumentException("Cannot encrypt with empty key");
if (vector.Length == 0)
throw new ArgumentException("Cannot encrypt with empty vector");
if (encrypted == null || encrypted.Length == 0)
throw new ArgumentException("Cannot decrypt empty or null byte array");
byte[] unencrypted;
using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider { Key = key, IV = vector })
using (ICryptoTransform decryptor = aes.CreateDecryptor(key, vector))
using (MemoryStream ms = new MemoryStream(encrypted))
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
var decrypted = new byte[encrypted.Length];
var bytesRead = cs.Read(decrypted, 0, encrypted.Length);
return decrypted.Take(bytesRead).ToArray();
}
}
}
}
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 a customer encrypting a string in PHP with the following code:
$password = 'Ty63rs4aVqcnh2vUqRJTbNT26caRZJ';
$method = 'AES-256-CBC';
texteACrypter = 'Whether you think you can, or you think you can\'t--you\'re right. - Henry Ford';
$encrypted = openssl_encrypt($texteACrypter, $method, $password);
which results in this encrypted output: MzVWX4tH4yZWc/w75zUagUMEsP34ywSYISsIIS9fj0W3Q/lR0hBrHmdvMOt106PlKhN/1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0=
When I try to decrypt that string in C# it gives me a bunch of junk like so: Z�o�}'*2��I4y�J6S��
��xz���{9^�ED�fF
�}��گs�)�Q���i��$)�
I have tried changing the padding, using AesManaged instead of RijndaelManaged, changing the keysize, using a different key, etc. All result in either different junk strings or various exceptions. I must be missing something really basic here but I'm not sure what else to try at this point.
Here is my decryption code (that I shamelessly copied from another stackoverflow question: openssl using only .NET classes)
class Program
{
//https://stackoverflow.com/questions/5452422/openssl-using-only-net-classes
static void Main(string[] args)
{
var secret = "Ty63rs4aVqcnh2vUqRJTbNT26caRZJ";
var encrypted = "MzVWX4tH4yZWc/w75zUagUMEsP34ywSYISsIIS9fj0W3Q/lR0hBrHmdvMOt106PlKhN/1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0=";
var yeah = OpenSSLDecrypt(encrypted, secret);
Console.WriteLine(yeah);
Console.ReadKey();
}
public static 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();
}
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;
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged { Mode = CipherMode.CBC, Padding = PaddingMode.None, 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();
}
}
}
return plaintext;
}
}
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 };
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.