Aggiornamento (2017/08/13): Per capire il motivo per cui ci stiamo separando selector
e token
, invece di utilizzare un token
, si prega di leggere this article about splitting tokens to prevent timing attacks su query SELECT.
ho intenzione di estrarre la strategia delineata in questo post del blog about secure long-term authentication dal momento che copre un sacco di terra e noi siamo interessati solo alla parte "remember me".
Preambolo - Struttura del database
Vogliamo un tavolo separato dalla tavola dei nostri utenti che assomiglia a questo (MySQL):
CREATE TABLE `auth_tokens` (
`id` integer(11) not null UNSIGNED AUTO_INCREMENT,
`selector` char(12),
`token` char(64),
`userid` integer(11) not null UNSIGNED,
`expires` datetime,
PRIMARY KEY (`id`)
);
Le cose importanti sono che selector
e token
sono campi separati.
dopo l'accesso
Se non si dispone di random_bytes()
, basta prendere una copia di random_compat.
if ($login->success && $login->rememberMe) { // However you implement it
$selector = base64_encode(random_bytes(9));
$authenticator = random_bytes(33);
setcookie(
'remember',
$selector.':'.base64_encode($authenticator),
time() + 864000,
'/',
'yourdomain.com',
true, // TLS-only
true // http-only
);
$database->exec(
"INSERT INTO auth_tokens (selector, token, userid, expires) VALUES (?, ?, ?, ?)",
[
$selector,
hash('sha256', $authenticator),
$login->userId,
date('Y-m-d\TH:i:s', time() + 864000)
]
);
}
Ri-Autenticazione On caricamento della pagina
if (empty($_SESSION['userid']) && !empty($_COOKIE['remember'])) {
list($selector, $authenticator) = explode(':', $_COOKIE['remember']);
$row = $database->selectRow(
"SELECT * FROM auth_tokens WHERE selector = ?",
[
$selector
]
);
if (hash_equals($row['token'], hash('sha256', base64_decode($authenticator)))) {
$_SESSION['userid'] = $row['userid'];
// Then regenerate login token as above
}
}
dettagli
Usiamo 9 byte di dati casuali (codifica Base64 a 12 caratteri) per il nostro selettore. Questo fornisce 72 bit di spazio delle chiavi e quindi 2 bit di resistenza collisione (attacco del compleanno), che è più grande della nostra capacità di stoccaggio (integer(11) UNSIGNED
) per un fattore di 16.
Usiamo 33 byte (264 bit) di casualità per il nostro vero autenticatore.Questo dovrebbe essere imprevedibile in tutti gli scenari pratici.
Memorizziamo un hash SHA256 dell'autenticatore nel database. Ciò riduce il rischio di impersonificazione degli utenti in seguito a perdite di informazioni.
Ricalcolo dell'hash SHA256 del valore di autenticazione memorizzato nel cookie dell'utente, quindi confrontarlo con l'hash SHA256 memorizzato utilizzando hash_equals()
per prevenire attacchi di temporizzazione.
Abbiamo separato il selettore dall'autenticatore perché le ricerche DB non sono costanti. Ciò elimina il potenziale impatto delle perdite temporali sulle ricerche senza causare un drastico calo delle prestazioni.
Si può dare un'occhiata a https://github.com/delight-im/PHP-Auth e la sua fonte per vedere come implementare una funzione * secure * "ricordami". Fondamentalmente, basta memorizzare una stringa * molto lunga * (cioè molta entropia) di dati casuali in un cookie. Quando l'utente visita la tua pagina, controlla quel "token" sul tuo database dove tieni traccia di questi token. Se il token è valido, autenticare l'utente. – caw