我正在创建一个密码管理器,它使用SQLite数据库来存储用户密码。我使用AES算法加密密码,但我不知道应该在哪里保存从主密码生成的加密密钥。
答案 0 :(得分:2)
重要:聘请专家。认真。这个东西很难。即使是专家也弄错了。但是他们知道要注意什么。
随着说:
所以这是一种非常常见的模式。有几种方法,但它们都有一些共同的模式。
密钥派生步骤发生在您保密并从中派生密钥时。此过程使用KDF (Key Derivation Function)。
在这种情况下,您需要一个PBKDF,这是一个基于密码的密钥派生函数。这是一个类似于KDF的功能,但是设计用于低熵秘密。因此,它通过使从秘密到密钥的链接更难找到来增加一层保护(因此更难以尝试强制执行)。
要使用的常见PBKDF函数是PBKDF2。您也可以使用scrypt(更新,但FAR更强)。我将从这里开始使用pbkdf2,但是肯定也会考虑scrypt(它有更多的调整参数,但在使用方面是相同的)。
首先,你需要生成一个盐。这是一个随机值。这允许相同的密码在不同的系统上产生不同的密钥。盐是不的秘密。
salt := genRandom(16)
这种盐需要存储在某个地方(可能存储在数据库中,或者某个地方)。
然后,从密码中导出密钥。请注意,对于具有相同设置(和salt)的每个派生,相同的密码将生成相同的密钥。所以我们不存储此密钥。每次我们需要时都会计算出来。
key := pbkdf2('sha256', password, salt, count, keyLength)
请注意count参数。这提供了防止暴力强制的保护。你做得越高,功能就越长。对于在线使用(例如,在Web请求中),10,000 - 20,000的值是不错的。对于离线使用(与您的一样),您可以容忍更高(更高更好)。
现在我们已经从密码中获得了一个密钥。
我们可以直接用该密钥加密。这意味着当用户打开应用程序时,他们会输入密码,这将导出密钥并将其存储在内存中。
这是一把双刃剑。很简单。但这也意味着如果任何人都能从内存中读取密钥,他们就可以开始强制密码了。
另一种选择是存储主密钥。这将在安装时生成,并使用派生密钥加密。因此,当用户登录时,您将导出密钥,检索主密钥并删除派生密钥。这提供了一些防止暴力强制的保护,并允许加密使用更强大的密钥。它还提供了更改密码的功能,而无需重新加密所有内容。
所以流程看起来像:
private masterKey;
function login(password) {
salt = lookupSalt()
derivedKey = pbkdf2('sha256', password, salt, count, keyLength)
masterKeyEncrypted = lookupEncryptedMasterKey()
// NOTE that you **must** authenticate this encryption
masterKey = decrypt(masterKeyEncrypted, derivedKey)
derivedKey = null
}
然后,只需使用该主密钥加密和解密。
所以我提到你必须验证加密。基本上,这意味着使用Encrypt-Then-Mac。
在伪代码中
function encrypt(data, key) {
cipherKey = key[0...256] // first 256 bits
macKey = key[256...512] // second 256 bits
iv = key[512...640] // final 128 bits
cipherText = AES-256-CBC-ENCRYPT(data, cipherKey, iv)
mac = HMAC('sha256', cipherText | iv, macKey)
return mac | cipherText
}
然后,在解密时,您也会这样做:
function decrypt(data, key) {
cipherKey = key[0...256] // first 256 bits
macKey = key[256...512] // second 256 bits
iv = key[512...640] // final 128 bits
mac = data[0...256] // first 256 bits
cipherText = data[256...] // rest
if ( mac != HMAC('sha256', cipherText | iv, macKey))
throw Exception "Invalid Data"
return AES-256-CBC-DECRYPT(cipherText, cipherKey, iv)
}
现在,MAC检查应该通过定时安全比较来完成(以防止侧信道定时攻击)。
请注意,由于身份验证取决于密码,因此无效的密码会导致身份验证失败。但请注意,您无法区分无效密码和篡改存储密钥的人。
如果这是一个重大问题,那么您可以再次哈希密码(使用不同的盐),并将其加密存储在数据库中。然后在解密主密钥之后,解密存储的密码哈希并验证密码是否满足哈希。如果你这样做,那么绝对确定来加密哈希,并使用不同的盐(否则将剩下的工作扔掉)。