[TUT] Wachtwoord salt

ICTscripters maakt gebruik van cookies. Door het gebruiken en browsen naar onze site gaat je automatisch akkoord met het gebruik van cookies. Klik hier voor meer informatie

  • Ik merk dat veel, vooral onervaren, programmeurs wachtwoorden vaak onveilig opslaan in een database. Daarom heb ik een tutorial met hoe je wachtwoorden behoorlijk veilig kan opslaan in een database door middel van een salt.

    Wat is een salt?
    Een salt is een extra random zin (string) die je achter iemand zijn wachtwoord plakt. Door dit te doen word het wachtwoord vrijwel identiek en kan deze o.a. niet worden 'gekraakt' door een Rainbow Table.

    Hoe gebruik je een salt?
    Een salt word dus gebruikt om achter iemand zijn wachtwoord te zetten. Deze salt word tijdens het registreren aangemaakt, achter het opgegeven wachtwoord gezet en dan gehasht door middel van een hash. Deze salt moet ook opgeslagen worden in de database, want steeds als de gebruiker na het registreren wil inloggen geeft deze zijn normale niet gehashte en gesalte wachtwoord op. Als de persoon dus inlogt word zijn gehashte + gesalte wachtwoord uit de database opgehaald, de salt die is opgehaald vanuit de database word achter het opgegeven wachtwoord geplakt, dan word het opgegeven wachtwoord met de salt weer gehasht en als laatste word gekeken of deze 2 wachtwoorden overeenkomen. Komen deze wachtwoorden overheen dat word de gebruiker natuurlijk ingelogd. Zo niet kan je een fout melding geven dat de gebruikers gegevens onbekend zijn.

    Voorbeeld
    Ik heb een class gemaakt die wachtwoorden een salt geeft en daarna meteen hasht met sha256.

    Eerste moeten we de database gegevens specificeren op regel 11 t/m 15 van de class. Hier moet je je database host, user, pass en naam + de tabel rij van de gebruikers neer zetten.

    Daarna beginnen we met het includen van de class en het aanmaken van de instance.

    PHP Source Code

    1. <?php
    2. include 'HashSalt.class.php';
    3. $HashSalt = new HashSalt;


    Nu kunnen we beginnen met het beveiligen van de wachtwoorden.

    We hebben een mooie site gemaakt en onze eerste gebruiker genaamd piet komt zich aanmelden op onze site. Hij vult alles velden netjes in hij gebruikt de username "piet" en zijn wachtwoord is "geweldigePiet3". Nu moeten wij zijn wachtwoord beveiligen. Dit doen we door de functie generateHashSalt($password) te gebruiken.

    PHP Source Code

    1. $safePass = $HashSalt->generateHashSalt("geweldigePiet3");

    Deze functie keert een array terug met het gehashte + gesalte wachtwoord en de salt. Hier de variabele dump van piet zijn nieuwe wachtwoord en salt ($safePass).

    PHP Source Code

    1. array(2) { ["password"]=> string(64) "31224b5799ee4c337b1ff58ad52ca2fb2daf4feb7dc851e00a6078df94f464df" ["salt"]=> string(8) "CB9TkJzX" }


    Het wachtwoord en salt moeten nu nog wel handmatig in de database opgeslagen worden, dit doet de class (nog) niet. Hier wil ik misschien later opties voor maken dat je kan kiezen of de functie ook meteen het wachtwoord zelf opslaat.

    Piet heeft zijn account geactiveerd en probeert in te loggen op onze website. Nu moeten wij controleren of hij zijn juiste wachtwoord heeft ingevoerd. Hier voor gebruiken we de functie checkUserPass($user, $password). Hier kan $user 2 waardes hebben: waarde 1 is de id van de gebruiker (int) of waarde 2 de username van de gebruiker (string). De functie controleert automatisch of het een int is, dus een id of een naam. Daarna gaat hij kijken of deze user bestaat in de database. Zo ja controleert hij of het wachtwoord overeenkomt en keert "true" terug, zo nee keer de functie "false" terug.

    PHP Source Code

    1. if ($HashSalt->checkUserPass("piet", "geweldigePiet3")) {
    2. echo "Ingelogd!";
    3. } else {
    4. echo "Gegevens niet bekend.";
    5. }

    De gegevens van onze Piet kloppen dus hij is nu ingelogd!

    Noot:
    De gebruikers tabel moet deze 4 rijen hebben (dit geldt niet als je de rij namen in de code veranderd) :
    1. `id` INT 11
    2. `username` VARCHAR 30 (lengte eigen keuze)
    3. `password` VARCHAR 64
    4. `usersalt` VARCHAR 8 (tenzij lengte van salt word veranderd op lijn 49)


    Ik hoop dat je er wat aan deze uitleg gehad hebt. Feedback is altijd welkom!
    Met vriendelijke groet,

    Dees Oomens

    1,075x gelezen

