C# AES 256-Bit Decrypt given Encrypted Text and Secret - c#

Somebody asked me how I would decrypt a given AES 256-bit encrypted string if I knew the secret key. I'm not very familiar with encryption, so I sat down to look into the problem.
I found this example on MSDN, and tried to modify it to do only the Decrypt:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
internal class AesExample
{
public static void Main()
{
var encryptedString = "U2FsdGVkX1/cHT8XuHCfpw0AV4jpaO8JfLqUeCRJqjY=";
var secret = "SPARKY";
// I know this is not the correct way to get my input byte arrays...
// Just illustrating that I DO need byte arrays.
var encryptedBytes = Encoding.UTF8.GetBytes(encryptedString);
var secretBytes = Encoding.UTF8.GetBytes(secret);
try
{
using (var aes = new AesManaged())
{
aes.Key = secretBytes;
// Decrypt the bytes to a string.
var decryptedString = Decrypt(encryptedBytes, aes.Key, aes.IV);
//Display the original data and the decrypted data.
Console.WriteLine("Encrypted: {0}", encryptedString);
Console.WriteLine("Decrypted: {0}", decryptedString);
}
}
catch (Exception e)
{
Console.WriteLine("Error: {0}", e.Message);
}
}
private static string Decrypt(byte[] cipherText, byte[] key, byte[] iv)
{
// Declare the string used to hold
// the decrypted text.
string plaintext;
// Create an AesManaged object
// with the specified key and IV.
using (var aes = new AesManaged())
{
aes.Key = key;
aes.IV = iv;
// Create a decrytor to perform the stream transform.
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(cipherText))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
Of course as soon as I hit the following line, a CryptographicExcetion is thrown with the message "Specified key is not a valid size for this algorithm."
==> aes.Key = secretBytes
Someone suggested taking a SHA1 hash of the secret and trimming that to 20 byes. I tried that, and I started getting a new CryptographicException with the message "Length of the data to decrypt is invalid."
So, I have a few questions:
1) Is this even possible given only the encrypted text and secret key?
2) If so, are them some base assumptions one would need to make, like the CipherMode? I was reading that the ECB mode doesn't have a initialization vector. That's why I ask.
3) What would I need to do to put the inputs (encrypted text and secret key) into the correct Byte[] format for the decryption to work?
Thanks!

You probably need more information to make this work. To answer your specific questions:
Yes, except that you don't have the secret key. "SPARKY" is not a valid AES key, as DavidH mentions, though passwords are routinely used to derive secret keys through what are called key derivation functions. You could try running your password through Rfc2898DeriveBytes (a popular KDF in .NET) to derive different AES keys that might work, but it too takes parameters that you apparently don't have. You could also try various SHA hash digests of your password, though again 20 bytes is not a valid AES key - you need a 16, 24 or 32 byte key.
If you don't have an IV, then yes, you'll have to assume the encryption uses ECB. (But note that in general you should never use ECB mode.)
Your encrypted string appears to be encoded using base64. Converting it to a byte array is simple enough in .NET using Convert.FromBase64String(encryptedString);.
This sounds like a fun exercise, but you're probably just going to end up frustrated without a bit more information.

AES key lengths are 128, 192, and 256 bit depending on the cipher you want to use. You must ensure that your string is the appropriate length of bytes.

Related

AES one-shot API decryption throwing if it has not encrypted before

I'm attempting to roundtrip crypting some data with the new .NET 6 AES one-shot APIs.
The following example works as expected:
using System.Security.Cryptography;
string key = "1234567890123456";
byte[] keyBytes = Encoding.ASCII.GetBytes(key);
string dec = "Hello One-Shot API!";
byte[] decBytes = Encoding.ASCII.GetBytes(dec);
// Encrypt
using Aes aes = Aes.Create();
byte[] encBytes = aes.EncryptCbc(decBytes, keyBytes);
// Decrypt again
byte[] recBytes = aes.DecryptCbc(encBytes, keyBytes);
string rec = Encoding.ASCII.GetString(recBytes);
Debug.Assert(rec == dec);
But if I create a new Aes instance to call DecryptCbc on, it throws a System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed. exception:
...
// Encrypt
using Aes aes = Aes.Create();
byte[] encBytes = aes.EncryptCbc(decBytes, keyBytes);
// Decrypt again - this time with new instance
using Aes aes2 = Aes.Create();
byte[] recBytes = aes2.DecryptCbc(encBytes, keyBytes); // <- throws
...
Am I missing something here? Is there state set up in an Aes instance when calling EncryptCbc that doesn't exist when only using it for decryption? Do I have to keep the Aes instance around, and then how would I use it for decryption initially if I haven't encrypted with it before?
As mentioned in the comments, I mistook the IV parameter for the key, as I was porting old code not using any IV.
Once setting the key in the Aes.Key property, and using an empty IV (or even better, fixing the old code and using a proper IV), it worked as expected with every new instance.

