2010-07-12 4 views
10

Eine Webanwendung enthält sensible Daten der Benutzer. Weder der Betreiber der Web-Anwendung noch der Hosting-Anbieter sollte diese Daten sehen können. Daher wollte ich diese Daten verschlüsselt in der DB mit dem Zugangs-Passwort der Benutzer speichern.Verschlüsselungsstrategie zur Sicherung sensibler Daten

dataInDB = encrypt (rawData, user password) 

Mit dieser Strategie es jedoch nicht möglich ist, den üblichen Anwendungsfall für Passwort-Wiederherstellung zu implementieren: Da in der Regel nur der Hash-Wert des Passworts durch das Web-App gespeichert ist, kann die Anwendung nicht das alte, vergessene Passwort senden für den Benutzer. Und mit der Vergabe eines neuen, zufälligen Passworts sind die verschlüsselten Daten in der DB nicht mehr lesbar.

Gibt es eine andere Lösung?

+1

+1: Große Frage. Was für eine schreckliche Situation zu sein ... –

Antwort

7

Eine mögliche Lösung (ich bin nicht für die Zerstörung verantwortlich):

Wenn sensible Daten zu verschlüsseln, nicht das Kennwort des Benutzers verwendet als Schlüssel. Eher, leitet den Schlüssel aus dem Benutzerpasswort (vorzugsweise mit einem Standardalgorithmus wie PBKDF2). Für den Fall, dass der Benutzer sein Passwort vergisst, können Sie eine Kopie dieses abgeleiteten Schlüssels behalten (verschlüsselt mit einem anderen Schlüssel, der von der Antwort des Benutzers abgeleitet wurde). Wenn der Benutzer sein Passwort vergisst, kann er seine Sicherheitsfrage beantworten. Nur die richtige Antwort entschlüsselt das ursprüngliche Passwort Schlüssel (nicht das ursprüngliche Passwort). Dies bietet Ihnen die Möglichkeit, die vertraulichen Informationen erneut zu verschlüsseln.

Ich werde demonstrieren mit (Python-esque) Pseudocode, aber zuerst schauen wir uns eine mögliche Tabelle für die Benutzer. Lassen Sie sich nicht nur noch in den Spalten gefangen, werden sie bald klar geworden ...

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 
) 

Wenn es darum geht, einen Benutzer zu registrieren, müssen sie eine Frage zur Verfügung stellen und beantworten:

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) 

Sollte der Benutzer sein Passwort vergessen, muss das System den Benutzer trotzdem bitten, seine Sicherheitsfrage zu beantworten. Ihr Passwort kann nicht wiederhergestellt werden, aber der Schlüssel kann aus dem Passwort abgeleitet werden. Dadurch kann das System auf die sensiblen Daten mit dem neuen Passwort erneut verschlüsseln:

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) 

Natürlich gibt es einige allgemeine Verschlüsselungs Prinzipien hier nicht explizit hervorgehoben (Chiffre-Modi, etc ...), die das sind Verantwortung des Implementierers, sich damit vertraut zu machen.

Hoffe das hilft ein wenig! :)

-Update mit freundlicher Genehmigung von Eadwacer Kommentar

Wie Eadwacer kommentiert:

ich vermeiden würde den Schlüssel direkt aus dem Passwort (begrenzte Entropie abzuleiten und das Kennwort zu ändern benötigen alle die erneut verschlüsseln Daten). Erstellen Sie stattdessen einen zufälligen Schlüssel für jeden Benutzer und verwenden Sie das Kennwort, um den Schlüssel zu verschlüsseln. Sie würden den Schlüssel auch mit einem Schlüssel verschlüsseln, der von den Sicherheitsfragen abgeleitet wurde.

Hier ist eine modifizierte Version meiner Lösung seine exzellente Beratung berücksichtigt:

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, 
) 

Sie dann den Benutzer registrieren würde, wie folgt:

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) 

nun das Kennwort eines Benutzers zurückzusetzen sieht so aus:

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) 

Hoffentlich funktioniert mein Kopf heute Nacht immer noch klar ...

+0

Danke Adam, das ist genau das, wonach ich suche. Ich werde versuchen, diese Lösung in den nächsten Tagen in Java mit der Java Crypto API zu implementieren und das Ergebnis hier zu veröffentlichen. – Dominik

+0

Eins ist mir bis jetzt nicht klar: Für den normalen Anwendungsfall der Webapp (Login und siehe die -verschlüsselten- Benutzerdaten) sollte der Benutzer die Sicherheitsfrage nicht beantworten. Ist es richtig, dass der geheime Schlüssel des Kennworts, der für die Verschlüsselung der sensiblen Daten verwendet wird, auf die gleiche Weise wie bei der ursprünglichen Erstellung dieses Schlüssels abgeleitet werden muss. Und um den gleichen Schlüssel zu reproduzieren, werden Iterationen und Salz auch innerhalb des Db gespeichert. – Dominik

+0

@Dominik: Sie haben Recht, deshalb speichern wir die Iterationen und Salz in der Datenbank. –

Verwandte Themen