Web应用程序包含用户的敏感数据。 Web应用程序的运营商和托管提供商都不应该能够看到这些数据。因此,我想将这些数据存储在使用用户的入口密码加密的DB中。
dataInDB = encrypt (rawData, user password)
但是,使用此策略无法实现密码恢复的常用用例:由于通常只有密码的哈希值由Web应用程序存储,因此应用程序无法将旧的,忘记的密码发送给用户。通过分配新的巧合密码,DB中的加密数据不再可读。
还有其他解决方案吗?
答案 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评论的那样:
我会避免直接从密码中获取密钥(有限的熵和更改密码将需要重新加密所有数据)。而是为每个用户创建一个随机密钥,并使用密码加密密钥。您还可以使用从安全问题派生的密钥加密密钥。
以下是我的解决方案的修改版本,考虑了他的优秀建议:
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)
希望我的脑袋今晚仍能正常运作......