Utilizzo di password unix in Php
Obiettivo: importare e gestire in php delle password unix in un database mysql.
Soluzione: il formato delle password in Linux è il formato md5-crypt.
Descrizione del formato md5-crypt
Le password in Debian Sarge, sono salvate nel formato md5-crypt in una installazione 'standard', e sono contenute nel file /etc/shadow leggibile solo da root:
cat /etc/shadow | grep testuser testuser:$1$vTa1.iGc$9ntQJ.PyvXviofUXuLdMT1:13003:0:99999:7:::
La password è la stringa:
$1$vTa1.iGc$9ntQJ.PyvXviofUXuLdMT1
Essa è composta da una parte chiamata "salt" che è nel formato
$1$xxxxxxxx$
Dove xxxxxxxx sono 8 caratteri alfanumerici oltre a . e /
La parte seguente è chiamata "hash" e contiene la password cifrata vera e propria.
Il formato è descritto con precisione su The GNU C Library - Function crypt()
Funzione ENCRYPT di Mysql
Mysql supporta la cifratura crypt basandosi sulle chiamate alla routine crypt() di sistema, quindi assicura la piena compatibilità tra hash Linux e Mysql.
Tale funzione è built-in in Mysql e può essere usata direttamente nelle query e si chiama ENCRYPT.
Il formato è:
ENCRYPT (stringa_password;[stringa_salt])
Se non si specifica il salt, esso viene generato casualmente e corrisponderà alle prime due lettere dell'hash.
ATTENZIONE: se non si usa il salt md5-crypt, la password verrà considerata fino ad un massimo di 8 caratteri, e questa è una limitazione di crypt(); cioè le password abcdefgh e abcdefghi saranno uguali.
Per provare la funzione ENCRYPT(), dalla console mysql:
mysql> select 'parola', ENCRYPT('parola');
+--------+-------------------+
| parola | ENCRYPT('parola') |
+--------+-------------------+
| parola | k5z7/FkdJb4VI |
+--------+-------------------+
1 row in set (0.01 sec)
In questo caso il salt è "k5".
Memorizzaione di una password crypt()
Ipotizziamo di avere una tabella con i campi:
mysql> describe accounts; +-----------------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+---------------+------+-----+---------+----------------+ | username | varchar(30) | | | | | | password | varchar(80) | | | | | +-----------------+---------------+------+-----+---------+----------------+ 2 rows in set (0.01 sec)
dove in password vogliamo memorizzare l'hash della password ottenuto con ENCRYPT('stringa_password')
La query per memorizzare una password crypt() standard sarà quindi:
UPDATE accounts
SET password=encrypt('parola') WHERE username='nomeutente'
Decifrazione di una password crypt()
Parlare di decifrazione non è proprio corretto. Per la natura "one-way" della cifratura crypt(), dall'hash, non è possibile ricavare la password, dato che ad un hash ed un salt corrispondono infinite password (cioè la funzione di generazione non è biunivoca).
Però ad una password ed un salt corrisponde uno ed un solo hash.
Quindi per verificare che la password sia corretta non si deve decifrare l'hash, ma piuttosto ricifrare la password con il salt conosciuto e vedere se l'hash generato è lo stesso. Se si cifra la password con lo stesso salt, si ottiene sempre lo stesso hash.
Conoscere l'hash, e di conseguenza il salt, non aiuta molto a scoprire la password, in quanto non esiste nessun modo per ricavare la password dall'hash e dal salt, ma si può solo ricavare l'hash dalla password e dal salt.
Se un utente fornisce una password ad esempio 'perla', e si ha memorizzato un hash, la query per verificare se è corretta sarà:
SELECT username
FROM accounts
WHERE username='nomeutente'
AND password=ENCRYPT('perla', SUBSTRING(password from 1 for 2));
Se la password è corretta, l'hash sarà uguale, e sarà restituita la riga corrispondente contente lo username.
In pratica, ricifriamo la passowrd fornita con il salt conosciuto (di default le prime due lettere dell'hash completo) e vediamo se otteniamo lo stesso hash completo.
Limitazione di 8 caratteri
Se usiamo questa tecnica, cioè crypt() standard, come detto prima, abbiamo un limite di considerazione di 8 caratteri per la password, infatti:
mysql> SELECT 'compiuto12', ENCRYPT('compiuto12','hX') AS hash1, 'compiuto99', ENCRYPT('compiuto99','hX') AS hash2;
+------------+---------------+------------+---------------+
| compiuto12 | hash1 | compiuto99 | hash2 |
+------------+---------------+------------+---------------+
| compiuto12 | hXV722CZvTG/c | compiuto99 | hXV722CZvTG/c |
+------------+---------------+------------+---------------+
1 row in set (0.01 sec)
I due hash sono uguali !!!! In questo caso un utente può pensare di aver inserito una password diversa più lunga di 8 caratteri, invece qualsiasi password con i primi 8 caratteri uguali sarebbe considerata valida.
Occorre utilizzare la tecnica md5. Come detto precedentemente in The GNU C Library - Function crypt() se forniamo come salt una stringa nel formato $1$xxxxxxxx$, automaticamente verrà usato md5-crypt, che genera un hash che permette di considerare un numero 'illimitato' di caratteri per la password.
Infatti:
mysql> SELECT 'compiuto12', ENCRYPT('compiuto12','$1$abcdefgh$') AS hash1, 'compiuto99', ENCRYPT('compiuto99','$1$abcdefgh$') AS hash2;
+------------+------------------------------------+------------+------------------------------------+
| compiuto12 | hash1 | compiuto99 | hash2 |
+------------+------------------------------------+------------+------------------------------------+
| compiuto12 | $1$abcdefgh$41Q6fZfcouppt81wG9pYW1 | compiuto99 | $1$abcdefgh$KStEbIeSNwyibwQSYhsd50 |
+------------+------------------------------------+------------+------------------------------------+
1 row in set (0.01 sec)
Ora gli hash sono diversi (anche se voultamente si è usato lo stesso salt), e sono nel formato 'shadow'.
Validazione di una password md5-crypt
La query dovrà quindi considerare come salt i primi 12 caratteri:
SELECT username
FROM accounts
WHERE username='nomeutente' AND password=encrypt('compiuto99', substring(password from 1 for 12));
Utilizzo di md5-crypt in Php
Per generare una password md5-crypt l'unica operazione da eseguire in più è la corretta generazione di un salt casuale, in modo da aumentare l'entropia dell'hash completo.
Allo scopo è utile la seguente funzione php che genera il salt correttamente:
function makeSalt () {
$saltlen=9;
$saltprefix='$1$';
$saltsuffix='$';
$salt='';
$hash = '';
for($i=0;$i<8;$i++) {
$j = mt_rand(0,53);
if($j<26)$hash .= chr(rand(65,90));
else if($j<52)$hash .= chr(rand(97,122));
else if($j<53)$hash .= '.';
else $hash .= '/';
}
return $saltprefix.$hash.$saltsuffix;
}
Quindi la query per meorizzare la password in php sarà costruita come:
$salt=makeSalt();
$changePassword_query=sprintf("UPDATE accounts SET password=encrypt('%s','%s') WHERE username='%s'",
$nuovapassword, $salt, $nomeutente);
Importazione di password Unix in Mysql
Quanto detto sopra permette di importare le password unix in formato shadow in un database mysql, ad esempio per usare un'autenticazione basata su pam_mysql, senza dover resettare le password egli utenti.