Java 密钥加密外包
我正在创建一个密码管理器,它使用SQLite数据库来存储用户密码。我用AES算法加密密码,但我不知道从主密码生成的加密密钥应该保存在哪里。重要信息:聘请专家。认真地这东西很硬。即使是专家也弄错了。但他们知道该注意什么 话虽如此: 所以这是一个非常常见的模式。有几种方法,但它们都有一些共同的模式 密钥派生 密钥派生步骤发生在获取密钥并从中派生密钥时。此过程使用了一个 在本例中,您需要一个PBKDF,它是一个基于密码的密钥派生函数。这是一个类似于KDF的函数,但设计用于低熵机密。因此,它增加了一层保护,使得从秘密到密钥的链接更难找到(从而更难尝试暴力强迫) 一个常用的PBKDF函数是。您也可以使用(更新,但更强)。从这里开始,我将使用pbkdf2,但肯定也会研究scrypt(它有更多的调优参数,但在使用方面是相同的) 首先,你需要生成一种盐。这是一个随机值。这允许相同的密码在不同的系统上生成不同的密钥。盐不是秘密Java 密钥加密外包,java,security,passwords,password-protection,password-encryption,Java,Security,Passwords,Password Protection,Password Encryption,我正在创建一个密码管理器,它使用SQLite数据库来存储用户密码。我用AES算法加密密码,但我不知道从主密码生成的加密密钥应该保存在哪里。重要信息:聘请专家。认真地这东西很硬。即使是专家也弄错了。但他们知道该注意什么 话虽如此: 所以这是一个非常常见的模式。有几种方法,但它们都有一些共同的模式 密钥派生 密钥派生步骤发生在获取密钥并从中派生密钥时。此过程使用了一个 在本例中,您需要一个PBKDF,它是一个基于密码的密钥派生函数。这是一个类似于KDF的函数,但设计用于低熵机密。因此,它增加了一层保
salt := genRandom(16)
这种盐需要存储在某个地方(可能在数据库中,或者类似的地方)
然后,从密码中派生一个密钥。请注意,对于具有相同设置(和salt)的每个派生,相同的密码将产生相同的密钥。因此,我们不存储此密钥。每次我们需要它时都会计算出来
key := pbkdf2('sha256', password, salt, count, keyLength)
请注意count参数。这提供了防止暴力强迫的保护。你做的越高,函数所用的时间就越长。对于在线使用(例如在web请求中),10000-20000的值是合适的。对于离线使用(就像你的一样),你可以容忍更高(而且越高越好)
现在我们从密码中导出了一个密钥
加密
我们可以直接用那个密钥加密。因此,这意味着当用户打开应用程序时,他们将输入密码,密码将导出密钥并存储在内存中
这是一把双刃剑。很简单。但这也意味着,如果任何人都可以从内存中读取密钥,那么他们就可以开始强行输入密码
另一种选择是存储主密钥。这将在安装时生成,并使用派生密钥进行加密。因此,当用户登录时,您将派生一个密钥,检索主密钥,然后擦除派生密钥。这提供了一些针对暴力强迫的保护,并允许加密使用更强大的密钥。它还提供了更改密码的能力,而无需重新加密所有内容
因此,流看起来像:
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
}
然后,只需使用主密钥进行加密和解密
认证
所以我提到您必须对加密进行验证。基本上,这意味着使用
伪码
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检查应该通过定时安全比较来完成(以防止侧通道定时攻击)
请注意,由于身份验证取决于密码,因此无效密码将导致身份验证失败。但还要注意的是,您无法区分无效密码和有人篡改存储的密钥之间的区别
如果这是一个重大问题,那么您可以再次散列密码(使用不同的salt),并将其加密存储在数据库中。然后,在解密主密钥之后,解密存储的密码散列并验证密码是否满足散列。如果您这样做,请务必对散列进行加密,并使用不同的salt(否则会丢掉其余的工作)。请认真阅读:我的应用程序是一个密码管理器。如果我理解正确,我必须散列主密码并加密SQLite表的密码?因为散列无法恢复原始文本。@AlexanderTobiasHeinrich该链接太棒了。不应该进行审核以确保输入的密码正确吗?