Send C# AES encrypted string to CyptoPP C++ program to decrypt - c#

I'm trying to send an encrypted string via a tcp connection from a C# program to a C++ program. The c++ program crashes in crypto++ when it tries to decrypt the string. I can see in the debugger the string is mostly decoded but does not terminate properly. For example if I send "Hello world" it gets decrypted as "Hello world%%##$##" (garbage at the end)
Here's how I encrypt
//Create byte arrays to hold original, encrypted, and decrypted data.
byte[] dataToEncrypt = ByteConverter.GetBytes(data);
byte[] key = new byte[16];
for (int i = 0; i < 16; ++i)
{
key[i] = 1;
}
byte[] iv = new byte[16];
for (int i = 0; i < 16; ++i)
{
iv[i] = 1;
}
RijndaelManaged myRijndael = new RijndaelManaged();
myRijndael.Key = key;
myRijndael.IV = iv;
byte[] encrypted = encryptStringToBytes_AES(data, myRijndael.Key, myRijndael.IV);
// sends the byte array via active tcp connection
_transport.SendEncryptedData(encrypted);
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("IV");
// Declare the stream used to encrypt to an in memory
// array of bytes.
MemoryStream msEncrypt = null;
// 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();
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create an encrypto 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);
}
}
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
// Return the encrypted bytes from the memory stream.
return msEncrypt.ToArray();
}
Here's the C++ side decrypting with Crypto++
byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ];
byte iv[ CryptoPP::AES::BLOCKSIZE ];
::memset( key, 0x01, CryptoPP::AES::DEFAULT_KEYLENGTH );
::memset( iv, 0x01, CryptoPP::AES::BLOCKSIZE );
std::string decryptedtext;
CryptoPP::AES::Decryption aesDecryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption( aesDecryption, iv );
CryptoPP::StreamTransformationFilter stfDecryptor(cbcDecryption, new CryptoPP::StringSink( decryptedtext ) );
// CRASHES IN .PUT(...)
stfDecryptor.Put( reinterpret_cast<const unsigned char*>(data ), len + 1);
stfDecryptor.MessageEnd();

Your problem is because of 1 of 2 things, perhaps both.
The C# stream writer isn't writing out a null byte. When the C++ code reads in the data, the string is not null terminated. I haven't tested out the code you've given, but this would seem to be expected behavior.
The assumed methods of padding (or lack thereof) may be different between the C# implementation and the Crypto++ implementation. AES in CBC mode can only encrypt or decrypt blocks that are multiples of the block size. In AES, the block size is 128-bits or 16 bytes.
Wikipedia has a great explanation of the various block cipher modes here. The typical padding for CBC is PKCS7, which is described here.
I don't know the internals of either implementation well enough to know what the default methods of padding are, or if they leave that up to the user.

Related

How do you create SHA-2 and AES encrypted string to use in HTTP request header in .NET?

