我一直在尝试了解对称加密的工作原理以及如何将其集成到CLI应用程序中,但在某些时候会遇到问题,下面将对此进行介绍。
我的用例如下:
我有一个CLI应用程序(SQLAlchemy
+click
+ Python 3.8
),这将是一个非常简单的密码管理器(个人使用)。
开始时,我想询问用户一个主密码,以便他能够从数据库中检索任何信息。如果用户还没有主密码,我将请他创建一个。我希望所有数据都使用相同的主密钥进行加密。
要做上述所有事情,我认为对称加密将是最合适的,并且想到了Fernet,所以我开始编写一些代码:
import base64
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
def generate_key_derivation(salt, master_password):
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend()
)
key = base64.urlsafe_b64encode(kdf.derive(master_password.encode()))
return key
def encrypt(key, value_to_encrypt):
f = Fernet(key)
encrypted_key = f.encrypt(value_to_encrypt.encode())
return encrypted_key
def decrypt(key, encrypted_key):
f = Fernet(key)
try:
return f.decrypt(encrypted_key)
except InvalidToken:
return b''
现在,我有点想从文档中了解这一点:
在此方案中,盐必须存储在可检索的位置以便将来从密码中导出相同的密钥。
这在我的脑海中意味着:将盐存储在DB中,并在用户每次尝试使用该应用程序时使用它。然后,运行用户通过密钥派生功能插入的主密码,并检查它是否与...密钥匹配? 但是我没有初始密钥,因为我没有和盐一起第一次存储它。如果我要保存它,难道没有人能够随意使用它来加密和解密数据吗?
用于防止上述情况的常见解决方案是什么?
这里是使用click
的小POC:
import os
import click
from models import MasterPasswordModel
@click.group(help="Simple CLI Password Manager for personal use")
@click.pass_context
def main(ctx):
# if the user hasn't stored any master password yet,
# create a new one
if MasterPasswordModel.is_empty():
# ask user for a new master password
master_password = click.prompt(
'Please enter your new master password: ',
hide_input=True
)
# generate the salt
salt = os.urandom(16)
# generate key_derivation
# this isn't stored because if it does anyone would be able
# to access any data
key = generate_key_derivation(salt, master_password)
# store the salt to the DB
MasterPasswordModel.create(salt)
# if the user stored a master password, check if it's valid and
# allow him to do other actions
else:
# ask user for existing master password
master_password = click.prompt(
'Please enter your new master password: ',
hide_input=True
)
# get existing master password salt from DB
salt = MasterPasswordModel.get_salt()
# generate key_derivation
key = generate_key_derivation(salt, master_password)
# At this point I don't know how to check whether the `key` is
# valid or not since I don't have anything to check it against.
# what am I missing?
我希望所有这些都说得通。作为TL; DR,我认为问题是:如何安全地存储密钥,以便可以进行进一步检查?还是那应该怎么做?我想念什么?我敢肯定我误会了一些东西:)
LE:如其中一条注释中所指定,似乎可以为我提供解决方案,但在此过程中我仍然会遇到困难。在this answer中指定:
如果您还没有这样做,我也强烈建议not直接使用用户提供的密钥,但首先传递它通过故意慢速键派生功能,例如PBKDF2,bcrypt或scrypt。您应该首先执行此操作,甚至要尝试之前验证密钥的正确性,并立即丢弃原始用户提供的密钥,并将派生的密钥用于所有内容(包括验证和实际的加密/解密)。
所以,让我们一步步举例说明:
1)首次要求我输入主密码。它在数据库中不存在,因此,显然,我必须创建并存储它。
2)与新生成的盐一起,我必须保存提供的主密码的哈希值(为了示例,我将使用SHA-256)。
3)我现在有一条包含盐和哈希主密码的记录,因此我可以继续使用该应用程序。我现在想在数据库中创建一条新记录,据称该记录将使用我的key。
进行加密。问题是... 什么键?如果要应用上面写的内容,则必须使用我的generate_key_derivation()
函数(使用来自数据库的salt和哈希主密码)并将其用于加密/解密。但是,如果我这样做了,没有人能使用存储在数据库中的hash_key并使用相同的generate_key_derivation
来完成他想要的任何事情吗?
所以,我想念什么?
我不是加密专家,但我想这个想法是像这样存储派生密钥的盐和哈希值:
稍后使用salt和hash验证派生密钥是真实的,如下所示: