PHP inloggnings klass
Då jag ser många fortfarande använda mysql_* osäkra variabler, sparar lösenord i klartext etc tänkte jag visa klassen jag började bygga på idag. Försöker även lära mig själv PHP då jag mest programmerat i C# så tänkte att man kan lika gärna göra någonting vettigt av det hela samtidigt.
Just nu består klassen av
-> konstruktorn
--- Körs när du skapar ett objekt av klassen db. Just nu är det endast anslutningen till SQL servern som upprättas när objektet skapas.
-> register
--- Körs när du vill sätta in ny information om en användare. Just nu är det endast det mest simpla som sätts in: användarnamn, det hashade lösenordet och e-post adressen.
-> login
--- Körs när du vill autentisera en användare. Kollar först om användaren finns, finns den inte returnerar den false och skriver ut att inget användarnamn/lösenord hittats. Egentligen är det bara att användarnamnet inte hittades men ingen god idé att ge denna information till en potentiell hackare. Sedan hashar den lösenordet som användaren skrivit in och jämför med den som är sparad i databasen redan. Är båda samma returnerar den true och skriver ut att inloggningen lyckades.
cryptology.php är en klass som är public domain och är inte min egen. Den innehåller funktionerna för hashningen och saltandet av lösenorden. Mer information hittar ni på http://crackstation.net/hashing-security.htm
PDO är klassen som används för databashantering istället för mysql_* funktionerna. PDO är mycket säkrare samt fungerar till andra databaser än mysql.
OBS! Koden är väldigt grundläggande och saknar sessions, koll om användarnamnet är upptaget redan etc. Kommer försöka uppdatera den i mån jag har tid över på jobbet.
För att skapa SQL databasen skapar du en tabell som heter users och har fyra kolumner: userid, username, hash och email
Koden:
db.php
<?php
include("cryptography.php");
class db
{
/* Databas variabler */
private $dbName, $dbUser, $dbPass, $dbHost, $connection;
/* Variabler som hanterar användarkonton */
private $hash, $username, $password, $email, $validate;
/* Meddelande hantering */
public $errors, $messages;
/* När objektet skapas skapas också anslutningen till databasen */
public function __construct($dbHost, $dbName, $dbUser, $dbPass)
{
$this->dbHost = $dbHost;
$this->dbUser = $dbUser;
$this->dbPass = $dbPass;
$this->dbName = $dbName;
try
{
$this->connection = new PDO("mysql:host=$this->dbHost;dbname=$this->dbName", $this->dbUser, $this->dbPass);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch (PDOException $e)
{
$this->errors = "Kunde inte ansluta. Följande sträng är den tekniska informationen: " . $e->getMessage();
}
}
/* Registrering av t.ex. en användare */
public function register($username, $password, $email)
{
if (!$this->connection)
{
$this->errors = "Ingen anslutning till databasen kunde hittas. Försök igen senare.";
return false;
}
$this->hash = create_hash($password); // Använd cryptography.php för att skapa sha256 hash + salt sträng
$this->username = $username;
$this->email = $email;
/* Namngivna platshållare */
$data = array("username" => $this->username,
"hash" => $this->hash,
"email" => $this->email);
$exec = $this->connection->prepare("
INSERT INTO `users` (username, hash, email)
VALUE (:username, :hash, :email)
");
try
{
$exec->execute($data);
}
catch(PDOException $e)
{
$this->errors = "Någonting gick fel. Följande sträng är den tekniska informationen: " . $e->getMessage();
return false;
}
$this->messages = "Registreringen lyckades!";
return $exec;
}
public function login($username, $password)
{
if (!$this->connection)
{
$this->errors = "Ingen anslutning till databasen kunde hittas. Försök igen senare.";
return false;
}
$this->username = $username;
$this->password = $password;
$data = array("username" => $this->username);
$exec = $this->connection->prepare("
SELECT username, hash
FROM `users`
WHERE username = :username
");
try
{
$exec->execute($data);
}
catch(PDOException $e)
{
$this->errors = "Någonting gick fel. Följande sträng är en tekniska informationen: " . $e->getMessage();
}
$checkRows = $exec->rowCount();
if (!$checkRows == 1)
{
$this->errors = "Fel användarnamn/lösenord";
return false;
}
while ($row = $exec->fetch(PDO::FETCH_ASSOC))
{
$this->hash = $row['hash'];
}
$this->validate = validate_password($this->password, $this->hash);
if ($this->validate)
{
$this->messages = "Inloggningen lyckades.";
}
else
{
$this->errors = "Inloggningen misslyckades. Fel användarnamn/lösenord";
}
return $this->validate;
}
}
cryptology.php
<?php
/*
* Password hashing with PBKDF2.
* Author: havoc AT defuse.ca
* www: https://defuse.ca/php-pbkdf2.htm
*/
// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTES", 24);
define("PBKDF2_HASH_BYTES", 24);
define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);
function create_hash($password)
{
// format: algorithm:iterations:salt:hash
$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" .
base64_encode(pbkdf2(
PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2_ITERATIONS,
PBKDF2_HASH_BYTES,
true
));
}
function validate_password($password, $good_hash)
{
$params = explode(":", $good_hash);
if(count($params) < HASH_SECTIONS)
return false;
$pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
return slow_equals(
$pbkdf2,
pbkdf2(
$params[HASH_ALGORITHM_INDEX],
$password,
$params[HASH_SALT_INDEX],
(int)$params[HASH_ITERATION_INDEX],
strlen($pbkdf2),
true
)
);
}
// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
$diff = strlen($a) ^ strlen($b);
for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
{
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}
/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}
?>
Hur använder man koden?
Gjorde ett snabbt exempel:
test.php
<?php
echo "<!DOCTYPE html><html><head></head><body>";
include("db.php");
$connect = new db("localhost", "c1db", "c1user", "password");
$register = $connect->register("admin", "123456", "email@domain.com");
if ($register)
{
echo $connect->messages;
}
else
{
echo $connect->errors;
}
echo "<br>";
$login = $connect->login("admin", "123456");
if ($login)
{
echo $connect->messages;
}
else
{
echo $connect->errors;
}
echo "</body></html>";
?>
Skriv gärna om det är något ni undrar eller anmärkte på, koden är antagligen inte helt perfekt.
AW3423DW QD-OLED - Ryzen 5800x - MSI Gaming Trio X 3090 - 64GB 3600@cl16 - Samsung 980 Pro 2TB/WD Black SN850 2TB