用于保护敏感数据的编码策略

时间:2010-07-12 11:45:08

标签: security encryption

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

dataInDB = encrypt (rawData, user password) 

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

还有其他解决方案吗?

1 个答案:

答案 0 :(得分:7)

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

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

我将演示使用(Python-esque)伪代码,但首先让我们看看用户可能的表。不要陷入专栏,他们很快就会变得清晰......

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)

如果用户忘记了密码,系统仍然需要让用户回答他们的安全问题。他们的密码无法恢复,但从密码派生的密钥可以是。这允许系统使用 new 密码重新加密敏感信息:

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)

希望我的脑袋今晚仍能正常运作......