Обновление (13 августа 2017 г.): чтобы понять, почему мы разделяем selector
и token
, а не просто используем token
, прочтите эта статья о разделении токенов для предотвращения временных атак на запросы SELECT.
Я собираюсь извлечь стратегию, изложенную в этом сообщении блога о безопасной долгосрочной аутентификации, поскольку это охватывает множество вопросов, и нас интересует только " запомни меня ".
Преамбула - структура базы данных
Нам нужна отдельная таблица из таблицы наших пользователей, которая выглядит так (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`)
);
Здесь важно то, что selector
и token
- это отдельные поля.
После входа в систему
Если у вас нет random_bytes()
, просто возьмите копию 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)
]
);
}
Повторная аутентификация при загрузке страницы
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
}
}
Подробности
Мы используем 9 байтов случайных данных (в кодировке base64 до 12 символов) для нашего селектора. Это обеспечивает 72 бита пространства ключей и, следовательно, 2 36 битов устойчивости к столкновениям (атаки дня рождения), что в 16 раз больше, чем емкость нашего хранилища (integer(11) UNSIGNED
).
Мы используем 33 байта (264 бита) случайности для нашего фактического аутентификатора. Это должно быть непредсказуемо во всех практических сценариях.
Мы храним SHA256-хэш аутентификатора в базе данных. Это снижает риск олицетворения пользователя после утечки информации.
Мы повторно вычисляем хэш SHA256 значения аутентификатора, хранящегося в файле cookie пользователя, затем сравниваем его с сохраненным хешем SHA256, используя _11 _ Для предотвращения тайминговых атак.
Мы отделили селектор от аутентификатора, потому что поиск в БД не является постоянным по времени. Это устраняет потенциальное влияние утечек времени на поиск, не вызывая резкого снижения производительности.
person
Scott Arciszewski
schedule
09.05.2015