Jump to Update below < not sure how to make a link :(
I've made some improvments on the code from : Csharp-AES-bits-Encryption-Library-with-Salt
- saltBytes is now the SHA512 of the password.
- Random IV for each encryption call. ( IV length 16 is added to the encrypted file , removed from file before decryption)
Do you see any flaws, something that needs optimization? In particular, my questions are:
Is my
generateIV()
method below in the "Encryption Code" safe and secure ? Note that its only dependency is on the .NETRNGCryptoServiceProvider Class
.Is it safe to use password hash as a salt? or it should be random like the IV and stored along with the ciphertext?
Just for reference, here is my code.
The Code bellow is working, I've made a test to encrypt two text files with exact same text inside each. The result is both have different data, and decrypt succeed.
Also, I checked before the random IV, both files had same encrypted text, results in same data.
Encryption code :
private static int IV_LENGTH = 16;
public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] encryptedBytesAndIV = null;
byte[] saltBytes = SHA512.Create().ComputeHash(passwordBytes);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
//AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = generateIV();
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
encryptedBytesAndIV = new byte[encryptedBytes.Length + AES.IV.Length];
AES.IV.CopyTo(encryptedBytesAndIV, 0);
encryptedBytes.CopyTo(encryptedBytesAndIV, IV_LENGTH);
}
}
return encryptedBytesAndIV;
}
private static byte[] generateIV()
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] nonce = new byte[IV_LENGTH];
rng.GetBytes(nonce);
return nonce;
}
}
Decryption code :
public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
byte[] saltBytes = SHA512.Create().ComputeHash(passwordBytes);
using (MemoryStream ms = new MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
//AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = getIV(bytesToBeDecrypted);
bytesToBeDecrypted = removeTagAndIV(bytesToBeDecrypted);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
private static byte[] removeTagAndIV(byte[] arr)
{
byte[] enc = new byte[arr.Length - IV_LENGTH];
Array.Copy(arr, IV_LENGTH, enc, 0, arr.Length - IV_LENGTH);
return enc;
}
private static byte[] getIV(byte[] arr)
{
byte[] IV = new byte[IV_LENGTH];
Array.Copy(arr, 0, IV, 0, IV_LENGTH);
return IV;
}
Update:
Here is updated code based on comments/recommendations/advice
- Random Salt True
- iterations 100000
Encryption:
public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] encryptedBytesFinal = null;
byte[] saltBytes = generateIVandSalt(16);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = generateIVandSalt(16);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
encryptedBytesFinal = new byte[encryptedBytes.Length + AES.IV.Length + saltBytes.Length];
AES.IV.CopyTo(encryptedBytesFinal, 0);
saltBytes.CopyTo(encryptedBytesFinal, 16);
encryptedBytes.CopyTo(encryptedBytesFinal, 16 + 16);
}
}
return encryptedBytesFinal;
}
private static byte[] generateIVandSalt(int len)
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] nonce = new byte[len];
rng.GetBytes(nonce);
return nonce;
}
}
Decryption:
public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
byte[] saltBytes = getSalt(bytesToBeDecrypted);
using (MemoryStream ms = new MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = getIV(bytesToBeDecrypted);
bytesToBeDecrypted = removeIVandSalt(bytesToBeDecrypted);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
private static byte[] removeIVandSalt(byte[] arr)
{
byte[] enc = new byte[arr.Length - 16 - IV_LENGTH];
Array.Copy(arr, IV_LENGTH + 16, enc, 0, arr.Length - IV_LENGTH - 16);
return enc;
}
private static byte[] getIV(byte[] arr)
{
byte[] IV = new byte[IV_LENGTH];
Array.Copy(arr, 0, IV, 0, IV_LENGTH);
return IV;
}
private static byte[] getSalt(byte[] arr)
{
byte[] salt = new byte[16];
Array.Copy(arr, IV_LENGTH, salt, 0, 16);
return salt;
}