[在Python中使用Fernet进行对称加密-主密码用例

问题描述 投票:0回答:1

我一直在尝试了解对称加密的工作原理以及如何将其集成到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来完成他想要的任何事情吗?

所以,我想念什么?

python encryption sqlalchemy encryption-symmetric
1个回答
0
投票

我不是加密专家,但我想这个想法是像这样存储派生密钥的盐和哈希值:

  1. 首次获取主密码
  2. 产生盐
  3. 使用盐和主密码导出新密钥
  4. 丢弃主密码
  5. 哈希派生密钥
  6. 将盐和派生密钥存储在数据库中
  7. 使用派生密钥来加密存储的密码

稍后使用salt和hash验证派生密钥是真实的,如下所示:

  1. 获取主密码
  2. 从数据库获取盐和哈希值
  3. 使用盐和主密码派生密钥
  4. 丢弃主密码
  5. 哈希派生密钥
  6. 通过查看散列是否匹配数据库中的散列来验证派生密钥
  7. 如果不匹配,请退出
  8. 否则,请使用派生密钥解密其他密码。
© www.soinside.com 2019 - 2024. All rights reserved.