Wachtwoorden versleutelen zoals het hoort

Wanneer je een webapp bouwt waarin mensen met een gebruikersnaam en wachtwoord moeten kunnen inloggen, besteed dan in ieder geval veel aandacht aan het versleutelen van het wachtwoord. Dit zorgt niet alleen voor "discretie" bij jouwzelf (je kunt nooit zien wat iemands wachtwoord is, alleen het versleutelde exemplaar), maar het zorgt er ook voor dat áls het gebeurt dat de gebruikersnamen en wachtwoorden op straat komen te liggen, deze niet te misbruiken zijn.

Via deze pagina vind je een handreiking voor het versleutelen van wachtwoorden.

Dit versleutelen gebeurt door eenmalig "zout" te creëren, dit aan het wachtwoord van iemand vast te plakken en vervolgens het wachtwoord gecombineerd met het zout 50000 keer te versleutelen met behulp van SHA265 (265bits encryptie).

Werk je met een database met gebruiksaccounts, dan is het de bedoeling dat er voor elke nieuwe gebruiker eenmalig "zout" wordt gecreëerd. Een tabel waarin gebruikersaccounts zouden worden opgeslagen zou er dan zo uit kunnen zien:

Herhaling: de kolom "wachtwoord" bevat het wachtwoord met vastgeplakt zout dat 50000 keer versleuteld is. De kolom "zout" is nodig voor als iemand wil inloggen.

Voorbeeld van een inlogproces

Een inlogproces zou er dan zo uit zien:

  1. Een gebruiker vult zijn gebruikersnaam+wachtwoord in en klikt op "inloggen".
  2. Er wordt gecheckt of de gebruikersnaam in de database voor komt. Als dat zo is, wordt de waarde uit de kolom "zout" opgehaald.
  3. Nu wordt het "zout" vastgeplakt aan het wachtwoord dat de gebruiker invulde. Vervolgens wordt deze combinatie 50000 x versleuteld.
  4. Nu wordt gecheckt of het versleutelde exemplaar van het wachtwoord hoort bij de betreffende gebruiker in de database. Is dat zo? Dan klopt alles, en is de gebruiker ingelogd.
  5. Klopt de combinatie van gebruikersnaam en versleuteld wachtwoord niet, dan krijgt de gebruiker een melding te zien dat de combinatie van gebruikersnaam en wachtwoord niet klopt (al dan niet met een herstel-link)

Testje met versleutelen

Stel, je hebt een wachtwoord "DingFlofBips"

En er is willekeurig zout gecreëerd: "462a8678214a5a26"

Als je "DingFlofBips" aan "462a8678214a5a26" vastplakt, krijg je "DingFlofBips462a8678214a5a26"

Na 1 versleuteling wordt dit: "bf1c809c8a18adcbd4cac2272d9318510c02da0ef86985d508db092f627d103f"

En na 50000 keer versleutelen wordt het: "768408da39df613512dd94cf5b5af1185f5818ce81b8e15ade6b624e748646ec". Dit exemplaar stop je in je database, evenals het oorspronkelijke zout "462a8678214a5a26". Geen mens of computer die dit zo 1-2-3 kan terugredeneren naar het wachtwoord van de gebruiker! Het zal echter wel altijd verifieerbaar blijven.

Zelf met versleuteling stoeien?

Bovenstaand testje is gebaseerd op deze code:

<?php
    
# Vul hieronder een wachtwoord in:
    
$password "DingFlofBips";

    
printf("Stel, je hebt een wachtwoord \"%s\" <br />"$password);

    
# Dit is een voorbeeld van hoe zout kan worden gecreëerd:
    
$salt dechex(mt_rand(02147483647)) . dechex(mt_rand(02147483647));

    
printf("En er is willekeurig zout gecreëerd: \"%s\" <br />"$salt);

    
# Op deze manier kun je het zout vastplakken aan het wachtwoord
    
$salted_password $password $salt;

    
printf("Als je \"%s\" aan \"%s\" vastplakt, krijg je \"%s\" <br />"$password$salt$salted_password);

    
# Hieronder wordt de een variabele voorbereid voor de versleutelde wachtwoord-zout combinatie (dit is de eerste keer versleuteling)
    
$salted_encrypted_password hash('sha256'$salted_password);

    
printf("Na 1 versleuteling wordt dit: \"%s\" <br />"$salted_encrypted_password);

    
# Nu wordt het versleutelde combinatie nog eens 49999 maal versleuteld
    
for($round 1$round 50000$round++){
        
$salted_encrypted_password hash('sha256'$salted_encrypted_password);
    }

    
printf("
        En na 50000 keer versleutelen wordt het: \"%s\".
        Dit exemplaar stop je in je database, evenals het oorspronkelijke zout \"%s\". 
        Geen mens of computer die dit zo 1-2-3 kan terugredeneren naar het wachtwoord van de gebruiker!
        Het zal echter wel altijd verifieerbaar blijven.<br />"
$salted_encrypted_password$salt);
?>

Voorbeeld van een inlogprocedure in PHP

Hieronder zie je een voorbeeld van een inlogcode die met versleuteling werkt:

<?php
    
# Beveiligingsmaatregel: als er niks is ingevuld, stop dan.
    
if(empty($_POST)) exit;

    
# Ingevulde gebruikersnaam en wachtwoord inventariseren:
    
$username $_POST['username'];
    
$password $_POST['password'];

    
# Kijken of de gebruiker voorkomt:
    
$stmt mysqli_prepare($link"SELECT id, password, salt FROM users WHERE username = ?");
    
mysqli_stmt_bind_param($stmt"s"$username);
    
mysqli_stmt_execute($stmt);
    
mysqli_stmt_bind_results($stmt$id$encrypted_pwd$salt);
    
mysqli_stmt_fetch($stmt);

    
# Als $id, $encrypted_pwd en $salt gevuld zijn, betekent het dat de gebruiker in ieder geval bestaat.
    
if($id && $encrypted_pwd && $salt){
        
# Eerst het ingevulde wachtwoord met het zout plakken en 1x encrypten
        
$check_password hash('sha256'$password $salt);

        
# Nu wordt de versleutelde combinatie nog eens 49999 maal versleuteld
        
for($round 1$round 50000$round++){
            
$check_password hash('sha256'$check_password);
        }

        
# Komt het 50000 maal versleutelde wachtwoord overeen met dat in de database? Dan mag de gebruiker inloggen!
        
if($check_password === $encrypted_pwd){
            
# De gebruiker mag inloggen want het wachtwoord klopt met datgene in de database!
        
}
        else{
            
# Het ingevulde wachtwoord klopt niet. Toon een algemene foutmelding
            
echo "Gebruikersnaam of wachtwoord klopt niet.";
            exit;
        }

    }else{
        
# Gebruiker bestaat niet. Toon een algemene foutmelding
        
echo "Gebruikersnaam of wachtwoord klopt niet.";
        exit;
    }
?>