I need to secure the user input password of an electron app to create a key for crypto AES ancryption. As suggested here on SO I've replaced the createHash
function with scryptSync
function. I'm not sure about the salt.
// this is the old method I've used to hash the password to obtain the correct lenght for aes-256-gcm
// let key = crypto.createHash('sha256')
// .update(data.password)
// .digest();
// salt creation
let salt = crypto.randomBytes(64);
let key = crypto.scryptSync(data.password, salt, 32);
Is there a predefined size that I need to pass to randomBytes
function for aes-256-gcm
encryption algo?
Also when I attach the salt to the encrypted output I'm using this code
let encryptedData = Buffer.concat([cipher.update(base64, 'utf8'), cipher.final()]);
let payload = `${salt.toString('base64')}:${iv.toString('base64')}:${encryptedData.toString('base64')}`;
Since I'm using the join()
function to merge multiple encoded strings, I need a way to split them when the encrypted output file needs to be decoded. At the moment I'm using this way, but I've figured out that I need to extract the salt to check the password. Is there a way to do it better?
const decryptData = (data) => {
let output = [];
// I need the salt here but the file needs to be splitted before
let key = crypto.scryptSync(data.password, 'salt', 32);
return new Promise( (resolve, reject) => {
fs.readFile(data.path, 'utf8', (err, content) => {
if(err) return reject(err);
let fileContents = content.split(' ');
output = fileContents.map( (file) => {
let [salt, iv, content] = file.split(':');
let cipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(iv, 'base64'));
let decrypted = cipher.update(content, 'base64', 'utf8');
return decryptedData;
});
resolve(output);
});
});
}
As the Node.js indicates, the hash needs to be minimally 128 bits or 16 bytes. 64 bytes is really too much, I'd max out on 256 bits but 128 is really enough when it comes to a password. As you seem to be aiming at max security, try 32 bytes. Don't forget that requiring a strong password is at least as important, if not more important.
Currently you are using the default cost parameter for scryptSync
. That's probably not a good idea as it is set to 16384 by default. The higher the better, so try and see what's best for your particular target platforms.
Note that you both know the salt and IV size in bytes. This means that it would be just as easy to get them from a binary string before it is encoded to base 64. That also has some advantages: if you load the binary data into a Buffer
then you can simply use methods like from
to get a different view, which can then represent an IV or salt without copying the data.
That's most important for the plaintext of course. You want to copy that as few times as possible.