2010-07-12 8 views
10

Una aplicación web contiene datos confidenciales de los usuarios. Ni el operador de la aplicación web ni el proveedor de alojamiento deberían poder ver estos datos. Por lo tanto, quería almacenar estos datos en el DB encriptados con la contraseña de entrada de los usuarios.Estrategia de codificación para proteger datos confidenciales

dataInDB = encrypt (rawData, user password) 

Con esta estrategia, sin embargo, no es posible implementar el caso de uso habitual para la recuperación de la contraseña: Dado que por lo general sólo el valor hash de la contraseña es almacenada por la aplicación web, la aplicación no puede enviar el viejo, contraseña olvidada para el usuario. Y con la asignación de una nueva contraseña coincidente, los datos encriptados en el DB ya no son legibles.

¿Hay alguna otra solución?

+1

+1: Gran pregunta. Qué situación tan horrible ... –

Respuesta

7

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 ...

+0

Gracias Adam, esto es exactamente lo que estoy buscando. Trataré de implementar esta solución dentro de los próximos días en Java utilizando Java Crypto API y publicaré el resultado aquí. – Dominik

+0

Una cosa no está clara para mí hasta ahora: Para el caso de uso normal de la aplicación web (inicie sesión y vea los datos del usuario descifrado), el usuario no debe responder la pregunta de seguridad. ¿Es correcto que la clave secreta de contraseña, que se utiliza para cifrar los datos confidenciales, se obtenga de la misma manera que para la creación inicial de esa clave. Y para reproducir la misma clave, las iteraciones y sal también se almacenan dentro del Db. – Dominik

+0

@Dominik: Tiene razón, es por eso que almacenamos las iteraciones y sal en la base de datos. –

Cuestiones relacionadas