I am trying to write client code that requests for information from an external API over the web.
The API is simple enough (to me) except for the stipulations of how to generate the authorisation key.
First some context:
There are 6 string values required to start with:
token
password
devId
salt
orgId
givenKey
Now for the encryption stuff. First up SHA2.
hashedString = SHA2(token + password + devId)
Followed by AES.
authKey = AES(salt + orgId + "=" + hashedString)
The AES parameters are specified as follows:
Mode = ECB
Padding = PKCS5Padding
Secret Key = givenKey
My problem is that I know next to nothing about cryptography.
Below is the code I have attempting to accomplish the above.
// Generate Authorisation key
byte[] fieldsBytes = Encoding.ASCII.GetBytes(token + password + devId);
byte[] keyBytes = Encoding.ASCII.GetBytes(secretKey);
SHA512 shaM = new SHA512Managed();
string hashedFields = Encoding.ASCII.GetString(shaM.ComputeHash(fieldsBytes));
byte[] encryptedBytes = EncryptStringToBytes_Aes(salt + orgId + "=" + hashedfields,
keyBytes, keyBytes);
string encryptedString = Encoding.ASCII.GetString(encryptedBytes);
private 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("IV");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// 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;
}
This code gets a "401" from the external service.
My first issue is that there does not seem to be a NET method named SHA2. The closest I could find is SHA512 and I am not sure if SHA512 is a .NET implementation of SHA2.
Secondly, padding for AES has been specified as PKCS5Padding but again the closest (naming-wise) I could find is PKCS7 which I am not sure about how similar it is to PKCS5.
There is also the matter of an Initialisation Vector (IV), which the AES parameters don't specify but I see C# AES examples including. In the code, I have set it to have the same value as the Key (which I believe
is what the API calls "secret key") out of sheer desperation but I have tried making the request without setting IV to any value and still get back a 401.
I should probably also mention that I am using ASCII encoding to convert to-and-from bytes because I first tried using UTF8 but when it came to actually making the HTTP request, I was getting an exception
saying that header values (remember we are generating an authorisation key that will be tucked in a HTTP request header) should only be encoded in ASCII.
Any help pointing me in the right direction will be immensely appreciated as I am woefully out of my depth with this cryptography stuff.
Don't worry, crypto can feel overwhelmingly complicated. I think you're close.
SHA2 is a family of hash functions. In practice, "SHA2" usually means SHA2-256 or occasionally SHA2-512. My guess is that your external API is probably using the more common SHA2-256.
This answer on crypto.stackexchange explains that PKCS#5 is essentially a subset of PKCS#7. I'd be willing to bet that the API you're calling made the same mistake described in that answer and should really be calling it PKCS7Padding. Your code is fine!
The IV isn't the same thing as the secret key (or just the "key" for AES). The IV should be random for every encryption run. You aren't supposed to derive it from the input plaintext or the input key. Fortunately, AesCryptoServiceProvider.GenerateIV() will generate one for you. It's up to you to prepend it to your output stream, though.
Using Encoding.ASCII.GetBytes() to get the plaintext and secret key bytes makes sense to me. I don't think that's causing a problem.
Stealing from this excellent answer to a similar question (go give them a vote!), I'd try code like this:
static byte[] AesEncrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
// prepend IV to data
cipherStream.Write(iv);
tBinaryWriter.Write(data);
tCryptoStream.FlushFinalBlock();
}
var cipherBytes = cipherStream.ToArray();
return cipherBytes;
}
}
}
Unless there's something else weird going on with this API, I'd guess it's probably #3 above that is causing your request to fail.
I finally managed to get it to work.
A big part of the problem was that API documentation did not specify that hashedString has to
be composed of hexadecimal characters.
It also didn't say that authKey should be a base64 string.
I don't know if this is so standard that it goes without saying but knowing this could have
saved me hours of agony. I was converting the hashed/encrypted bytes back to ASCII
and much of it was unprintable characters that were causing the server to send back
a HTTP response with status 400 BAD_REQUEST.
It also required hashedString to be hashed using SHA256 but the documentation does not mention it.
Thanks to #Nate Barbettini's answer for steering me in the right direction on this.
Also, it appears that AES ECB mode does not require an initialisation vector unlike other
modes like CBC so I didn't specify an IV.
For padding I specified PKCS7 (again thanks to #Nate Barbettini for that).
With that here's the code that finally worked out for me.
string hashedFields = ComputeSha256HashHex(authToken + password + devId);
string encryptedString = AesEncryptToBase64String(saltString + orgId + "=" + hashedFields, secretKey);
private string AesEncryptToBase64String(string plainText, string key)
{
// Convert string arguments into byte arrays
byte[] keyBytes = Encoding.ASCII.GetBytes(key);
byte[] plainTextBytes = Encoding.ASCII.GetBytes(plainText);
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("key");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = keyBytes;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// 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 bytes as Base 64 string
return Convert.ToBase64String(encrypted);
}
private string ComputeSha256HashHex(string plainText)
{
using (SHA256 sha256Hash = SHA256.Create())
{
// ComputeHash - returns byte array
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(plainText));
// Convert byte array to a string
return BytesToHexString(bytes);
}
}
private string BytesToHexString(byte[] bytes)
{
// Convert byte array to a string
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.Append(bytes[i].ToString("x2"));
}
return builder.ToString();
}

AES CTR decryption in C#

