My objective is to perform data encryption/decryption locally for some data such as personal information and so on, instead of password, within a mobile app. Stumbled upon this library, and now considering two options which I can have
Option 1
Using user's password as secret passphrase
, instead of hardcoding a passphrase
, encryption and decryption key are "customized"
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
Option 2 Generating a key based on user's password such as below
const CryptoJS = require("crypto-js");
const salt = CryptoJS.lib.WordArray.random(128 / 8);
const key = CryptoJS.PBKDF2("password", salt, {
keySize: 512 / 32,
iterations: 10000,
});
var encrypted = CryptoJS.AES.encrypt("Message", key.toString());
var decrypted = CryptoJS.AES.decrypt(encrypted, key.toString());
console.log(decrypted.toString(CryptoJS.enc.Utf8));
Given my use case, I'm wondering if there is any advantages of one option over another?
Neither of these two options can be recommended:
The first option should definitely not be used unless compatibility reasons enforce this.
The main reason for this is that the built-in key derivation applies the deprecated key derivation function EVP_BytesToKey()
, which derives a 32 bytes key (AES-256) and a 16 bytes IV.
EVP_BytesToKey()
and the pararmeters used for key derivation (MD5, iteration count of 1) are considered insecure today, s. e.g. here.
In addition, as already mentioned in the comment, EVP_BytesToKey()
is a proprietary and non-standard implementation of OpenSSL, and is therefore not available on many platforms, which can be a problem in a cross-platform architecture.
The clearly more secure alternative is to apply a standardized key derivation function such as PBKDF2 to derive a key that is then used for encryption/decryption.
The second option uses PBKDF2 as key derivation function though, but performs a hexadecimal encoding of the key with key.toString()
, i.e. converts the key to a string, with far-reaching consequences:
CryptoJS just uses the data type to interpret the second parameter as password or key. In case of a string the data is interpreted as password and the key derivation with EVP_BytesTokey()
is performed (as for the first option), in case of a WordArray
the data is interpreted as key which is directly applied.
The current implementation passes the key as string, i.e. the key derived with PBKDF2 is interpreted as password and the built-in key derivation with EVP_BytesTokey()
is unnecessarily performed in addition to the key derivation with PBKDF2.
Change the second option so that the key is passed as WordArray
, i.e. key
instead of key.toString()
.
This change has the consequence that salt and IV must be handled explicitly (in contrast to the built-in key derivation with EVP_BytesToKey()
, where this happens under the hood):
random()
, the IV can be derived together with the key via PBKDF2.salt|IV|ciphertext
(if the IV is derived together with the key, of course only the salt needs to be concatenated). On the decryption side, the portions can be separated based on the known lengths of salt and IV.Regarding the key size mentioned in the comment: All AES variants are considered secure today, even the smallest key size AES-128.
Whether AES-128, AES-192 or AES-256 should be used depends on the respective requirements. If the highest possible security is needed e.g. with regard to future quantum computers, AES-256 is probably the better choice. However, this requirement may not always exist. See e.g. also the post Why most people use 256 bit encryption instead of 128 bit?.