I am trying to encrypt and decrypt using AES256 algorithm, I have the following code.
public string Encrypt(string dataToEncrypt, string key)
{
if (dataToEncrypt == null || dataToEncrypt.Length <= 0)
throw new ArgumentNullException("plainText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("Key");
var iv = GenerateRandomNumber(16);
using (var aes = new AesCryptoServiceProvider())
{
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = Convert.FromBase64String(key);
aes.IV = iv;
byte[] data = Encoding.UTF8.GetBytes(dataToEncrypt);
using (var memoryStream = new MemoryStream())
{
var cryptoStream = new CryptoStream(memoryStream,
aes.CreateEncryptor(),
CryptoStreamMode.Write);
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return Convert.ToBase64String(memoryStream.ToArray());
}
}
}
public string DecryptReturnsString(string dataToDecrypt, string key)
{
if (dataToDecrypt == null || dataToDecrypt.Length <= 0)
throw new ArgumentNullException("dataToDecrypt");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("Key");
using (var aes = new AesCryptoServiceProvider())
{
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = Convert.FromBase64String(key);
byte[] data= Convert.FromBase64String(dataToDecrypt);
aes.IV = getIV(data);
using (var memoryStream = new MemoryStream())
{
var cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(),
CryptoStreamMode.Write);
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
var decryptBytes = Encoding.UTF8.GetString(memoryStream.ToArray());
return decryptBytes;
}
}
}
public byte[] GenerateRandomNumber(int length)
{
using (var randomNumberGenerator = new RNGCryptoServiceProvider())
{
var randomNumber = new byte[length];
randomNumberGenerator.GetBytes(randomNumber);
return randomNumber;
}
}
private static byte[] getIV(byte[] arr)
{
byte[] IV = new byte[16];
Array.Copy(arr, 0, IV, 0, 16);
return IV;
}
Here I am trying to Get the IV from the encrypted string, but the originally generated IV and the extracted IV are different when I was debugging, so its giving me this exception, "Padding is invalid and cannot be removed."
Can anyone please look at my GetIV() method and let me know where I am doing wrong.
There are two issues.
Seems you are not sending IV along your ciphertext (it is not done itself) . Common practice is prepending the IV to the the ciphertext. So as Robert commented, you can write IVto the memory stream directly prior encryption
when decrypting, you can read the IV (first bytes of your data, but decrypt only the rest (sub-array 16 .. length )
IV in CBC mode of operation the IV must be sent as unencrypted, otherwise, you cannot decrypt the first block, the rest can be decrypted. There is no security issue in this. For the CBC the crucial about the IV is that it must be unpredictable.
as Robert said, prepend it to the beginning of the stream.
see
var mergedStream = new MergedStream(new Stream
{
new MemoryStream(iv),
cryptoStream,
}
Related
I'm using a string Encryption/Decryption class similar to the one provided here as a solution.
This worked well for me in .Net 5.
Now I wanted to update my project to .Net 6.
When using .Net 6, the decrypted string does get cut off a certain point depending on the length of the input string.
▶️ To make it easy to debug/reproduce my issue, I created a public repro Repository here.
The encryption code is on purpose in a Standard 2.0 Project.
Referencing this project are both a .Net 6 as well as a .Net 5 Console project.
Both are calling the encryption methods with the exact same input of "12345678901234567890" with the path phrase of "nzv86ri4H2qYHqc&m6rL".
.Net 5 output: "12345678901234567890"
.Net 6 output: "1234567890123456"
The difference in length is 4.
I also looked at the breaking changes for .Net 6, but could not find something which guided me to a solution.
I'm glad for any suggestions regarding my issue, thanks!
Encryption Class
public static class StringCipher
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 128;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = Generate128BitsOfRandomEntropy();
var ivStringBytes = Generate128BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = Aes.Create())
{
symmetricKey.BlockSize = 128;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [16 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 16 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 16 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = Aes.Create())
{
symmetricKey.BlockSize = 128;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
private static byte[] Generate128BitsOfRandomEntropy()
{
var randomBytes = new byte[16]; // 16 Bytes will give us 128 bits.
using (var rngCsp = RandomNumberGenerator.Create())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
Calling code
var input = "12345678901234567890";
var inputLength = input.Length;
var inputBytes = Encoding.UTF8.GetBytes(input);
var encrypted = StringCipher.Encrypt(input, "nzv86ri4H2qYHqc&m6rL");
var output = StringCipher.Decrypt(encrypted, "nzv86ri4H2qYHqc&m6rL");
var outputLength = output.Length;
var outputBytes = Encoding.UTF8.GetBytes(output);
var lengthDiff = inputLength - outputLength;
The reason is this breaking change:
DeflateStream, GZipStream, and CryptoStream diverged from typical
Stream.Read and Stream.ReadAsync behavior in two ways:
They didn't complete the read operation until either the buffer passed
to the read operation was completely filled or the end of the stream
was reached.
And the new behaviour is:
Starting in .NET 6, when Stream.Read or Stream.ReadAsync is called on
one of the affected stream types with a buffer of length N, the
operation completes when:
At least one byte has been read from the stream, or The underlying
stream they wrap returns 0 from a call to its read, indicating no more
data is available.
In your case you are affected because of this code in Decrypt method:
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
You do not check how much bytes Read actually read and whether it read them all. You could get away with this in previous versions of .NET because as mentioned CryptoStream behaviour was different from other streams, and because your buffer length is enough to hold all data. However, this is no longer the case and you need to check it as you would do for other streams. Or even better - just use CopyTo:
using (var plainTextStream = new MemoryStream())
{
cryptoStream.CopyTo(plainTextStream);
var plainTextBytes = plainTextStream.ToArray();
return Encoding.UTF8.GetString(plainTextBytes, 0, plainTextBytes.Length);
}
Or even better as another answer suggests, since you decrypt UTF8 text:
using (var plainTextReader = new StreamReader(cryptoStream))
{
return plainTextReader.ReadToEnd();
}
I think your problem is in here:
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
From the Stream.Read docs:
An implementation is free to return fewer bytes than requested even if the end of the stream has not been reached.
So that single call to Read is not guaranteed to read all available bytes (up to plainTextBytes.Length) -- it's well within its rights to read a smaller number of bytes.
.NET 6 has many performance improvements, and I wouldn't be surprised if this was the sort of trade-off they'd make in the name of performance.
You'll have to be good, and keep calling Read until it returns 0, which indicates that there's no more data to return.
However, it's a lot easier to just use a StreamReader, which will also take care of the UTF-8 decoding for you.
return new StreamReader(cryptoStream).ReadToEnd();
I use these 2 extension methods in my .net6 project.
namespace WebApi.Utilities;
public static class StringUtil
{
static string key = "Mohammad-Komaei#Encrypt!keY#";
public static string Encrypt(this string text)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key must have valid value.", nameof(key));
if (string.IsNullOrEmpty(text))
throw new ArgumentException("The text must have valid value.", nameof(text));
var buffer = Encoding.UTF8.GetBytes(text);
var hash = SHA512.Create();
var aesKey = new byte[24];
Buffer.BlockCopy(hash.ComputeHash(Encoding.UTF8.GetBytes(key)), 0, aesKey, 0, 24);
using (var aes = Aes.Create())
{
if (aes == null)
throw new ArgumentException("Parameter must not be null.", nameof(aes));
aes.Key = aesKey;
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (var resultStream = new MemoryStream())
{
using (var aesStream = new CryptoStream(resultStream, encryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(buffer))
{
plainStream.CopyTo(aesStream);
}
var result = resultStream.ToArray();
var combined = new byte[aes.IV.Length + result.Length];
Array.ConstrainedCopy(aes.IV, 0, combined, 0, aes.IV.Length);
Array.ConstrainedCopy(result, 0, combined, aes.IV.Length, result.Length);
return Convert.ToBase64String(combined);
}
}
}
public static string Decrypt(this string encryptedText)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key must have valid value.", nameof(key));
if (string.IsNullOrEmpty(encryptedText))
throw new ArgumentException("The encrypted text must have valid value.", nameof(encryptedText));
var combined = Convert.FromBase64String(encryptedText);
var buffer = new byte[combined.Length];
var hash = SHA512.Create();
var aesKey = new byte[24];
Buffer.BlockCopy(hash.ComputeHash(Encoding.UTF8.GetBytes(key)), 0, aesKey, 0, 24);
using (var aes = Aes.Create())
{
if (aes == null)
throw new ArgumentException("Parameter must not be null.", nameof(aes));
aes.Key = aesKey;
var iv = new byte[aes.IV.Length];
var ciphertext = new byte[buffer.Length - iv.Length];
Array.ConstrainedCopy(combined, 0, iv, 0, iv.Length);
Array.ConstrainedCopy(combined, iv.Length, ciphertext, 0, ciphertext.Length);
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
using (var resultStream = new MemoryStream())
{
using (var aesStream = new CryptoStream(resultStream, decryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(ciphertext))
{
plainStream.CopyTo(aesStream);
}
return Encoding.UTF8.GetString(resultStream.ToArray());
}
}
}
}
After upgrading from .net 2.2 to 6 I was facing exactly the same issue. It does not read entire buffer - mostly reads only upto 16 bytes, so, just break it down in loop upto maximum of 16 bytes.
This code may help:
int totalRead = 0;
int maxRead = 16;
while (totalRead < plainTextBytes.Length)
{
var countLeft = plainTextBytes.Length - totalRead;
var count = countLeft < 16 ? countLeft : maxRead;
int bytesRead = cryptoStream.Read(plainTextBytes, totalRead, count);
totalRead += bytesRead;
if (bytesRead == 0) break;
}
I'm trying to use AesCryptoProvider to encrypt and decrypt byte arrays.
Here are my encrypt and decrypt methods:
public static byte[] EncryptAes(byte[] data, out byte[] key, out byte[] iv)
{
if (data == null || data.Length <= 0)
throw new ArgumentNullException("data");
try
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.KeySize = 256;
aesAlg.BlockSize = 128;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.CBC;
aesAlg.GenerateKey();
aesAlg.GenerateIV();
key = aesAlg.Key;
iv = aesAlg.IV;
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, aesAlg.CreateEncryptor(), CryptoStreamMode.Write))
{
csEncrypt.Write(data, 0, data.Length);
}
return msEncrypt.ToArray();
}
}
}
catch (CryptographicException e)
{
Log.Error(e);
key = null;
iv = null;
return null;
}
}
public static byte[] DecryptAes(byte[] encryptedData, byte[] key, byte[] iv)
{
if (encryptedData == null || encryptedData.Length <= 0)
throw new ArgumentNullException("encryptedData");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("key");
if (iv == null || iv.Length <= 0)
throw new ArgumentNullException("iv");
try
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.KeySize = 256;
aesAlg.BlockSize = 128;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Key = key;
aesAlg.IV = iv;
using (MemoryStream msDecrypt = new MemoryStream(encryptedData))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, aesAlg.CreateDecryptor(), CryptoStreamMode.Write))
{
csDecrypt.Write(encryptedData, 0, encryptedData.Length);
}
return msDecrypt.ToArray();
}
}
}
catch (CryptographicException e)
{
Log.Error(e);
return null;
}
}
Then to test it, I'm using this code:
originalMessage = "This is a test message.";
originalData = System.Text.Encoding.UTF8.GetBytes(originalMessage);
byte[] key, iv;
byte[] encryptedData = Encryption.EncryptAes(originalData, out key, out iv);
byte[] decryptedData = Encryption.DecryptAes(encryptedData, key, iv);
string decryptedMessage = System.Text.Encoding.UTF8.GetString(decryptedData);
Log.Debug(decryptedMessage); // This is a test message.?{?o?}??
The log output shows that the decrypted message has a bunch of garbage characters "?{?o?}??" at the end.
I've seen similar questions, but their answers don't seem to help. I've tried writing to another array during decryption like this:
using (MemoryStream msDecrypt = new MemoryStream(encryptedData))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, aesAlg.CreateDecryptor(), CryptoStreamMode.Write))
{
byte[] decryptedData = new byte[encryptedData.Length];
csDecrypt.Write(decryptedData, 0, decryptedData.Length);
}
return msDecrypt.ToArray();
}
But that results in this exception:
System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.
So there's gotta be something I'm missing. Any ideas? Thanks!
Yeah, reusing buffers is biting you. You generally don't expect the encrypted and decrypted data to be the same sizes, so reusing a buffer causes you to see left-over encrypted data in the decrypted data.
Make your decrypt similar to encrypt. Don't pass the buffer to the constructor of MemoryStream, let it allocate a buffer of the correct size:
using (MemoryStream msDecrypt = new MemoryStream())
{
using (CryptoStream csDecrypt =
new CryptoStream(msDecrypt,
aesAlg.CreateDecryptor(),
CryptoStreamMode.Write))
{
csDecrypt.Write(encryptedData, 0, encryptedData.Length);
}
return msDecrypt.ToArray();
}
I've tried writing to another array during decryption like this:
using (MemoryStream msDecrypt = new MemoryStream(encryptedData))
{
using (CryptoStream csDecrypt =
new CryptoStream(msDecrypt,
aesAlg.CreateDecryptor(),
CryptoStreamMode.Write))
{
byte[] decryptedData = new byte[encryptedData.Length];
csDecrypt.Write(decryptedData, 0, decryptedData.Length);
}
return msDecrypt.ToArray();
}
No read it back to yourself. You're still configuring the cryptostream to write rather than read. What you're doing here is allocating a new buffer and then telling AES to decrypt that empty buffer into the memory stream which was initialized with the encrypted data.
I want to do AES256 Encryption in .net core 1.1. RijndaelManaged is not supporting with the .net core 1.1. So I'm using here AES aes = new AES.create()
This part of code create the random private key for the encryption
public string GenaratePassPharse()
{
RandomNumberGenerator rngCryptoServiceProvider = RandomNumberGenerator.Create();
byte[] randomBytes = new byte[KEY_SIZE];
rngCryptoServiceProvider.GetBytes(randomBytes);
string plainPassPharse = Convert.ToBase64String(randomBytes);
return plainPassPharse;
}
and here the AES() encryption method. What I want to do is pass my generated key (which is return from above method)instead of the aesAlg.Key as an encryption key.
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");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.BlockSize = 128;
aesAlg.KeySize = 128;
// 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())
{
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;
}
And any another way to use AES256 Encryption algorithms?
The full code should be:
public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] key, byte[] iv = null)
{
// Check arguments.
if (plainText == null)
{
throw new ArgumentNullException("plainText");
}
if (key == null || key.Length == 0)
{
throw new ArgumentNullException("Key");
}
// Create an Aes object
// with the specified key and IV.
using (Aes aes = Aes.Create())
{
aes.Key = key;
if (iv == null)
{
iv = new byte[aes.BlockSize / 8];
using (RandomNumberGenerator rngCryptoServiceProvider = RandomNumberGenerator.Create())
{
rngCryptoServiceProvider.GetBytes(iv);
}
}
// Note that we are setting IV, Mode, Padding
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// Create an encryptor to perform the stream transform.
using (ICryptoTransform encryptor = aes.CreateEncryptor())
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
// Prepend the IV
cs.Write(iv, 0, iv.Length);
// Here we are setting the Encoding
using (StreamWriter sw = new StreamWriter(cs, Encoding.UTF8))
{
// Write all data to the stream.
sw.Write(plainText);
}
byte[] encrypted = ms.ToArray();
return encrypted;
}
}
}
public static string DecryptBytesToString_Aes(byte[] encrypted, byte[] key)
{
// Check arguments.
if (encrypted == null || encrypted.Length == 0)
{
throw new ArgumentNullException("plainText");
}
if (key == null || key.Length == 0)
{
throw new ArgumentNullException("Key");
}
// Create an Aes object
// with the specified key and IV.
using (Aes aes = Aes.Create())
{
aes.Key = key;
using (MemoryStream ms = new MemoryStream(encrypted))
{
// Read the prepended IV
var iv = new byte[aes.BlockSize / 8];
ms.Read(iv, 0, iv.Length);
// Note that we are setting IV, Mode, Padding
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// Create an encryptor to perform the stream transform.
using (ICryptoTransform decrytor = aes.CreateDecryptor())
using (CryptoStream cs = new CryptoStream(ms, decrytor, CryptoStreamMode.Read))
// Here we are setting the Encoding
using (StreamReader sr = new StreamReader(cs, Encoding.UTF8))
{
// Read all data from the stream.
string plainText = sr.ReadToEnd();
return plainText;
}
}
}
}
public static byte[] GenerateAesKey(int bits)
{
using (RandomNumberGenerator rngCryptoServiceProvider = RandomNumberGenerator.Create())
{
byte[] key = new byte[bits / 8];
rngCryptoServiceProvider.GetBytes(key);
return key;
}
}
public static void Main()
{
var key = GenerateAesKey(256);
var encrypted = EncryptStringToBytes_Aes("Hello", key);
var decrypted = DecryptBytesToString_Aes(encrypted, key);
}
This code prepend a random IV to the encrypted stream, and the recovers it from the encrypted stream to decrypt the stream.
It is very important that when you speak of encryption you set everything that can be setted. There must be no questions open. The encryption Mode, the Padding, the Encoding the plaintext must be encoded. Everything!
I am currently working on AES implementation in C#. The encryption method has two parameters: a string and a password. I am taking the supplied string and converting it to an array of bytes, so I can use it later for writing data to a stream with BinaryWriter.
The problem is that when I use Convert.FromBase64String(string) I get FormatException: Invalid length.and when I use Encoding.UTF8.GetBytes(string) my decryption method throws and invalid PKCS7.Padding exception.
I have been trying to solve this problem for the last couple of days. I have read near infinite questions in stackoverflow.com and other websites, but I still don't know what is the most reliable way to solve this problem.
Strings that will be used in this program are limited to sentences (ex. "Something to encrypt.") and numbers (ex. "12345").
Thank you in advance, here is the code I have at this point in time:
public class AESProvider {
public byte[] EncryptStringToBytes_Aes(string plainText, string Key)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
byte[] plainTextInBytes = Convert.FromBase64String(plainText);
byte[] encrypted;
//Create an Aes object
//with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.GenerateIV();
byte[] IV = aesAlg.IV;
//The Salt will be the first 8 bytes of the IV.
byte[] theSalt = new byte[8];
Array.Copy(IV,theSalt,8);
//A key for AES is generated by expanding the password using the following method.
Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
byte[] aesKey = keyGen.GetBytes(16);
aesAlg.Key = aesKey;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (BinaryWriter swEncrypt = new BinaryWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainTextInBytes);
}
encrypted = msEncrypt.ToArray();
}
}
// Prepend the IV to the ciphertext so it can be used in the decryption process.
using (MemoryStream ivPlusCipher = new MemoryStream())
{
using (BinaryWriter tBinaryWriter = new BinaryWriter(ivPlusCipher))
{
tBinaryWriter.Write(IV);
tBinaryWriter.Write(encrypted);
tBinaryWriter.Flush();
}
return ivPlusCipher.ToArray();
}
}
}
public byte[] DecryptStringFromBytes_Aes(byte[] cipherText, string Key)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
byte[] decrypted;
// Create an Aes object
// with the specified key and IV.
// Create the streams used for decryption.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
//Grab IV from ciphertext
byte[] IV = new byte[16];
Array.Copy(cipherText,0,IV,0,16);
//Use the IV for the Salt
byte[] theSalt = new byte[8];
Array.Copy(IV,theSalt,8);
Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
byte[] aesKey = keyGen.GetBytes(16);
aesAlg.Key = aesKey;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, IV);
using (MemoryStream msDecrypt = new MemoryStream())
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
{
using (BinaryWriter srDecrypt = new BinaryWriter(csDecrypt))
{
//Decrypt the ciphertext
srDecrypt.Write(cipherText, IV.Length, (cipherText.Length - IV.Length));
}
decrypted = msDecrypt.ToArray();
return decrypted;
}
}
}
}
}
You need to convert between bytes and strings before and after encryption/decryption. This is not the same operation, and you should not use the same method.
When encrypting you start out with an arbitrary string. Convert this to a byte[] using Encoding.UTF8.GetBytes(). Encrypt it. The resulting byte[] can now be converted to a string using Convert.ToBase64String().
When decrypting you now start out with a Base64 encoded string. Decode this to a byte[] using Convert.FromBase64String(). Decrypt it. You now have the UTF-8 encoding of your original string, which you can decode using Encoding.UTF8.GetString().
Remember:
Encoding.UTF8 works to convert arbitrary strings to byte-arrays (but it can only convert byte-arrays that contain actual UTF8-encodings back).
Convert.[To/From]Base64String works to convert arbitrary byte-arrays to strings (but it can only convert strings that contain actual Base64-encodings back).
Looking at your lines
public byte[] EncryptStringToBytes_Aes(string plainText, string Key)
byte[] plainTextInBytes = Convert.FromBase64String(plainText);
Arbitrary plain text will not be a base 64 encoded string. Even if it is supposed to be base 64 encoded text, your error message indicates that the length is not divisible by 4
FormatException
The length of s, ignoring white-space characters, is not zero or a multiple of 4.
-or-
The format of s is invalid. s contains a non-base-64 character, more than two padding characters, or a > non-white space-character among the padding characters.
http://msdn.microsoft.com/en-us/library/system.convert.frombase64string(v=vs.110).aspx
If it is a base 64 encoded string, you need to pad it accorgingly
http://en.wikipedia.org/wiki/Base64
Convert.FromBase64String(string); is expected to receive a string generated by Convert.ToBase64String(byte[]); passing in a arbitrary string will not work.
The easiest solution is replace the BinaryWriter and BinaryReader with a StreamWriter and a StreamReader and not do any conversion at all.
public byte[] EncryptStringToBytes_Aes(string plainText, string Key)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
//Create an Aes object
//with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.GenerateIV();
byte[] IV = aesAlg.IV;
//The Salt will be the first 8 bytes of the IV.
byte[] theSalt = new byte[8];
Array.Copy(IV,theSalt,8);
//A key for AES is generated by expanding the password using the following method.
Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
byte[] aesKey = keyGen.GetBytes(16);
aesAlg.Key = aesKey;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
//You can write the IV here and not need to do it later.
msEncrypt.Write(IV, 0, 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);
}
}
//Move this outside of the using statement for CryptoStream so it is flushed and dipsoed.
return msEncrypt.ToArray();
}
}
}
Also, your decryption function is actually trying to encrypt the text a 2nd time, you need to pass the byte array in to the constructor of msDecrypt and put it in decryption mode.
public string DecryptStringFromBytes_Aes(byte[] cipherText, string Key)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
// Create an Aes object
// with the specified key and IV.
// Create the streams used for decryption.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
//Grab IV from ciphertext
byte[] IV = new byte[16];
Array.Copy(cipherText,0,IV,0,16);
//Use the IV for the Salt
byte[] theSalt = new byte[8];
Array.Copy(IV,theSalt,8);
Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(Key,theSalt);
byte[] aesKey = keyGen.GetBytes(16);
aesAlg.Key = aesKey;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, IV);
//You can chain using statements like this to make the code easier to read.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) //Notice this is Read mode not Write mode.
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
//Decrypt the ciphertext
return srDecrypt.ReadToEnd();
}
}
}
There may be other errors with your code, but at least this gets you on the right track.
I have taken the decrypt code from http://msdn.microsoft.com/en-us/library/system.security.cryptography.cryptostream.aspx and modified it as follows below. I have an encrypted example, and it works just fine while decoding. But when using the encryption function, it returns junk string with strange symbols. below are the functions of encrypt/decrypt.
An example of encrypted string "hey" : "???U?b???z?Y???"
When decoded again: "ûc{ÁpÅ`ñ""Â"
I'm using this code to convert the byte array to string:
private string ByteArrayToString(byte[] input)
{
ASCIIEncoding dec = new ASCIIEncoding();
return dec.GetString(input);
}
here are the encrypt/decrypt functions. the decryption function is working fine.
private 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");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged aesAlg = new RijndaelManaged())
{
aesAlg.Key = Key;
aesAlg.Padding = PaddingMode.Zeros;
aesAlg.Mode = CipherMode.ECB;
// 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;
}
private byte[] EncryptStringToBytesAes(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");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged aesAlg = new RijndaelManaged())
{
aesAlg.Key = Key;
aesAlg.Padding = PaddingMode.Zeros;
aesAlg.Mode = CipherMode.ECB;
// 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 encrypted;
}
What you observe is the problem of mapping arbitrary bytes (in the range 0-255) to characters. Meaningful characters are only in the range 32-255 or even only 32-127 (ASCII). Values below 32 are the so-called non-printable characters and values above 127 are dependent on the character encoding you are using. That's why the crypted text looks like junk. Mast crypto-systems therefore transform the bytes into the sensible ASCII-range. One such algorithm is BASE64. So mangling the crypted bytes through BASE64 gives characters that are all printable and that will go without problems through e-mail. Before decrypting you then have to undo the BASE64 encoding.
Another way to make the encrypted result look better is to show the hexa-decimal representation of it. For example if you have a byte value of 15 you print 0F. You may use this to represent your byte array in hex:
private string ByteArrayToHexString(byte[] data)
{
return String.Concat(data.Select(b => b.ToString("x2")));
}
In order to have your output as a hexadecimal encoding of the data, follow the methods found here. I modified them slightly to be extension methods:
public static string ToHexString(this byte[] bytes)
{
return bytes == null ? string.Empty : BitConverter.ToString(bytes).Replace("-", string.Empty);
}
public static byte[] FromHexString(this string hexString)
{
if (hexString == null)
{
return new byte[0];
}
var numberChars = hexString.Length;
var bytes = new byte[numberChars / 2];
for (var i = 0; i < numberChars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
}
return bytes;
}
Encrypted strings will look like garble. The way to test if the encryption is working correctly is to pass your string back through decrypt. If it works at decrypting then you know the string is correct despite looking like garbage to you.