Porting PHP decryption AES-256-CBC to C#

i have the following PHP decryption routine which is working flawlessly and need help converting it to c#. i have tried many approaches but none of them is working.
i have managed to match the hash function output between c# and php.
also matched the output of the conversion from and to base64.
PHP Code:
function decrypt($encrypted_txt, $secret_key, $secret_iv)
{
$encrypt_method = "AES-256-CBC";
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
$output = openssl_decrypt(base64_decode($encrypted_txt), $encrypt_method, $key, 0, $iv);
return $output;
}
secret_key= "t-3zafRa";
secret_key_hash = "d03a4d94b29e7f55c80726f1152dcebc9f03f4c698470f72083af967cf786b6b";
the problem is that the key hash is a 64 bytes which is invalid for the AES-256 but i am not sure how it's working in php and how the openssl_decrypt php function is dealing with the keys.
i have also tried to pass the MD5 of the key hash but also failed to decrypt.
byte[] asciiBytes = ASCIIEncoding.ASCII.GetBytes(keyhash);
byte[] hashedBytes = MD5CryptoServiceProvider.Create().ComputeHash(asciiBytes);
string keymd5 = BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(); //To match with PHP MD5 output
C# Hashing function:
static string sha256(string randomString)
{
var crypt = new System.Security.Cryptography.SHA256Managed();
var hash = new System.Text.StringBuilder();
byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
foreach (byte theByte in crypto)
{
hash.Append(theByte.ToString("x2"));
}
return hash.ToString();
}
C# Decryption routine:
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;
}
any help or ideas are highly appreciated.
openssl_decrypt simply takes as many bytes for the key as required for the algorithm. As your algorithm is "AES-256-CBC" is uses 32 bytes (256 bits), as AES-256 is defined to as AES with a 256 bit key (and 14 rounds, rather than 10 or 12).
The way PHP does this is either by adding 00 valued bytes to the right in case the key is too small, or - as in your case - by simply ignoring the bytes after the 32st one. That's not a good way to behave of any kind of cryptographic library, especially for a high level language like PHP, but the OpenSSL wrapper library does it anyway.
So you have to extract the first 32 bytes from the hex encoded key and use that as key in C# to be compatible. Using different hash functions is of course not going to work, MD5 and SHA-256 are entirely incompatible (by design). Of course, you now have 16 hex encoded bytes left, which means you are using AES-256 with 128 bit keys, leaving you with 128 bit security. And yes, you need to use PKCS#7 padding in C#.
Note that using CBC with a static IV is not secure. Using CBC mode for transport mode security is not secure. Using SHA-256 or any normal hash over a password or key with little entropy is not secure. Storing keys in strings is generally not secure.
Getting crypto working is hard enough; getting it secure is much harder and requires understanding what the heck you're doing in the first place. You need to start off with a good protocol for your specific use case (and that's skipping a few steps already).

String encryption in Objective-C decrypt in C#

