一个可能的解决方案(我不对任何破坏负责):
加密敏感数据时,不要使用用户密码作为密钥。相反,派生用户密码的密钥(最好使用标准算法,例如 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)
希望今晚我的头脑仍然正常运转......