Una posible solución (no soy responsable de cualquier destrucción):
Durante el cifrado de datos sensibles, no utilice la contraseña del usuario como la clave. Por el contrario, deriva la clave de la contraseña del usuario (preferiblemente utilizando un algoritmo estándar como PBKDF2). En caso de que el usuario olvide su contraseña, puede conservar una copia de esta clave derivada (cifrada con una clave diferente derivada de la respuesta del usuario). Si el usuario olvida su contraseña, puede responder su pregunta de seguridad. Solo la respuesta correcta descifrará la contraseña original clave (no la contraseña original). Esto le brinda la oportunidad de volver a cifrar la información confidencial.
Demostraré usando un seudo código (Python-esque), pero primero veamos una posible tabla para los usuarios. No quedar atrapado en las columnas por el momento, que se pondrán de manifiesto pronto ...
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
)
Cuando llega el momento de registrar un usuario, deben proporcionar una pregunta y respuesta:
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)
En caso de que el usuario olvide su contraseña, el sistema tendrá que pedirle al usuario que responda su pregunta de seguridad. Su contraseña no se puede recuperar, pero la clave derivada de la contraseña puede ser. Esto permite que el sistema para volver a cifrar la información sensible usando la contraseña nueva :
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)
Por supuesto, hay algunos principios generales criptográficas no explícitamente resaltados aquí (modos de cifrado, etc ...) que son la responsabilidad del implementador de familiarizarse con
Espero que esto ayude un poco! :)
actualización cortesía de comentario de Eadwacer
Como Eadwacer comentó:
evitaría derivar la clave directamente de la contraseña (entropía limitado y cambio de la contraseña será necesario volver a cifrar toda la datos). En su lugar, cree una clave aleatoria para cada usuario y use la contraseña para encriptar la clave. También encriptaría la clave usando una clave derivada de las preguntas de seguridad.
Aquí es una versión modificada de mi solución teniendo su excelente consejo en consideración:
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,
)
A continuación, registrar el usuario de la siguiente manera:
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)
Ahora, al restablecer la contraseña de un usuario se ve así:
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)
Afortunadamente, mi cabeza sigue funcionando claramente esta noche ...
+1: Gran pregunta. Qué situación tan horrible ... –