I am building a iPhone app which uses a c# web service. My iPhone app takes in some data and encrypts it and passes it to the web service. How do I decrypt the data in C#?
My iPhone app contains the following code:
NSString *pString = #"Some string to be encoded";
NSString *key = #"My encryption key";
NSData *pData = [pString dataUsingEncoding:NSUTF8StringEncoding];
pData = [pData AES256EncryptWithKey:key];
NSString *pID = [pData base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];
EDIT: The data is already stored in the web service so I can't readily change the encryption approach. The C# application is not on the server so there is no possibility of compromising the key.
I have tried the following C# code to decrypt the data:
static string DecryptString(string encryptedText, string key)
{
byte[] encryptedString = Convert.FromBase64String(encryptedText);
byte[] encryptionKey = Encoding.UTF8.GetBytes(key.Substring(0, 32));
using (var provider = new AesCryptoServiceProvider())
{
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
provider.Key = encryptionKey;
using (var ms = new MemoryStream(encryptedString))
{
// Read the first 16 bytes which is the IV.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
using (var decryptor = provider.CreateDecryptor())
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
However, I get the following exception:
System.Security.Cryptography.CryptographicException was unhandled
HResult=-2146233296 Message=Padding is invalid and cannot be
removed.
The encryptedText received by DecryptString is 80 bytes in length.
The sample ObjC code uses by default CBC modem, PKCS#7 padding and a default iv of 16 0x00 bytes.
The C# also uses CBC mode and PKCS#7 padding. The decryption code expects a 16-byte iv pre-pended to the encrypted data and that does not exist.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
This needs to be changed so that iv is set to an array of 16 0x00 bytes and the ms.Read(iv, 0, 16) statement needs to be deleted so the decrypt function gets all of the encrypted data.
Notes:
Using a devault anything in encryption is a bad idea, always provide the correect length data.
Authentication of the encrypted data needs should be added so that it can be determined if there an incorrect key or the data has been tampered with.
There really should be a version number and a random IV used and prepended to the encrypted so you should really consider correcting this. This demonstrates why a version number generally needs to be provided and used.
RNCryptor covers the above issues.
The handling of the encryption key also needs to be considered so that is is as secure as necessary.
You need to first decode the base-64 encoded string to a byte[] - see Convert.FromBase64String(). Then you need to use the Aes class to decrypt it - there's an example on its documentation page.

aes decryption not working properly sometime

I am using aes for encryption/decryption of the text but sometime its giving me exact value after decryption while some times i am getting error. I referred to different answers over but didn't get the root cause of my problem .
private static string DecryptStringFromBytes(byte[] cipherText, byte[] key, byte[] iv)
{
// Declare the string used to hold the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new System.Security.Cryptography.RijndaelManaged())
{
//Settings
rijAlg.Mode = System.Security.Cryptography.CipherMode.CBC;
rijAlg.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
rijAlg.FeedbackSize = 128;
rijAlg.Key = key;
rijAlg.IV = iv;
// Create a decrytor to perform the stream transform.
var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
try
{
// Create the streams used for decryption.
using (var msDecrypt = new System.IO.MemoryStream(cipherText))
{
using (var csDecrypt = new System.Security.Cryptography.CryptoStream(msDecrypt, decryptor, System.Security.Cryptography.CryptoStreamMode.Read))
{
using (var srDecrypt = new System.IO.StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
catch
{
plaintext = "keyError";
}
}
return plaintext;
}
It throws error "Padding is invalid and cannot be removed"
I seen some suggestion like to remove padding but it didn't seems proper solution.
I am not able to find the cause behind this as sometimes it runs perfectly without throwing error .
Any help or suggestion is really appreciated.
For Encryption - The encryption is being done on to client side in js and passing encryped text to server.
var key = CryptoJS.enc.Utf8.parse("16 digit number here");
var iv = CryptoJS.enc.Utf8.parse("16 digit number here");
var EncryptedString = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse("entered string to encrypt"), key,
{ keySize: 128 / 8, iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
By using a similar encryption routine in .NET to the decryption function you give I was able to successfully round-trip plaintext to ciphertext and back to plaintext, so it seems that the decryption function itself is ok. It therefore seems very likely that the key and/or IV you're using to encrypt does not match byte-for-byte with the values you're using when decrypting.
Given that your encryption code is using the UTF-8 encoded version of string values to form the key and IV, it would be worth doing the same in your decryption code (using Encoding.UTF8.GetBytes()).
However, it would be worth noting that whilst this might resolve the immediate issue, it is in itself a bad practice to use string values directly for keys without some form of key-derivation process (e.g. Rfc2898DeriveBytes), and IVs should be generated randomly for every application of the encryption function. Those are just a few issues with your use of cryptography (and are independent of whether the code works or not).

Is this wrapper around AesManaged ok?

I need to encrypt/decrypt some strings. I've build my wrapper class according to the msdn documentation but with some changes.
Since I want to encrypt/decrypt data with a given string/passphrase, I don't use AesManaged for creating a key. (The user should be able to encrypt/decrypt with a key he enters, and therefore I cannot use the key from AesManaged and I cannot save the key).
I instead create the key by using Rfc2898DeriveBytes (PBKDF2) with a given salt. The given salt is used since I do not store the key and I think because of this, the salt must be always the same.
I then create an IV, encrypt the given string and concatenate the IV and the encrypted string. This will then eventually got saved in a file. This means the IV gets save together with the encrypted data.
Questions:
Is it ok to store the IV together with the encrypted data?
Is there another way to create the key without using the same salt everytime(Based on a given passphrase)?
Is this encryption done using AES128 or AES256?
Will the IV be always 16 bytes, or can this change?
static void Main(string[] args)
{
const string stringToEncrypt = "String to be encrypted/decrypted. Encryption is done via AesManaged";
const string password = "m1Sup3rS3cre!Password";
string encrypted = EncryptString(stringToEncrypt, password);
string roundtrip = DecryptStringFromBytes_Aes(encrypted, password);
Console.WriteLine("Original: {0}", stringToEncrypt);
Console.WriteLine("Round Trip: {0}", roundtrip);
Console.ReadLine();
}
static string EncryptString(string plainText, string password)
{
string encryptedString;
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = PasswordAsByte(password);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
var encrypted = msEncrypt.ToArray();
encryptedString = Encoding.Default.GetString(aesAlg.IV);
encryptedString += Encoding.Default.GetString(encrypted);
}
}
}
return encryptedString;
}
static string DecryptStringFromBytes_Aes(string cipherText, string password)
{
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = PasswordAsByte(password);
aesAlg.IV = Encoding.Default.GetBytes(cipherText).Take(16).ToArray();
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
var encryptedByteArray = Encoding.Default.GetBytes(cipherText).Skip(16).ToArray();
using (MemoryStream msDecrypt = new MemoryStream(encryptedByteArray))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
return srDecrypt.ReadToEnd();
}
}
}
}
}
private static byte[] PasswordAsByte(string password)
{
byte[] salt = Encoding.Default.GetBytes("foobar42");
Rfc2898DeriveBytes passwordBytes = new Rfc2898DeriveBytes(password, salt);
return passwordBytes.GetBytes(32);
}
No, this is not okay.
1) You're using Encoding.Default in various places. Don't do that - it means you're at the whim of the platform you're on. Always use an explicit encoding, ideally UTF-8 in most cases.
2) You're using Encoding.GetString / Encoding.GetBytes to convert arbitrary binary data to a string and back. That's almost bound to lose data. (It happened to succeed on my machine, but it really depends on the encoding - and it's fundamentally a bad idea.) Encoding is designed for data which is inherently text data, and you're just applying an encoding one way or the other. Your encrypted data is inherently binary data. Use Convert.ToBase64String and Convert.FromBase64String instead.
For your other questions:
Yes, it's okay to store the IV with the encrypted data, as far as I know.
You could use the same approach for the password: generate a different salt each time, and store that with the encrypted text. Not sure whether that's generally recommended or not, I'm afraid.
I believe you're controlling whether the key size is 128 or 256 bits, with your call to passwordBytes.GetBytes(32) - that's a 256-bit key, so it's AES256.
I believe the IV size for AES is always 16 bytes (128 bits)
Normally salt is used together with cryptographic hashing of say passwords to protect against dictionary attacks. To get the same kind of protection for symmetric encryption with AES you should use a random initialization vector. So when you encrypt create a random IV and prepend it to the message (in cleartext). When you decrypt get the IV from the encrypted message and use it to decrypt the message. Then the ciphertext of the same message encrypted with the same key will be different.
So, yes, it is OK to store the IV together with the encrypted data.
You do not need a different salt every time because the purpose of the random IV is similar in how salt makes dictionary attacks on hashes harder.
AES can use key sizes of 128, 192 or 256 bits so to use AES 256 you need a 256 bit key (32 bytes) which is what you use.
AES uses a 128 bit block which requires a 128 bit IV (or 16 bytes).
Is it ok to store the IV together with the encrypted data?
Yes, it is ok. Moreover, you're using AesManaged without explicit setting of Mode - it this case mode is CBC, and in CBC mode IV should preceed cyphertext.
Is there another way to create the key without using the same salt everytime(Based on a given passphrase)?
Rfc2898DeriveBytes is pretty standard way to derive key from text password. There is no need to reinvent way of deriving key from password, just use Rfc2898DeriveBytes as you're doing it now.
Is this encryption done using AES128 or AES256?
It is AES256 since you're using 32-byte password.
Will the IV be always 16byte, or can this change?
The size of the IV property must be the same as the BlockSize property divided by 8. So it is 16 for 128-bit blocks.

Categories

Resources