Reacties 8

  • Victor -

    @FangorN Ik doelde inderdaad op een User class die in principe elke willekeurige gebruiker zou kunnen zijn.
    Mijn vraag is dan wel waarom je een User class zou willen maken van de actuele (dan al niet geauthenticeerde) gebruiker van het systeem? Dat zou je in principe toch op kunnen nemen in de iedereen overkoepelende User class?

  • FangorN -

    @Victor "Ik zou bijvoorbeeld een User class nooit een authenticate functie meegeven."

    Waarom niet? Een object van de User class (bijvoorbeeld $user of $this->user bij gebruik in een andere klasse) is een interne representatie van een gebruiker, maar de gebruiker zelf komt hier sowieso nooit aan.

    Een authenticate() methode is ook een proef he, deze kan slagen (bij kloppende gegevens) en falen (bij foutieve gegevens). Vervolgens is er best iets voor te zeggen om een methode login() te schrijven die tot doel heeft om een gebruiker X daadwerkelijk "in te loggen". De implementatie van deze methode bevat dan alle stappen die doorlopen moeten worden om een gebruiker in te loggen. Dit doe je uiteraard alleen nadat authenticate() een positief resultaat had, met andere woorden, nadat je (met een grote mate van zekerheid) hebt vastgesteld dat de persoon daadwerkelijk is wie deze claimt te zijn.

    Alles op een hoop gooien is niet het idee van klasses. Hier moet een zekere mate van "separation of concerns" uit spreken. De manier waarop je omgaat met de versleuteling van wachtwoorden en het controleren op de kloppendheid horen bij elkaar, maar om daar dan rechtstreeks je gebruikersadministratie aan te koppelen gaat te ver. Je neemt hier impliciet ontwerpbeslissingen over hoe je usertabel er uit zou moeten zien terwijl dit los zou moeten staan van een wachtwoordcontrole.

    Het klopt dat het niet altijd de oplossing is om alles te scheiden, maar in dit geval was dit zeker verstandiger geweest. En vaak zijn "losse koppelingen" toch een betere oplossing omdat, op het moment dat je ergens functionaliteit wilt aanpassen, de impact daarvan beperkt blijft tot één klasse of zelfs één methode.

    Als je puur kijkt naar de inzetbaarheid/herbruikbaarheid van de hierboven beschreven klasse dat neemt deze enorm toe wanneer je de afhankelijkheid met een hardcoded tabel er gewoon uitgooit. Dat is ook een van de kenmerken/doelen van klasses / OOP in het algemeen: elementaire en *herbruikbare* blokken code.

    EDIT: "Mijn User class bevat (normaal gesproken) enkel informatie over de gebruiker zelf." Ik denk dat we twee verschillende dingen bedoelen met de User class, hiermee bedoel ik: een representatie van de actuele (al dan niet geauthenticeerde) gebruiker van een systeem. Waarschijnlijk bedoel jij met een object van deze klasse "een representatie van een willekeurige gebruiker" (maar niet noodzakelijkerwijs dezelfde persoon die dit object bekijkt via een interface).

  • Victor -

    @FangorN Het verschilt natuurlijk per situatie hoever je wilt gaan. Ik zou bijvoorbeeld een User class nooit een authenticate functie meegeven. Mijn denkwijze hierachter is dat een gebruiker zichzelf niet hoort goed te keuren. Mijn User class bevat (normaal gesproken) enkel informatie over de gebruiker zelf.

    Is het dan praktisch om álles van elkaar los te maken? Ik denk dat dat zeker niet altijd het geval is. Dees zal in dit geval gedacht hebben dat het minder complex zou zijn als hij alles binnen één class zou zetten. Om voor een tutorial speciale database helpers en User classes te gaan aanmaken is misschien ook ietwat aan de uitgebreide kant.

    Je maakt zeker een punt, maar weet dat altijd alles van elkaar losmaken niet altijd hetgeen oplevert wat je zou willen. ;)

  • FangorN -

    Functionaliteit voor het creëren en valideren van hashes lijkt mij een goede zaak want het maakt deze taken eenvoudiger (maar zoals terecht wordt aangegeven zijn hiervoor tegenwoordig de standaard functies password_hash() en password_verify() vanaf PHP 5.5.0). Maar om nu een afhankelijkheid als een database-connectie te introduceren of zelfs zover te gaan dat je (deels) voorschrijft hoe je user tabel er uitziet is nogal vreemd.

    Klassen zouden elk zoveel mogelijk een enkele taak/rol moeten vervullen en zouden "losse koppelingen" tussen elkaar moeten hebben. Bijvoorbeeld, ik zou een User klasse (object) gebruiken voor het uitvoeren van authenticatie door middel van een methode-aanroep (bijvoorbeeld authenticate()) die op zijn beurt een database-object en deze (helper) klasse gebruikt om te bepalen of iemand kloppende gegevens heeft ingevuld.

    Als ik naar de code op github kijk is de code een beetje langdradig voor het daarvoor bestemde doel. Wanneer deze code gerefactored zou worden naar een kleine set methoden die als enig doel het bouwen+checken van hashes zou hebben zou dit een handige helper klasse kunnen zijn.

  • J.Rijdes -

    Erg goede functie DirkZz. Had er ook nog nooit van gehoord, bedankt!

  • WHMCSAddons -

    @DirkZz
    Had nog niet gehoord van die functie, lijkt me wel heel makkelijk om te gebruiken THNX!

  • WebMobiel -

    Ik vind het een hele handige functie Dees! Heb hem geimplenteerd in mijn website. Hartstikke bedankt!

  • DirkZz -

    PHP heeft hiervoor sinds 5.5 een prachtige functie:

    password_hash
    docs.php.net/manual/en/function.password-hash.php

    En ik zou sowieso geen SHA256 gebruiken voor wachtwoorden, deze functie is veel te snel.
    Dit klinkt misschien raar, maar doordat die functie zo snel is, is het ook eenvoudiger om deze te bruteforcen.

    Een goed alternatief zou zijn bcrypt, deze is een stuk langzamer.