AES decryption, IV length - c#

I'm trying to decrypt a string, but I'm getting the
Specified initialization vector (IV) does not match the block size for this algorithm.
I've been searching SO and the web for a while and I understand my IV is 32 bytes and should be 16 bytes, but I can't figure out how to achieve it. The string to get has been encrypted using AES/CBC/PKCS5Padding and my code (well, actually I've found it somewhere in the web) is
var btKey = Encoding.ASCII.GetBytes("7c6e1257d0e81ff55bda80cc904365ae");
var btIV = Encoding.ASCII.GetBytes("cf5e4620455cd7190fcb53ede874f1a8");
aesAlg.Key = btKey;
aesAlg.IV = btIV;
aesAlg.Padding = PaddingMode.PKCS7;
// Create a decrytor to perform the stream transform.
var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(encodedTicketAsBytes))
{
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();
}
}
}
What I don't understand is the use of the aesAlg.Padding, to be honest I couldn't find yet an easy, to my understanding, example of this in C#.
Any help?,
Thanks!!

The key you have is almost certainly a bunch of hex values and not ascii characters. you are doing:
var btIV = Encoding.ASCII.GetBytes("cf5e4620455cd7190fcb53ede874f1a8");
which treats it like any other string and converts it to its binary ascii bytes. Those look like hex digits to me. Every 2 characters is a single byte value. You probably want something like
var btIV = new byte[] {0xcf,0x5e,0x46,0x20,0x45,0x5c,0xd7,0x19,0x0f,0xcb,0x53,0xed,0xe8,0x74,0xf1,0xa8};

Related

AES decrypt in C# when the encryption did not have a IV (init vector)

So we use the MySQL built in command to encrypt passwords called AES_ENCRYPT. Optionally there you can use an init vector. However, it is optional, so we didn't use one. When we decrypt in SQL, works just fine. However, if we would like to decrypt that byte array in C#, we cannot because the C# decryptor requires an IV. I tried null, but it just blows up.
In MySQL I can do this:
"SELECT CAST(AES_DECRYPT((SELECT Password FROM table WHERE RecordID = 1 }), 'KEY') AS CHAR(100));")
The data is stored in a blob data type. If I grab that data out in C# with an ORM or whatever, I need to decrypt that byte array. However, can't decrypt with the correct key because we never used a initialization vector.
C#
using (Aes aesFactory = Aes.Create())
{
aesFactory.Key = key;
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = aesFactory.CreateDecryptor(aesFactory.Key, aesFactory.IV);
// Create the streams used for decryption.
using (MemoryStream stream = new MemoryStream())
{
using (CryptoStream decryptStream = new CryptoStream(stream, decryptor, CryptoStreamMode.Write))
{
decryptStream.Write(encryptedText, 0, encryptedText.Length);
decryptedText = Encoding.ASCII.GetString(stream.ToArray());
}
}
}
return decryptedText;
The C# code might not be 100% accurate, I tried many different variations with streams, but the real problem is really with the CreateDecryptor function and the IV.

.NET Core AES CryptoStream cipher always 16 bytes

I'm trying to encrypt/decrypt a string with AES, using streams. I'm using the following code for encryption:
var provider = Aes.Create();
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using var encryptor = provider.CreateEncryptor();
using var memoryStream = new MemoryStream();
using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
using var streamWriter = new StreamWriter(cryptoStream, Encoding.UTF8);
streamWriter.Write(plainText);
cryptoStream.FlushFinalBlock();
var cipher = memoryStream.ToArray();
This successfully produces a byte array, though no matter the plaintext length, the cipher is always 16 bytes. From my understanding, with a block size of 16, a plaintext string with a length of 16 or more should result in a cipher that is larger than 16 bytes. Also, even for plaintext that is less than 16 bytes, decryption always results in an empty string.
var provider = Aes.Create();
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using var decryptor = _provider.CreateDecryptor(key, iv);
using var memoryStream = new MemoryStream(cipher);
using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
using var streamReader = new StreamReader(cryptoStream, Encoding.UTF8);
var plainText = streamReader.ReadToEnd();
My code is based on this sample in the Microsoft docs, though I'm calling cryptoStream.FlushFinalBlock(), after writing to the stream, although this isn't working as desired.
Calling FlushFinalBlock is not really necessary if you correctly close the stream. It might be useful if you want to write the last block (including padding) without closing it.
However, using the generic streaming API, preferably with the using statement and closing the stream should write any bytes left in the buffer + any padding that could be required.
Of course you should include any stream that writes to the CryptoStream in that using statement, otherwise they may have leftover data. Of course the receiving stream should only be closed after the data has been retrieved, for instance in case a MemoryStream is used.

Correct AesCryptoServiceProvider usage

