Mise à jour (13/08/2017): Pour comprendre pourquoi nous séparer selector
et token
, au lieu d'utiliser un token
, s'il vous plaît lire this article about splitting tokens to prevent timing attacks sur les requêtes SELECT.
Je vais extraire la stratégie décrite dans ce billet de blog about secure long-term authentication depuis qui couvre beaucoup de terrain et nous ne sommes intéressés par la partie "remember me".
Préambule - Base de données Structure
Nous voulons une table séparée de notre table des utilisateurs qui ressemble à ceci (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`)
);
Les choses importantes ici sont que selector
et token
sont des champs séparés.
Une fois connecté
Si vous n'avez pas random_bytes()
, prenez juste une copie de 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)
]
);
}
réauthentifier Sur la page charge
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
}
}
Détails
Nous utilisons 9 octets de données aléatoires (codées en base64 à 12 caractères) pour notre sélection. Cela fournit 72 bits de keyspace et donc 2 bits de résistance aux collisions (attaques d'anniversaire), ce qui est plus grand que notre capacité de stockage (integer(11) UNSIGNED
) par un facteur de 16.
Nous utilisons 33 octets (264 bits) de aléatoire pour notre authentificateur réel.Cela devrait être imprévisible dans tous les scénarios pratiques.
Nous stockons un hachage SHA256 de l'authentificateur dans la base de données. Cela réduit le risque d'usurpation d'identité suite à des fuites d'informations.
Nous recalculons le hachage SHA256 de la valeur d'authentificateur stockée dans le cookie de l'utilisateur, puis le comparons avec le hachage SHA256 stocké en utilisant hash_equals()
pour empêcher les attaques de synchronisation.
Nous avons séparé le sélecteur de l'authentificateur car les recherches de base de données ne sont pas à temps constant. Cela élimine l'impact potentiel des fuites de synchronisation sur les recherches sans causer de baisse drastique des performances.
Vous pouvez jeter un oeil à https://github.com/delight-im/PHP-Auth et sa source pour voir comment implémenter une fonction «se souvenir de moi» * secure *. Fondamentalement, il suffit de stocker une chaîne * très longue * (c'est-à-dire beaucoup d'entropie) de données aléatoires dans un cookie. Lorsque l'utilisateur visite votre page, vérifiez que "token" par rapport à votre base de données où vous suivez ces jetons. Si le jeton est valide, authentifiez l'utilisateur. – caw