Хеширование паролей

Ради безопасности наших пользователей мы никогда не храним их пароли в читаемом виде, а храним только их отпечаток (так называемый хеш). Из хеша нельзя обратным путём получить исходный пароль. Важно использовать для создания хеша безопасный алгоритм. С этим нам помогает класс Nette\Security\Passwords.

Установка и требования

Фреймворк автоматически добавляет в DI-контейнер сервис типа Nette\Security\Passwords под именем security.passwords. Получить его можно, попросив передать его через внедрение зависимостей.

use Nette\Security\Passwords;

class Foo
{
	public function __construct(
		private Passwords $passwords,
	) {
	}
}

__construct (string $algo=PASSWORD_DEFAULT, array $options=[])

Мы выбираем, какой безопасный алгоритм использовать для порождения хеша, и настраиваем его параметры.

По умолчанию используется PASSWORD_DEFAULT, то есть выбор алгоритма оставлен на усмотрение PHP. В новых версиях PHP алгоритм может измениться, если появятся более новые и стойкие алгоритмы хеширования. Поэтому вам стоит учитывать, что длина получающегося хеша может измениться, и хранить его так, чтобы вместилось достаточно символов; рекомендуемая ширина – 255.

Пример задания скорости хеширования для алгоритма bcrypt изменением параметра cost: (в 2020 году по умолчанию 10, хеширование пароля занимает примерно 80 мс; при cost 11 – около 160 мс; при cost 12 – около 320 мс; чем медленнее, тем лучше защита, при скорости 10–12 защита уже считается достаточной)

// будем хешировать пароли 2^12 (2^cost) итерациями алгоритма bcrypt
$passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]);

С использованием внедрения зависимостей:

services:
	security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12])

static bcrypt (?int $cost=null): Passwords

Создаёт экземпляр, настроенный на алгоритм bcrypt. Параметр $cost задаёт описанную выше скорость хеширования; если вы его опустите, будет использовано значение PHP по умолчанию.

$passwords = Passwords::bcrypt(12);

static argon2id (?int $memoryCost=null, ?int $timeCost=null, ?int $threads=null): Passwords

Создаёт экземпляр, настроенный на алгоритм Argon2id. Опущенные параметры оставляются на значениях PHP по умолчанию. Если PHP собран без поддержки Argon2, метод выбрасывает Nette\NotSupportedException.

$passwords = Passwords::argon2id(memoryCost: 1 << 17, timeCost: 4);

hash (string $password): string

Порождает хеш пароля.

$res = $passwords->hash($password); // Хеширует пароль

Результат $res – строка, которая кроме самого хеша содержит идентификатор использованного алгоритма, его настройки и криптографическую соль (случайные данные, обеспечивающие, что для одного и того же пароля порождается разный хеш). Поэтому он обратно совместим: например, если вы измените параметры, хеши, сохранённые с прежними настройками, всё равно можно проверить. Весь этот результат сохраняется в базу данных, так что хранить соль или настройки отдельно не нужно.

verify (string $password, string $hash)bool

Выясняет, соответствует ли данный пароль данному хешу. $hash возьмите из базы данных по введённому имени пользователя или адресу электронной почты.

if ($passwords->verify($password, $hash)) {
	// верный пароль
}

needsRehash (string $hash)bool

Выясняет, соответствует ли хеш параметрам, заданным в конструкторе.

Его удобно использовать, когда вы, например, меняете стоимость хеширования. Проверка происходит по сохранённым настройкам, и если needsRehash() вернёт true, нужно создать хеш заново, на этот раз с новыми параметрами, и снова сохранить его в базу данных. Так сохранённые хеши автоматически “обновляются” при входе пользователей.

if ($passwords->needsRehash($hash)) {
	$hash = $passwords->hash($password);
	// сохраняем $hash в базу данных
}
версия: 4.x