I'm trying to write some straight forward encryption routines. Here's what I've been able to come up with based on searching the Web.
public string Encrypt(string plainText)
{
byte[] encrypted;
// Create an AesCryptoServiceProvider object
// with the specified key and IV.
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
msEncrypt.WriteByte((byte)aesAlg.Key.Length);
msEncrypt.Write(aesAlg.Key, 0, aesAlg.Key.Length);
msEncrypt.WriteByte((byte)aesAlg.IV.Length);
msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);
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 Convert.ToBase64String(encrypted);
}
public string Decrypt(string cipherText)
{
string plaintext = null;
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(cipherText)))
{
int l = msDecrypt.ReadByte();
byte[] key = new byte[l];
msDecrypt.Read(key, 0, l);
l = msDecrypt.ReadByte();
byte[] IV = new byte[l];
msDecrypt.Read(IV, 0, l);
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(key, 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();
}
}
}
return plaintext;
}
Two questions:
First, most of the examples I found hard coded the Key and IV. So what I'm doing is writing it to the encrypted bytes. This will make my encrypted data larger. Is there a better way?
Also, I'm not using any password. Would one use a password to generate a custom Key? And, if so, how would I know how long that key needed to be?
First, most of the examples I found hard coded the Key and IV. So what I'm doing is writing it to the encrypted bytes. This will make my encrypted data larger. Is there a better way?
Obviously you should not write the key to the unprotected stream, as the key needs to be shared or established in advance and remain secret. This sharing of the secret key can be performed in many ways, ranging from key agreement to key derivation, ratcheting, etc. etc.
Also, I'm not using any password. Would one use a password to generate a custom Key? And, if so, how would I know how long that key needed to be?
That's a possibility. However, remind yourself that passwords are often not that strong, so if password based encryption (PBE) can be avoided, it may be a good idea to do so.
If you derive a key from a password, you should use a Password Based Key Derivation Function (also sometimes called a password hash). In C# there is an implementation of PBKDF2 (badly) called Rfc2898DeriveBytes. By now that's not very state of the art either, but it should suffice - if you set a high enough iteration count anyway.
When you derive a key from a human remembered password then 128 bits is plenty. There is almost no way that the key can be found easier than the password that was used to derive it.

Rijndael PHP vs C# - Invalid KeySize in C# but not in PHP

I try to encrypt a string (json) with Rijndael in C# and come up with a string, which I can offer to a PHP web service. This web service in turn decodes the string using the IV and masterkey (known to them). I have to write the C# code that can talk to the PHP service, I do not control/own the PHP service.
The PHP code for encrypting is as follows:
function encrypt($plaintext) {
$masterkey = 'masterKeyOfLength29Characters';
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $masterkey, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
return base64_encode($iv.$crypttext);
}
$param = array("key" => "value");
$encryptedString = rawurlencode(encrypt(json_encode($param)))
The code above I'll have to convert to C#, so I can encrypt my JSON and offer it to the PHP web service.
There are two problems. The first was with the masterkey length, the second (might be related) is with the rawurlencode of the encrypted data (hard for me to test at this point).
var masterkey = "masterKeyOfLength29Characters";
var data = EncryptData(json, masterkey);
// Some code to URL Encode the data, I haven't gotten as far to test this
// since I can't encrypt with the key used in PHP, so I can't call the service
// to test the encoded string from my C# code.
data = HttpUtility.UrlEncode(data);
data = data.Replace("+", "%20");
public static string EncryptData(string json, string encryptionKey) {
Rijndael rj = Rijndael.Create();
rj.Mode = CipherMode.CBC;
rj.Padding = PaddingMode.PKCS7;
rj.BlockSize = 256;
rj.KeySize = 256;
rj.Key = Encoding.UTF8.GetBytes(encryptionKey); // ERROR here
rj.GenerateIV();
var encryptedJSON = EncryptStringToBytes(json, rj.Key, rj.IV);
var r1 = Convert.ToBase64String(rj.IV);
var r2 = Convert.ToBase64String(encryptedJSON);
return r1 + r2;
}
The EncryptStringToBytes does some checks and uses this code (plucked from the many examples on the internet):
using (Rijndael rijAlg = Rijndael.Create()) {
// Basically I do the same here as above, and I could also generate
// the IV here, but then I'd had to return it too. I know I can clean this
// code up quite a bit, but I'd rather focus on getting it to work first ;)
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.BlockSize = 256;
rijAlg.KeySize = 256;
rijAlg.Key = Key;
rijAlg.IV = IV;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
The error I'll get:
Specified key is not a valid size for this algorithm.
So, the problems in short:
1) How come the PHP code accepts the key of length 29 in the Rijndael 256 (CBC mode), and my C# doesn't? I've played around with the Mode, added the Padding later, set the KeySize (was 256 default already), and I just can't see what I'm doing wrong here.
2) When I use a key of length 32, this one is accepted and my code works. I can also decrypt it in C# (but can't test this in PHP). I would like to solve problem 1, and then continue on problem 2, but maybe someone can give me some understanding here. The encrypted string contains 1 '=' in the IV, and 2x '==' (at the end) in the encrypted json. I've read about padding and such, but I was wondering why no '=' signs are visible in the PHP examples I received. Again, maybe after fixing problem 1 this won't be an issue.
Many thanks for reading and I hope I'm not being too stupid here. After a day of trying yesterday I kind of get the feeling I've tried many different approaches and non seem to work.
Just thought I'd add a tiny bit to what #artjom-b has said.
Firstly, it does work :-)
But in addition you need to change your
rj.Padding = PaddingMode.PKCS7
to use
rj.Padding = PaddingMode.Zeros
Also, technically, your two functions aren't returning the same thing. The PHP returns base 64 of two concatenated bits of binary data whereas the C# returns a concatenation of separate b64 strings. The result will be different in the second half of the returned string.
EDIT: The rough and ready decryption routine:
public string DecryptRijndael(byte[] cipherText, string password, byte[] iv)
{
var key = new byte[32];
Encoding.UTF8.GetBytes(password).CopyTo(key, 0);
var cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 256;
cipher.BlockSize = 256;
cipher.Key = key;
cipher.IV = iv;
byte[] plain;
using (var decryptor = cipher.CreateDecryptor())
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
{
cs.Write(cipherText, 0, cipherText.Length);
cs.FlushFinalBlock();
plain = ms.ToArray();
}
}
}
return Encoding.UTF8.GetString(plain);
}
NB: All the caveats and warnings from Artjom B still apply.
You're using an old version of PHP which happily accepts keys that have an invalid length. Rijndael supports key sizes of 16, 24 and 32 bytes and nothing inbetween. The mcrypt extension in PHP silently pads the key with 0x00 bytes up to the next valid key size which is 32 bytes. You will have to do the same thing in C#:
byte[] key = new byte[32];
byte[] password = Encoding.UTF8.GetBytes(encryptionKey);
Array.Copy(password, key, password.Length);
rj.Key = key;
Keep in mind that in order to provide some security a key must have high entropy. A password is not a key and therefore doesn't provide much entropy, because of the limited character set and possible use words. Always derive a key from the password with available derivation functions such as Argon2, scrypt, bcrypt or PBKDF2 with a high cost factor/iteration count and a random salt.
You should also add authentication to your ciphertexts. Otherwise, an attacker might change the ciphertext without you knowing it. This either done by using an authenticated mode like GCM/EAX or running HMAC over the ciphertext to produce the authentication tag.

