【问题标题】:Symmetric encryption using Fernet in Python - Master password use case在 Python 中使用 Fernet 进行对称加密 - 主密码用例
【发布时间】:2020-05-24 11:53:02
【问题描述】:

我一直试图了解对称加密的工作原理以及如何将其集成到我的 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''

现在,我有点想从文档中理解这一点:

在此方案中,盐必须存储在可检索的位置 以便将来从密码中派生相同的密钥。

在我看来,这意味着:将盐存储在数据库中,并在用户每次尝试使用应用程序时使用它。然后,运行用户通过密钥派生函数插入的主密码并检查它是否匹配......密钥? 但我没有初始密钥,因为我第一次没有将它与盐一起存储。而且如果我要保存,难道没有人可以随意使用它来加密和解密数据吗?

防止上述情况的常用解决方案是什么?

这是一个使用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:正如其中一个 cmets 中所指定的,看起来我可能有一个解决方案,但我仍然在这个过程中的某个地方卡住了。在this answer 中指定:

如果您还没有这样做,我也强烈建议您不要 直接使用用户提供的密钥,而是先传递它 通过故意缓慢的密钥派生函数,例如 PBKDF2, bcrypt 或 scrypt。你应该先这样做,在尝试之前 验证密钥的正确性,并立即丢弃 用户提供的原始密钥,并将派生密钥用于所有内容 (验证和实际加密/解密)。

所以,让我们一步一步来举例:

1) 我第一次被要求输入主密码。它在 DB 中不存在,所以很明显,我必须创建并存储它。

2) 除了新生成的 salt,我还必须保存提供的主密码的哈希值(例如,我将使用 SHA-256)。

3) 我现在有一个包含盐和散列主密码的记录,因此我可以继续使用该应用程序。我现在想在 DB 中创建一条新记录,据说它会使用我的 key 进行加密。

问题是……什么键?如果我要应用上面写的内容,我必须使用我的 generate_key_derivation() 函数,使用来自 DB 的盐和散列主密码,并将其用于加密/解密。但是,如果我这样做,任何人都无法获取存储在 DB 中的 hash_key,并使用相同的generate_key_derivation 做任何他想做的事吗?

那么,我错过了什么?

【问题讨论】:

  • 为什么要存储密钥?给定相同的主密码和相同的盐,您可以派生相同的密钥。所以你只需要储存盐。
  • @PresidentJamesK.Polk 我知道。但是,如果您没有密钥,如何验证主密码是正确的呢?
  • 啊,我明白了,这是个好问题。我可以想到一些特别的想法,比如从 KDF 生成 32 个额外的字节并将 16 个字节的一半异或并存储起来。但是您应该使用正确分析的方法,而我不知道任何方法。
  • 在加密堆栈交换上有一个很好的答案:crypto.stackexchange.com/questions/1507/…
  • @rfkortekaas 如果我做对了(接受答案中的前一种方法),我应该添加一个散列函数(可能是 SHA-512),它基本上会散列用户将输入的 master_password并将该哈希存储在盐旁边,对吗?

标签: python encryption sqlalchemy encryption-symmetric


【解决方案1】:

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

  1. 第一次获取主密码
  2. 生成盐
  3. 使用盐和主密码派生新密钥
  4. 放弃主密码
  5. 散列派生密钥
  6. 将盐和派生密钥存储在数据库中
  7. 使用派生密钥加密存储的密码

稍后使用盐和哈希来验证派生密钥的真实性,如下所示:

  1. 获取主密码
  2. 从数据库中获取盐和哈希
  3. 使用盐和主密码派生密钥
  4. 放弃主密码
  5. 散列派生密钥
  6. 通过查看哈希是否与数据库中的哈希匹配来验证派生密钥
  7. 如果不匹配,退出
  8. 否则,请使用派生密钥解密其他密码。

【讨论】:

  • 嗯,即使在这种情况下,是什么阻止了有人直接从数据库中获取派生密钥并使用它来解密其他密码?
  • @GrajdeanuAlex。派生密钥未存储在任何地方hash 被存储。您不能使用哈希来生成生成的密钥,您只能从主密码生成生成的密钥。哈希仅用于有效地验证主密码。因此,阻止某人获取派生密钥的原因是他们无法获得无法获得的东西。
猜你喜欢
  • 2021-02-15
  • 2022-12-30
  • 1970-01-01
  • 1970-01-01
  • 2021-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
相关资源
最近更新 更多