离线使用的 AES 加密选项
AES encryption option for offline usage
我的objective是在移动应用程序中对某些数据(例如个人信息等)在本地执行数据encryption/decryption,而不是密码。偶然发现了这个library,现在考虑我可以有的两个选择
选项 1
使用用户密码作为 secret passphrase
,而不是硬编码 passphrase
,加密和解密密钥是“自定义的”
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
选项 2 根据用户密码生成密钥,如下所示
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));
鉴于我的用例,我想知道一个选项是否比另一个选项有任何优势?
您永远不应该对密码短语进行硬编码,它会将密码学转化为编码。
最好使用散列而不是标准的人类可读文本字符串,尤其是因为它添加了我认为是迭代散列的内容,这会增加抵御暴力攻击的工作量。 1000 次迭代会增加相当多的时间,10k 甚至更多,但所有消耗 CPU 周期的东西都需要与性能相平衡。
还请确保在显式设置 reading/writing 数据时使用的任何编码(如 Utf8),以免混用和匹配类型,否则会导致一些问题。
这两个选项都不推荐:
绝对不应使用第一个选项,除非出于兼容性原因强制这样做。
主要原因是内置密钥派生应用了已弃用的密钥派生函数EVP_BytesToKey()
,它派生了一个 32 字节的密钥 (AES-256) 和一个 16 字节的 IV。
EVP_BytesToKey()
和用于密钥派生的参数(MD5,迭代计数为 1)在今天被认为是不安全的,s。例如here.
此外,正如评论中已经提到的,EVP_BytesToKey()
是 OpenSSL 的专有和非标准实现,因此在许多平台上不可用,这在跨平台架构中可能是一个问题。
显然更安全的替代方法是应用标准化密钥派生函数(例如 PBKDF2)来派生密钥,然后用于 encryption/decryption。
虽然第二个选项使用 PBKDF2 作为密钥派生函数,但是使用 key.toString()
对密钥执行十六进制编码,即将密钥转换为字符串,具有深远的影响:
CryptoJS 只是使用数据类型将第二个参数解释为密码或密钥。在字符串的情况下,数据被解释为密码,并且使用 EVP_BytesTokey()
执行密钥推导(对于第一个选项),在 WordArray
的情况下,数据被解释为密钥 直接应用。
当前的实现将密钥作为字符串传递,即使用 PBKDF2 派生的密钥被解释为密码,并且不必要地执行使用 EVP_BytesTokey()
的内置密钥派生 此外 到 PBKDF2 的密钥派生。
正确做法:
更改第二个选项,使密钥作为 WordArray
传递,即 key
而不是 key.toString()
。
此更改的结果是 salt 和 IV 必须 显式处理 (与使用 EVP_BytesToKey()
的内置密钥派生相反,这种情况发生在幕后):
- 当前代码已经生成随机盐,完全正确。同样,必须生成随机 IV(CryptoJS 默认使用 CBC)并传递(s. here)。
作为使用 random()
显式生成的替代方法,IV 可以通过 PBKDF2.
与密钥一起导出
- 由于解密需要salt和IV,不是secret,所以通常两者都与密文串联:
salt|IV|ciphertext
(如果IV和key一起导出,当然只需要salt串联)。在解密端,可以根据已知的salt和IV长度将部分分开。
关于评论中提到的密钥大小:今天所有 AES 变体都被认为是安全的,即使是最小的密钥大小 AES-128。
应使用 AES-128、AES-192 还是 AES-256 取决于各自的要求。如果需要尽可能高的安全性,例如对于未来的量子计算机,AES-256 可能是更好的选择。但是,此要求可能并不总是存在。参见例如还有 post Why most people use 256 bit encryption instead of 128 bit?.
我的objective是在移动应用程序中对某些数据(例如个人信息等)在本地执行数据encryption/decryption,而不是密码。偶然发现了这个library,现在考虑我可以有的两个选择
选项 1
使用用户密码作为 secret passphrase
,而不是硬编码 passphrase
,加密和解密密钥是“自定义的”
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
选项 2 根据用户密码生成密钥,如下所示
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));
鉴于我的用例,我想知道一个选项是否比另一个选项有任何优势?
您永远不应该对密码短语进行硬编码,它会将密码学转化为编码。
最好使用散列而不是标准的人类可读文本字符串,尤其是因为它添加了我认为是迭代散列的内容,这会增加抵御暴力攻击的工作量。 1000 次迭代会增加相当多的时间,10k 甚至更多,但所有消耗 CPU 周期的东西都需要与性能相平衡。
还请确保在显式设置 reading/writing 数据时使用的任何编码(如 Utf8),以免混用和匹配类型,否则会导致一些问题。
这两个选项都不推荐:
绝对不应使用第一个选项,除非出于兼容性原因强制这样做。
主要原因是内置密钥派生应用了已弃用的密钥派生函数
EVP_BytesToKey()
,它派生了一个 32 字节的密钥 (AES-256) 和一个 16 字节的 IV。
EVP_BytesToKey()
和用于密钥派生的参数(MD5,迭代计数为 1)在今天被认为是不安全的,s。例如here.
此外,正如评论中已经提到的,EVP_BytesToKey()
是 OpenSSL 的专有和非标准实现,因此在许多平台上不可用,这在跨平台架构中可能是一个问题。显然更安全的替代方法是应用标准化密钥派生函数(例如 PBKDF2)来派生密钥,然后用于 encryption/decryption。
虽然第二个选项使用 PBKDF2 作为密钥派生函数,但是使用
key.toString()
对密钥执行十六进制编码,即将密钥转换为字符串,具有深远的影响:CryptoJS 只是使用数据类型将第二个参数解释为密码或密钥。在字符串的情况下,数据被解释为密码,并且使用
EVP_BytesTokey()
执行密钥推导(对于第一个选项),在WordArray
的情况下,数据被解释为密钥 直接应用。当前的实现将密钥作为字符串传递,即使用 PBKDF2 派生的密钥被解释为密码,并且不必要地执行使用
EVP_BytesTokey()
的内置密钥派生 此外 到 PBKDF2 的密钥派生。
正确做法:
更改第二个选项,使密钥作为 WordArray
传递,即 key
而不是 key.toString()
。
此更改的结果是 salt 和 IV 必须 显式处理 (与使用 EVP_BytesToKey()
的内置密钥派生相反,这种情况发生在幕后):
- 当前代码已经生成随机盐,完全正确。同样,必须生成随机 IV(CryptoJS 默认使用 CBC)并传递(s. here)。
作为使用random()
显式生成的替代方法,IV 可以通过 PBKDF2. 与密钥一起导出
- 由于解密需要salt和IV,不是secret,所以通常两者都与密文串联:
salt|IV|ciphertext
(如果IV和key一起导出,当然只需要salt串联)。在解密端,可以根据已知的salt和IV长度将部分分开。
关于评论中提到的密钥大小:今天所有 AES 变体都被认为是安全的,即使是最小的密钥大小 AES-128。
应使用 AES-128、AES-192 还是 AES-256 取决于各自的要求。如果需要尽可能高的安全性,例如对于未来的量子计算机,AES-256 可能是更好的选择。但是,此要求可能并不总是存在。参见例如还有 post Why most people use 256 bit encryption instead of 128 bit?.