ASP.NET wrong string length after Decryption

I am getting length of the string wrong after using the following Decryption Method.
public static string DecryptRJ256(string prm_key, string prm_iv, string prm_text_to_decrypt) {
string sEncryptedString = prm_text_to_decrypt;
RijndaelManaged myRijndael = new RijndaelManaged();
myRijndael.Padding = PaddingMode.Zeros;
myRijndael.Mode = CipherMode.CBC;
myRijndael.KeySize = 256;
myRijndael.BlockSize = 256;
byte[] key = Encoding.ASCII.GetBytes(prm_key);
byte[] IV = Encoding.ASCII.GetBytes(prm_iv);
ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, IV);
byte[] sEncrypted = Convert.FromBase64String(sEncryptedString);
byte[] fromEncrypt = new byte[sEncrypted.Length];
MemoryStream msDecrypt = new MemoryStream(sEncrypted);
CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
return (Encoding.ASCII.GetString(fromEncrypt));
}
For example:
string ID = "yUFYhclPyPubnhMZ+SHJb1wrt44pao3B82jdbL1ierM=";
string finalID = DecryptRJ256(sKy, sIV, ID);
Response.Write(finalID); \\200905410 (**this is correct**)
Response.Write(finalID.Length); \\32 (**this should be 9**)
What am I doing wrong?
You are using zero padding. This pads the message with zero bytes until it reaches the block size (32 bytes in your case). Since zero padding is ambiguous (can't distinguish between an input that ended with zero bytes and the padding), .net doesn't remove it automatically.
So you have two choices:
Use PKCS7 padding for both encryption and decryption (that's what I recommend)
Manually strip all terminal zero bytes from the decrypted plaintext.
Your crypto isn't good either:
Keys and IVs should be binary, not ASCII (use base64 encoding here)
Using ASCII on the plaintext silently corrupts unicode characters - Use utf-8 instead
You need a new random IV for each encryption call and need to read it back during decryption
You should add a MAC, else active attacks (such as padding oracles) can often break it.
Use TransformFinalBlock instead of those memory streams.
Why use Rijndael256 over AES?
When I compiled this with symmetric decryptor object with the current Key, that is without key and IV, I get this as finalID.
???hV?9-2O?o?????}yl?????N?W
exactly 32 characters.
Refining the key and IV would help. I am not sure, but hope this might help.

Categories

Resources