I'm working on a CryptSharp SCrypt implementation in VS2015. I need to encrypt/decrypt text files meant to be sent as email attachments. Initially I was using AES but considering that HMAC-SHA1 is outdated I opted to use SCrypt for password hashing. However, SCrypt does not expose public methods for the data encryption itself, so would it make sense to pass the SCrypt hashed password to AES, then use the latter for data encryption? Or perhaps there is a better approach?
In this scenario, I would imagine something like this, yet I would need to find a way to reliably randomize the IV...
private static Aes SetAes(string userName, string password)
{
var passBytes = Encoding.UTF8.GetBytes(password);
var saltBytes = Encoding.UTF8.GetBytes(userName);
var cost = 131072; // around 5 secs with block at 16(on Xeon 1241 v3)
var blockSize = 16; // 8 is default but might not suffice against modern GPUs(?)
var parallel = 1;
var maxThreads = (int?)null;
byte[] derivedKey = new byte[32]; // 256 bits
SCrypt.ComputeKey(passBytes, saltBytes, cost, blockSize, parallel, maxThreads, derivedKey);
Aes aes = new AesManaged();
aes.Padding = PaddingMode.PKCS7;
aes.Key = derivedKey;
byte[] IV = new byte[16];
Array.Copy(derivedKey, IV, 16); // how to reliably randomize the IV?
aes.IV = IV;
return aes;
}
Then for file encryption:
internal static void EncryptText(string text, string userName, string password, string file)
{
// omitting argument checks for readability
using (Aes aes = SetAes(userName, password))
{
using (FileStream fileStream = new FileStream(file, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
using (CryptoStream cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(cryptoStream, text); // I'm using a class to wrap the text for serialization, not shown here for readability
}
}
}
}
Even though it seems to work, I'm not sure it makes sense, so thank you very much for any insight.
EDIT:
Following vcsjones recommendations, the SetAes function would rather look like this, if I understand correctly:
private static Aes SetAes(string userName, string password, byte[] IV = null)
{
var passBytes = Encoding.UTF8.GetBytes(password);
var saltBytes = Encoding.UTF8.GetBytes(userName);
var cost = 131072;
var blockSize = 16;
var parallel = 1;
var maxThreads = (int?)null;
byte[] derivedKey = new byte[32];
SCrypt.ComputeKey(passBytes, saltBytes, cost, blockSize, parallel, maxThreads, derivedKey);
Aes aes = new AesManaged();
aes.Padding = PaddingMode.PKCS7;
aes.Key = derivedKey;
if (IV == null) // when encrypting, generate IV
{
RandomNumberGenerator rn = RandomNumberGenerator.Create();
rn.GetBytes(aes.IV);
}
else aes.IV = IV; // when decrypting, read IV from file and pass it to aes through IV parameter for decryption
return aes;
}
SCrypt is a Key Derivation Function, so yes, that is an acceptable thing to do.
Don't use the output of the KDF in the IV. The IV should be random for AES-CBC, so use
RandomNumberGenerator.Create()
to create a CSPRNG for the IV. Using the KDF output as part of the IV actually leaks the key since the IV is stored in plaintext.An IV in AES-CBC should be random, and it should not be reused. Don't derive it from the password. You do need to store the IV somewhere. Since it looks like you're trying to encrypt files, you may just want to put the IV in at the beginning of the file. The IV is not a secret - it's OK if someone can read it. Then, when it comes time to decrypt the file, read the IV from the file, and then decrypt everything past the IV.
I would also recommend that you MAC the file, as well, as right now your application does not authenticate the encryption.