So I have this piece of php code that I\'m not allowed to modify for now, mainly because it\'s old and works properly.
Warning! Very bad
Well, I managed to solve this in a not so bad manner.
Following @ste-fu advice I tried to get rid of every piece of encoding that I could find.
But I still wasn't anywhere close to getting the Key and IV right. So I did some testing with php. I made a var_dump
of the IV and got a neat 16 length array with bytes shown as integers.
var_dump
result array starts allways in [1]. Be advised.
$iv = substr(hash('sha256', $param2), 0, 16);
$byte_array = unpack('C*', $iv);
var_dump($byte_array);
That peaked my interest, thinking that if I had the hex string right I should be able to convert each char in the string to it's equivalent byte. Lo and behold, I made this function in C#:
private byte[] StringToByteArray(string hex)
{
IList<byte> resultList = new List<byte>();
foreach (char c in hex)
{
resultList.Add(Convert.ToByte(c));
}
return resultList.ToArray();
}
And this worked very well for the IV. Now I just had to do the same thing for the key. And so I did, just to find that I had a 64 length byte array. That's weird, but ok. More testing in php.
Since it does make sense that the php Key behaves the same as the IV I didn't get how the openssl encryption functions allowed a 64 length Key. So I tryed to encrypt and decrypt the same data with a Key made from the first 32 chars. $ky = substr(hash('sha256', $param1), 0, 32);
And it gave me the same result as with the full Key. So, my educated guess is that openssl just takes the bytes necesary for the encoding to work. In fact it will take anything since I tested with substrings of 1, 16, 20, 32, 33 and 50 length. If the length of the string is bigger than 32 the function itself will cut it.
Anyway, i just had to get the first 32 chars of the Key hex and use my new function to convert them into a byte array and I got my Key. So, the main C# code right now looks like this:
public CryptoHelper(string keyFilePath, string ivFilePath)
{
//Reading bytes from txt file encoded in UTF8.
byte[] key = File.ReadAllBytes(keyFilePath);
byte[] iv = File.ReadAllBytes(ivFilePath);
IV = StringToByteArray(GetStringHexSha256Hash(iv).Substring(0, 16));
Key = StringToByteArray(GetStringHexSha256Hash(key).Substring(0, 32));
//Tests
var st = Encrypt("abcdefg");
var en = Decrypt(st);
}
//Convert each char into a byte
private byte[] StringToByteArray(string hex)
{
IList<byte> resultList = new List<byte>();
foreach (char c in hex)
{
resultList.Add(Convert.ToByte(c));
}
return resultList.ToArray();
}
private string GetStringHexSha256Hash(byte[] source)
{
string result = "";
try
{
using (SHA256 sha256Hash = SHA256.Create("SHA256"))
{
//Get rid of Encoding!
byte[] hashedBytes = sha256Hash.ComputeHash(source);
for (int i = 0; i < hashedBytes.Length; i++)
{
result = string.Format("{0}{1}",
result,
hashedBytes[i].ToString("x2"));
}
}
}
catch (Exception)
{
throw;
}
return result;
}
private string Encrypt(string source)
{
try
{
string result = "";
using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
{
byte[] sourceByteArray = Encoding.UTF8.GetBytes(source);
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
byte[] encriptedSource = encryptor.TransformFinalBlock(sourceByteArray, 0, sourceByteArray.Length);
result = Convert.ToBase64String(encriptedSource);
//Nothing to see here, move along.
result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result));
}
}
return result;
}
catch (Exception ex)
{
throw;
}
}
private string Decrypt(string source)
{
try
{
string result = "";
byte[] sourceByte = Convert.FromBase64String(source);
byte[] sourceFreeOfBase64 = Convert.FromBase64String(Encoding.UTF8.GetString(sourceByte));
byte[] resultByte;
int decryptedByteCount = 0;
using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
{
using (ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV))
{
using (MemoryStream memoryStream = new MemoryStream(sourceFreeOfBase64))
{
using (CryptoStream cs = new CryptoStream(memoryStream, AESDecrypt, CryptoStreamMode.Read))
{
resultByte = new byte[sourceFreeOfBase64.Length];
//Now that everything works as expected I actually get the number of bytes decrypted!
decryptedByteCount = cs.Read(resultByte, 0, resultByte.Length);
}
}
}
//Nothing to see here, move along.
result = Encoding.UTF8.GetString(resultByte);
//Use that byte count to get the actual data and discard the padding.
result = result.Substring(0, decryptedByteCount);
}
return result;
}
catch (Exception ex)
{
throw;
}
}
I still need to clean all the code from my class from all the testing I did, but this is all that's needed to make it work. I hope this helps anybody with the same problem that I faced.
Cheers.
The hash function will return the same number of bytes whatever the input, so I suspect it is a difference in how you convert the resulting byte[] back to a string in C# compared to the PHP implementation.
The PHP docs say that the hash function output the result in lower case hexits. This is absolutely not the same as the UTF8 encoding that you are returning.
There isn't a built in framework way to do this, but check out this SO question for several different methods.
Also worth noting is that you do not specify the Padding
value in your C# code. AES-CBC is a block cipher and will need to use some padding scheme. You may well get a padding exception. I think that it will need Zero padding (docs)
aes.Padding = PaddingMode.Zeros
but I'm not 100%