Возможное решение (я не несу ответственности за уничтожение):
При шифровании конфиденциальных данных не используйте пароль пользователя в качестве ключа. Скорее, извлекает ключ из пароля пользователя (предпочтительно с использованием стандартного алгоритма, такого как PBKDF2). На случай, если пользователь забудет свой пароль, вы можете сохранить копию этого производного ключа (зашифрованного с использованием другого ключа, полученного из пользовательского answer ). Если пользователь забудет свой пароль, он может ответить на секретный вопрос. Только правильный ответ расшифрует исходный пароль ключ (не оригинальный пароль). Это дает вам возможность повторно зашифровать конфиденциальную информацию.
Я продемонстрирую использование (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)
Если пользователь забудет свой пароль, системе все равно придется попросить пользователя ответить на его секретный вопрос. Их пароль не может быть восстановлен, но ключ , полученный из пароля, может быть. Это позволяет системе повторно зашифровать конфиденциальную информацию, используя новый пароль:
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)
Надеюсь, моя голова все еще четко работает сегодня вечером ...