crypt() is erg traag
Hier mijn inlogscriptje:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<? php
// controle inloggegevens
$sql = "SELECT klantID, wachtwoord, voornaam, salt " .
"FROM tblKlant " .
"WHERE email = '" . mysql_real_escape_string($_POST["txtEmail"]) . "'";
$resultaat = mysql_query($sql);
if(mysql_num_rows($resultaat) > 0)
{
// e-mailadres gevonden. Wachtwoord controleren.
$rs = mysql_fetch_array($resultaat);
if($rs["wachtwoord"] == crypt(mysql_real_escape_string($_POST["txtPassword"]), $rs["salt"])) // wachtwoord juist
{
// session setten en redirect
}
else
{
// wachtwoord onjuist
$strErrInlog = "<span class=\"warning\">De combinatie van gebruikersnaam<br />en wachtwoord is niet juist.</span><br /><br />";
}
?>
// controle inloggegevens
$sql = "SELECT klantID, wachtwoord, voornaam, salt " .
"FROM tblKlant " .
"WHERE email = '" . mysql_real_escape_string($_POST["txtEmail"]) . "'";
$resultaat = mysql_query($sql);
if(mysql_num_rows($resultaat) > 0)
{
// e-mailadres gevonden. Wachtwoord controleren.
$rs = mysql_fetch_array($resultaat);
if($rs["wachtwoord"] == crypt(mysql_real_escape_string($_POST["txtPassword"]), $rs["salt"])) // wachtwoord juist
{
// session setten en redirect
}
else
{
// wachtwoord onjuist
$strErrInlog = "<span class=\"warning\">De combinatie van gebruikersnaam<br />en wachtwoord is niet juist.</span><br /><br />";
}
?>
2) waarom heeft <antwoord op vraag 1> bedacht dat het op regel 11 nodig is om $_POST["txtPassword"] door mysql_real_escape_string() te halen terwijl het nooit in een query gebruikt zal worden?
3) de tweede parameter voor crypt() moet $rs['wachtwoord'] zijn
4) weet <antwoord op vraag 1> van het bestaan van password_hash() en password_verify() af? (php implementatie voor PHP versies 5.3.7 tot 5.5 bestaat, die de password_hash() documentatie en zoek naar "userland")
De tweede parameter van crypt() bepaald het algoritme. Aangezien je geen voorbeeld hash (crypt() slaat salt in de hash op) gegeven hebt zou ik niet weten of er ook echt crypt_blowfish gebruikt wordt.
A1)ik
A2)ok....dus dat is hier niet nodig (ik ben beginnend PHPer)
A3)Ik wil weten of het wachtwoord uit de database, $rs['wachtwoord'], overeenkomt met het ingevoerde wachtwoord $_POST["txtPassword"].
A4)Ja, wel van gehoord, maar kreeg ik niet aan de praat. Is dat veel sneller dan?
Dit is een voorbeeldhash: $2a$17$NsQYncP5tPv/Q1zBQeqjRuR3r6oZeefzVeFZ4B3gWie5TeaNjuYuy
Het eerste stuk is de gegenereerde salt.
Dat snapte ik al, dus ik weet niet wat je probeert te vertellen, of hoe dat een reactie is op mijn opmerking.
"A4)Ja, wel van gehoord, maar kreeg ik niet aan de praat. Is dat veel sneller dan?"
Nee, achter de schermen wordt de zelfde crypt_blowfish gebruikt, maar het gebruik er van is duidelijker.
uitleg voorbeeldhash:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
$ // scheidingsteken
2a // blowfish, vanaf PHP 5.3.7 anders dan ervoor omdat er een security issue is gefixt, 2x is de oude, 2y is de nieuwe.
$ // scheidingsteken
17 // 2^17 = 131072 rondes, 12-14 is een betere waarde (speel wat met dit getal en de microtime() functie zodat het hashen 100-300 miliseconde duurt)
$ // scheidingsteken
NsQYncP5tPv/Q1zBQeqjRuR3r6oZeefzVeFZ4B3gWie5TeaNjuYuy // komt neer op de base64 representatie van de salt en hash, maar dan met een iets ander alfabet
2a // blowfish, vanaf PHP 5.3.7 anders dan ervoor omdat er een security issue is gefixt, 2x is de oude, 2y is de nieuwe.
$ // scheidingsteken
17 // 2^17 = 131072 rondes, 12-14 is een betere waarde (speel wat met dit getal en de microtime() functie zodat het hashen 100-300 miliseconde duurt)
$ // scheidingsteken
NsQYncP5tPv/Q1zBQeqjRuR3r6oZeefzVeFZ4B3gWie5TeaNjuYuy // komt neer op de base64 representatie van de salt en hash, maar dan met een iets ander alfabet
Wat ik bedoelde met A3) is dat jij zegt dat ik $rs['wachtwoord'] als tweede parameter moet meegeven aan crypt. Bedoel je dit? if($rs["wachtwoord"] == crypt($_POST["txtPassword"], $rs['wachtwoord']))
Dat snap ik niet. Ik heb bij het aanmaken van het wachtwoord een salt gegenereerd. Die heb ik samen met het ingevoerde wachtwoord gehasht. Dus bij het checken van het wachtwoord moet ik weer het ingevoerde wachtwoord en de opgeslagen salt hashen. Met crypt($_POST["txtPassword"], $rs['wachtwoord']) hash ik het ingevoerde wachtwoord en het opgeslagen (gehashte) wachtwoord. Maar misschien begrijp ik het niet goed.
Toevoeging op 04/08/2014 23:46:19:
Ik heb de waarde veranderd en het gaat nu een heel stuk sneller! Dank!
crypt() verwerkt de salt in de uiteindelijke hash, die hash is dus ook een salt aangezien crypt het niet-salt gedeelte dan negeert.
2) Stop met het gebruik van de mysql-functies, want die gaan op termijn verdwijnen. Gebruik in plaats daarvan de mysqli-functies.
3) Zelf vind het altijd fijner om prepared statements te gebruiken. Je hebt dan niet meer het gedoe met mysql_real_escape_string en wanneer je een query meermaals moet uitvoeren is het ook iets efficiënter.
Als code zou je dan zoiets krijgen:
(let op dat de gecrypte wachtwoorden waarschijnlijk niet meer overeenkomen met de PHP-versie)
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$dbh = new mysqli($hostname,$user,$password,$database);
$sql = "
SELECT klantID, voornaam
FROM tblKlant
WHERE email = ?
AND wachtwoord = SHA2(CONCAT(salt,?),256)
";
$stmt = $dbh->prepare($sql);
$stmt->bind_param("ss", $_POST["txtEmail"], $_POST["txtPassword"]);
$stmt->execute();
$stmt->bind_result($klantid,$voornaam);
$result = $stmt->fetch();
$stmt->close();
if ($result)
{
// session setten en redirect
}
else
{
// wachtwoord onjuist
$strErrInlog = "<span class=\"warning\">De combinatie van gebruikersnaam<br />en wachtwoord is niet juist.</span><br /><br />";
}
?>
$dbh = new mysqli($hostname,$user,$password,$database);
$sql = "
SELECT klantID, voornaam
FROM tblKlant
WHERE email = ?
AND wachtwoord = SHA2(CONCAT(salt,?),256)
";
$stmt = $dbh->prepare($sql);
$stmt->bind_param("ss", $_POST["txtEmail"], $_POST["txtPassword"]);
$stmt->execute();
$stmt->bind_result($klantid,$voornaam);
$result = $stmt->fetch();
$stmt->close();
if ($result)
{
// session setten en redirect
}
else
{
// wachtwoord onjuist
$strErrInlog = "<span class=\"warning\">De combinatie van gebruikersnaam<br />en wachtwoord is niet juist.</span><br /><br />";
}
?>
Nog even wat uitleg bij de code:
In regel 2 maak je een connectie met de database (deze komt eigenlijk in de plaats van je oude mysql_connect).
In regel 3-8 staat het SQL-statement. Daarin zie je twee vraagtekens. Bij het preparen van het statement weet MySQL dat we straks op die plaatsen onze eigen parameters gaan invoeren. De eerste parameter wordt dan het email-adres en de tweede het door de gebruiker ingevoerde wachtwoord.
Aan de WHERE heb ik een extra conditie toegevoegd. Die crypt het wachtwoord en kijkt of het overeenkomt met wat er in de database staat. De CONCAT(salt,?) plakt de salt uit de database aan het ingevoerde (text) wachtwoord van de gebruiker. Die gehele string wordt vervolgens gecrypt met SHA2 (256-bits).
In regel 9 wordt het SQL-statement geprepared. MySQL doet dan het voorbereidende werk om de query uit te voeren, maar wacht nog tot je daar daadwerkelijk opdracht toe geeft.
In regel 10 worden de parameters aan de query gebonden: daar zeg je eigenlijk dat MySQL op de plaats van het eerste vraagteken $_POST["txtEmail"] moet invullen en op de plaats van het tweede vraagteken $_POST["txtPassword"]. Met de "ss" geef je aan dat beide parameters strings zijn. De strings hoef je niet meer zelf te escapen; bij prepared statements gebeurt dat automatisch.
In regel 11 wordt de query uitgevoerd.
In regel 12 worden de velden die je in de query opvraagt (in dit geval dus klantID en voornaam) gekoppeld aan PHP-variabelen.
In regel 13 wordt het eerste resultaat-record opgevraagd. Als het wachtwoord correct was, bevatten $klantID en $voornaam nu de waardes die zijn opgehaald uit de database. Als het wachtwoord niet correct was, of als het email-adres niet bestaat, geeft $stmt->fetch() een waarde terug die niet TRUE is, en wordt de foutmelding getoond.
In regel 14 wordt het prepared statement afgesloten. In dit geval ben je slechts geïnteresseerd in het eerste record (als dat er is). Normaal gesproken voer je de close() pas uit als je alle resultaten hebt verwerkt.
In regel 15 en verder kijk je naar het resultaat van de query. Heb je een TRUE-waarde gekregen dan is de combinatie van email-adres en wachtwoord correct en kun je de sessie opbouwen enzovoort.
Let overigens op dat je op het veld 'email' in je tabel een unique index zet; hiermee voorkom je dat hetzelfde email-adres meermaals (met verschillende wachtwoorden) wordt opgeslagen in je database.
Gewijzigd op 05/08/2014 10:17:41 door Willem vp
Is hashen met mysql veiliger of sneller dan via php?
Het email veld is geen unique index, maar bij registratie check ik of het wachtwoord al bestaat. De gebruiker wordt dan niet toegevoegd en krijgt de melding dat er al een gebruiker met dat emailadres is.
Dat betekend dat je de wachtwoorden hashed op een manier die niet geschikt is voor het hashen van wachtwoorden. Wachtwoorden wil je met een relatief traag algoritme hashen, wat willem voorstelt is juist heel snel. Dus minder veilig.
Dos Moonen op 05/08/2014 07:14:54:
crypt() verwerkt de salt in de uiteindelijke hash, die hash is dus ook een salt aangezien crypt het niet-salt gedeelte dan negeert.
Even kijken of ik het nu goed begrijp: $rs['wachtwoord'] is de salt met het gehashte wachtwoord. Als ik dit in de vergelijking gebruik, negeert hij het gehaste wachtwoord en haalt hij dus eigenlijk de salt uit dat veld. Nu sla ik de salt apart op in de database, maar dat hoeft dus helemaal niet.
Gewijzigd op 05/08/2014 11:27:08 door Karin Gijssen
Geen idee of het sneller is. Op mijn systeem is het onmeetbaar snel in ieder geval. ;-) Overigens is snelheid niet echt een argument, want in dit geval zou het misschien zelfs wenselijk zijn als het hashen niet al te snel gaat.
Of het veiliger is: Ja. Het is in ieder geval veiliger dan blowfish, wat je nu gebruikt. Als je nog veiliger wilt zijn, moet je naar een 512-bits SHA (verander dan in de query de 256 in 512), maar dat kost meer opslagruimte. Zowel SHA-256 als SHA-512 zijn voor zover ik weet nog niet 'gekraakt'.
Het grote voordeel van het laten crypten door MySQL is dat je dat allemaal niet in je PHP-code hoeft af te handelen. Ook je gecrypte wachtwoord en zo hoef je niet op te halen. Je voert het opgegeven wachtwoord aan MySQL en ziet wel of er iets terugkomt. Lekker simpel. Hou ik van. ;-)
Overigens: als je je opslagruimte wilt beperken, zou je gebruik kunnen maken van het feit dat de door MySQL gegenereerde SHA-code hexadecimaal is. In plaats van het opslaan in een CHAR of VARCHAR-veld, kun je het wachtwoord (en eventueel de salt, als die ook hexadecimaal is) opslaan in een BINARY-veld. Je moet dan wel de MySQL-functies HEX en UNHEX gebruiken bij het ophalen en opslaan van de gecrypte wachtwoorden.
Toevoeging op 05/08/2014 11:37:21:
Dos Moonen op 05/08/2014 11:19:49:
Wachtwoorden wil je met een relatief traag algoritme hashen, wat willem voorstelt is juist heel snel. Dus minder veilig.
Eigenlijk wil je de penalty alleen toepassen wanneer iemand een fout wachtwoord invoert. Het is natuurlijk niet gebruikersvriendelijk om bij mensen die een correct wachtwoord invoeren een trage hash te gebruiken. ;-)
Je zou wanneer het wachtwoord niet correct blijkt te zijn een sleep(1) (of zelfs meer) kunnen uitvoeren.
Ik zie zelfs meer in het bijhouden van het aantal mislukte pogingen en het tijdstip van de laatste mislukte poging in een apart veld. Als het aantal mislukte pogingen meer is dan bijvoorbeeld 5, en het tijdstip van de laatste mislukte poging meer dan 5 minuten geleden, sta je voor die gebruiker vijf minuten geen inlogpoging meer toe (en daarna kun je de teller resetten). Dat heeft volgens mij meer effect dan een tragere hash-functie.
@Willem: ik ga nog aan de gang met mysqli, maar dat is voor een nieuwe dag.
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$sql = "SELECT klantID, wachtwoord, voornaam " .
"FROM tblKlant " .
"WHERE email = '" . mysql_real_escape_string($_POST["txtEmail"]) . "'";
$resultaat = mysql_query($sql);
if(mysql_num_rows($resultaat) > 0)
{
// e-mailadres gevonden. Wachtwoord controleren.
$rs = mysql_fetch_array($resultaat);
if($rs["wachtwoord"] == crypt($_POST["txtPassword"], $rs['wachtwoord'])) // wachtwoord juist
{
set session en redirect
}
else
{
// wachtwoord onjuist
$strErrInlog = "<span class=\"warning\">De combinatie van gebruikersnaam<br />en wachtwoord is niet juist.</span><br /><br />";
}
$sql = "SELECT klantID, wachtwoord, voornaam " .
"FROM tblKlant " .
"WHERE email = '" . mysql_real_escape_string($_POST["txtEmail"]) . "'";
$resultaat = mysql_query($sql);
if(mysql_num_rows($resultaat) > 0)
{
// e-mailadres gevonden. Wachtwoord controleren.
$rs = mysql_fetch_array($resultaat);
if($rs["wachtwoord"] == crypt($_POST["txtPassword"], $rs['wachtwoord'])) // wachtwoord juist
{
set session en redirect
}
else
{
// wachtwoord onjuist
$strErrInlog = "<span class=\"warning\">De combinatie van gebruikersnaam<br />en wachtwoord is niet juist.</span><br /><br />";
}
Als $rs["wachtwoord"] de in de database opgeslagen hash van het wachtwoord is, dan kan crypt($_POST["txtPassword"], $rs['wachtwoord']) (op regel 9 hierboven) toch nooit werken voor nieuwe wachtwoorden? Je krijgt dan $b == crypt($a, $b) als een kip-en-ei-probleem: de hash $b wordt bepaald door de salt, maar de salt $b is de hash $b. Of zie ik dat verkeerd?
Dit is alleen om het wachtwoord te controleren. Een nieuw wachtwoord wordt gevormd door een random gegenereerde salt ($salt) en het ingevoerde wachtwoord: crypt($_POST["txtWachtwoord"], $salt)
Toevoeging op 05/08/2014 13:33:22:
@Dos: in dit geval staat dit wel in een query. Moet ik hier dan wel de mysql_real_escape_string rond $_POST["txtWachtwoord"] zetten?
Als het wachtwoord in de query gewoon de invoer van de bezoeker is moet je het altijd beveiligen. Maar als het al gecrypt of gehashed is dan is dat niet nodig.
Tijdens het registreren/wijzigen van je wachtwoord? Nee, het resultaat van crypt_blowfish bevat nooit iets dat mysql_real_escape_string() zal vervangen.