外包密钥加密

时间:2014-08-26 10:57:32

标签: java security passwords password-protection password-encryption

我正在创建一个密码管理器,它使用SQLite数据库来存储用户密码。我使用AES算法加密密码,但我不知道应该在哪里保存从主密码生成的加密密钥。

1 个答案:

答案 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检查应该通过定时安全比较来完成(以防止侧信道定时攻击)。

请注意,由于身份验证取决于密码,因此无效的密码会导致身份验证失败。但请注意,您无法区分无效密码和篡改存储密钥的人。

如果这是一个重大问题,那么您可以再次哈希密码(使用不同的盐),并将其加密存储在数据库中。然后在解密主密钥之后,解密存储的密码哈希并验证密码是否满足哈希。如果你这样做,那么绝对确定来加密哈希,并使用不同的盐(否则将剩下的工作扔掉)。