I've been given the task of decrypting some data in C# that was originally encrypted using PHP. Below is the PHP code
$opts = OPENSSL_RAW_DATA;
$res = openssl_decrypt($raw, 'aes-256-ctr', $keyhash, $opts, base64_decode($iv));
$raw is a byte array with a length of 312, $keyhash is 32 bytes and $iv is 16 bytes.
My problem is when I duplicate this code in C# I receive the following CryptographicException -
The input data is not a complete block.
Not being a crypto guy, I've tried many, many C# examples to try to get this to work, but pretty much always end up with the same error. This is one sample of the C# code I've tried.
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("IV");
// 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.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();
}
}
}
}
return plaintext;
}
I've implemented the code in PHP and it works fine. I've also checked all the input byte arrays and they all exactly the same for both PHP and C#. In desperation I even implemented this in Java and again it works no problem.
SecretKeySpec keySpec = new SecretKeySpec(keyHash, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte [] original = cipher.doFinal(encData);
String plaintext = new String(original);
So what's up with the C# code and how can I make it work?
Thanks in advance.
OK. This is a duplicate question which has already been answered. I just missed the easy answer, which is here - Aes128CounterMode.cs. And this code will generate the correct result -
byte[] plainText = new byte[encData.Length];
Aes128CounterMode am = new Aes128CounterMode(iv);
ICryptoTransform ict = am.CreateEncryptor(keyHash, null);
ict.TransformBlock(encData, 0, encData.Length, plainText, 0);
return Encoding.UTF8.GetString(plainText);

C# AES encryption performs only once

I have a C# WinForms app, with AES encryption/decryption. The encryption (decryption) itself works fine, but only once. If I try to encrypt another string, I get a CryptographyException saying the padding is invalid. Based on some research, it seems I forgot to close some stream, but I can't figure out what it is. Does anyone know how to fix this?
Here is the code I use (I believe I found it earlier somewher on SO):
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 Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.PKCS7;
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.
//csDecrypt.FlushFinalBlock(); causes an exception
plaintext = srDecrypt.ReadToEnd();
//csDecrypt.Flush(); experimental solution, doesn't work either
srDecrypt.Close();
}
csDecrypt.Close();
}
msDecrypt.Close();
}
}
return plaintext;
}
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 Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.PKCS7;
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);
swEncrypt.Close();
}
csEncrypt.Close();
msEncrypt.Close();
encrypted = msEncrypt.ToArray();
//csEncrypt.FlushFinalBlock(); causes an exception saying it was already called
}
}
}
Here is a sample of code I use to test the ecnryption. Performs correctly, but only once...
The key and IV are converted into Unicode string so that the user is able to save it and use it later for decryption.
private void button7_Click(object sender, EventArgs e)
{
string aesKey = "";
string aesIV = "";
string ciphered = "";
string deciphered = "";
using (Aes myAes = Aes.Create())
{
myAes.GenerateKey();
myAes.GenerateIV();
aesKey = Encoding.Unicode.GetString(myAes.Key);
aesIV = Encoding.Unicode.GetString(myAes.IV);
ciphered = Encoding.Unicode.GetString(EncryptStringToBytes_Aes(".ahoj.", myAes.Key, myAes.IV));
byte[] deKey = Encoding.Unicode.GetBytes(aesKey);
byte[] deIv = Encoding.Unicode.GetBytes(aesIV);
deciphered = DecryptStringFromBytes_Aes(Encoding.Unicode.GetBytes(ciphered), deKey, deIv);
MessageBox.Show("key: " + aesKey + "\niv: " + aesIV + "\ndekey: " + Encoding.Unicode.GetString(deKey) + "\ndeIv: " + Encoding.Unicode.GetString(deIv) + "\nDeciphered: " + deciphered);
}
}
Instead of using GetBytes and GetString from the unicode encoding, I would recommend base 64 encoding the bytes.
You can do base 64 encoding using Convert.ToBase64String() and Convert.FromBase64String.
One problem with just getting the unicode string from a generated byte array is that there is no guarantee that all of the characters are printable characters. For example, if you have a 0 in the byte array, that will be a null control character.
I recently ran into this myself. I am making use of the 'using' statements in your first code block but I never call .Close() and I am not running into the issue of only being able to run once.
To solve issues with padding on decrypt and issue of non-printable characters, I am explicitly setting the Padding as you did in the first code block but I am using Encoding.Default.GetBytes(cipherText); and the GetString method of the same encoding so it uses Window's default Extended ASCII encoding so it will recognize all the characters.

Why does decryption not return the expected value?

When using c# rijndael to decrypt a string that was previously encrypted and saved in sql server ce, I get nothing back from decryption. I can tell from debugging and checking the database that the decrypted string appears to be saved as expected with different sets of nonsense for different input strings so I assume that the problem is in the decryption code. I also confirmed that cipherText input parameter got the correct number of bytes after being retrieved from the db and re-made into a byte array. I used the example found at:
http://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael%28v=vs.110%29.aspx to do this, with some modifications.
static string DecryptStringFromBytes(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 Rijndael object
// with the specified key and IV.
using (Rijndael rijAlg = Rijndael.Create())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
rijAlg.Padding = PaddingMode.PKCS7;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
var plainBytes = new byte[cipherText.Length];
int plainByteCount;
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
plainByteCount = csDecrypt.Read(plainBytes, 0, plainBytes.Length);
using (StreamReader srDecrypt = new StreamReader(csDecrypt, System.Text.Encoding.Unicode))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plainText = srDecrypt.ReadToEnd();
}
}
}
plainText = Encoding.Unicode.GetString(plainBytes, 0, plainBytes.Length);
}
return plainText;
}
At
plainText = srDecrypt.ReadToEnd();
plainText gets the value of "" where I would expect it to be the decrypted word.
Finally, at
plainText = Encoding.Unicode.GetString(plainBytes, 0, plainBytes.Length);
plainText becomes: "\0\0\0\0\0\0\0\0"
I have tried various padding options, both at the encryption and decryption stages, and can see that doing so has an effect but it doesn't solve the problem. Without setting padding 0 bytes get encrypted in the first place, where it should be 16 bytes. I also tried different ways of calling flush and flushfinalblock. I am storing the key/iv in plain text files (I am aware that may not be best practice for security, my goal for now is only to learn more about this topic).
Please help me find what I am doing wrong here.
I have encountered the similar problem in VB.NET and discovered that the variables to be used in both encryption and decryption process should be the same. Giving the encryption and decryption functions their own local variables would definitely cause the decryption function return a wrong value. Try making the key and the iv a global variable instead of a local variable.

Decrypt string in C# that was encrypted with PHP openssl_encrypt

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

Categories