【问题标题】:Coding strategy for securing sensitive data保护敏感数据的编码策略
【发布时间】:2011-03-14 18:41:56
【问题描述】:

Web 应用程序包含用户的敏感数据。 Web 应用程序的运营商和托管服务提供商都不能看到这些数据。因此,我想将这些数据存储在使用用户的入口密码加密的数据库中。

dataInDB = encrypt (rawData, user password) 

但是,使用此策略无法实现密码恢复的常见用例:由于 Web 应用程序通常只存储密码的哈希值,因此应用程序无法将旧的、忘记的密码发送给用户。并且分配了一个新的巧合密码,数据库中的加密数据不再可读。

还有其他解决办法吗?

【问题讨论】:

  • +1:好问题。处于多么糟糕的境地……

标签: security encryption


【解决方案1】:

一个可能的解决方案(我不对任何破坏负责):

加密敏感数据时,不要使用用户密码作为密钥。相反,派生用户密码的密钥(最好使用标准算法,例如 PBKDF2)。万一用户忘记了他们的密码,您可以保留这个派生密钥的副本(使用从用户的答案派生的不同密钥进行加密)。如果用户忘记了密码,他们可以回答他们的安全问题。只有正确答案才能解密原始密码key(不是原始密码)。这使您有机会重新加密敏感信息。

我将使用(Python 风格的)伪代码进行演示,但首先让我们看一下可能的用户表。暂时不要陷入专栏,它们很快就会变得清晰......

CREATE TABLE USERS
(
    user_name               VARCHAR,

    -- ... lots of other, useful columns ...

    password_key_iterations NUMBER,
    password_key_salt       BINARY,
    password_key_iv         BINARY,
    encrypted_password_key  BINARY,
    question                VARCHAR,
    answer_key_iterations   NUMBER,
    answer_key_salt         BINARY
)

在注册用户时,他们必须提供问题和答案:

def register_user(user_name, password, question, answer):
    user = User()

    # The question is simply stored for later use
    user.question = question

    # The password secret key is derived from the user's password
    user.password_key_iterations = generate_random_number(from=1000, to=2000)
    user.password_key_salt = generate_random_salt()
    password_key = derive_key(password, iterations=user.password_key_iterations, salt=user.password_key_salt)

    # The answer secret key is derived from the answer to the user's security question
    user.answer_key_iterations = generate_random_number(from=1000, to=2000)
    user.answer_key_salt = generate_random_salt()
    answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)

    # The password secret key is encrypted using the key derived from the answer
    user.password_key_iv = generate_random_iv()
    user.encrypted_password_key = encrypt(password_key, key=answer_key, iv=user.password_key_iv)

    database.insert_user(user)

如果用户忘记了密码,系统仍将要求用户回答他们的安全问题。他们的密码无法恢复,但从密码中导出的key可以。这允许系统使用密码重新加密敏感信息:

def reset_password(user_name, answer, new_password):
    user = database.rerieve_user(user_name)

    answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)

    # The answer key decrypts the old password key
    old_password_key = decrypt(user.encrypted_password_key, key=answer_key, iv=user.password_key_iv)
    
    # TODO: Decrypt sensitive data using the old password key

    new_password_key = derive_key(new_password, iterations=user.password_key_iterations, salt=user.password_key_salt)

    # TODO: Re-encrypt sensitive data using the new password key

    user.encrypted_password_key = encrypt(new_password_key, key=user.answer_key, iv=user.password_key_iv)

    database.update_user(user)

当然,这里没有明确强调一些通用的加密原则(密码模式等),实施者有责任熟悉这些原则。

希望这会有所帮助! :)

根据 Eadwacer 的评论更新

正如 Eadwacer 所说:

我会避免直接从密码中获取密钥(有限的熵和更改密码需要重新加密所有数据)。相反,为每个用户创建一个随机密钥并使用密码来加密密钥。您还可以使用从安全问题派生的密钥来加密密钥。

这是我的解决方案的修改版本,考虑了他的出色建议:

CREATE TABLE USERS
(
    user_name                      VARCHAR,

    -- ... lots of other, useful columns ...

    password_key_iterations        NUMBER,
    password_key_salt              BINARY,
    password_encrypted_data_key    BINARY,
    password_encrypted_data_key_iv BINARY,
    question                       VARCHAR,
    answer_key_iterations          NUMBER,
    answer_key_salt                BINARY,
    answer_encrypted_data_key      BINARY,
    answer_encrypted_data_key_iv   BINARY,
)

然后您将按如下方式注册用户:

def register_user(user_name, password, question, answer):
    user = User()

    # The question is simply stored for later use
    user.question = question

    # The randomly-generated data key will ultimately encrypt our sensitive data
    data_key = generate_random_key()

    # The password key is derived from the password
    user.password_key_iterations = generate_random_number(from=1000, to=2000)
    user.password_key_salt = generate_random_salt()
    password_key = derive_key(password, iterations=user.password_key_iterations, salt=user.password_key_salt)

    # The answer key is derived from the answer
    user.answer_key_iterations = generate_random_number(from=1000, to=2000)
    user.answer_key_salt = generate_random_salt()
    answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)

    # The data key is encrypted using the password key
    user.password_encrypted_data_key_iv = generate_random_iv()
    user.password_encrypted_data_key = encrypt(data_key, key=password_key, iv=user.password_encrypted_data_key_iv)
    
    # The data key is encrypted using the answer key
    user.answer_encrypted_data_key_iv = generate_random_iv()
    user.answer_encrypted_data_key = encrypt(data_key, key=answer_key, iv=user.answer_encrypted_data_key_iv)

    database.insert_user(user)

现在,重置用户密码如下所示:

def reset_password(user_name, answer, new_password):
    user = database.rerieve_user(user_name)

    answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)

    # The answer key decrypts the data key
    data_key = decrypt(user.answer_encrypted_data_key, key=answer_key, iv=user.answer_encrypted_data_key_iv)
    
    # Instead of re-encrypting all the sensitive data, we simply re-encrypt the password key
    new_password_key = derive_key(new_password, iterations=user.password_key_iterations, salt=user.password_key_salt)

    user.password_encrypted_data_key = encrypt(data_key, key=new_password_key, iv=user.password_encrypted_data_key_iv)

    database.update_user(user)

希望今晚我的头脑仍然正常运转......

【讨论】:

  • 谢谢亚当,这正是我要找的。我将尝试在接下来的几天内使用 Java Crypto API 在 Java 中实现这个解决方案,并将结果发布在这里。
  • 直到现在我还不清楚一件事:对于 webapp 的正常用例(登录并查看 -decrypted- 用户数据),用户不应该回答安全问题。是否正确,用于加密敏感数据的密码密钥必须以与初始创建该密钥相同的方式派生。为了重现相同的密钥,迭代和盐也存储在 Db 中。
  • @Dominik:你是对的,这就是我们将迭代和盐存储在数据库中的原因。
  • @Frank:见stackoverflow.com/questions/184112/…。他们推荐 16 字节的随机数据。
  • 我会避免直接从密码中获取密钥(有限的熵和更改密码需要重新加密所有数据)。相反,为每个用户创建一个随机密钥并使用密码来加密密钥。您还可以使用从安全问题派生的密钥来加密密钥。
猜你喜欢
  • 2015-04-22
  • 2014-07-14
  • 1970-01-01
  • 2019-05-09
  • 1970-01-01
  • 1970-01-01
  • 2011-01-19
  • 2012-02-16
  • 1970-01-01
相关资源
最近更新 更多