1 || abs($delta) === 1) { if (abs($delta) > 0 && abs($delta) <= self::MAX_DELTA) { $token = mb_substr($password, $begin, $end - $begin + 1); if (preg_match('/^[a-z]+$/u', $token)) { $sequenceName = 'lower'; $sequenceSpace = 26; } elseif (preg_match('/^[A-Z]+$/u', $token)) { $sequenceName = 'upper'; $sequenceSpace = 26; } elseif (preg_match('/^\d+$/u', $token)) { $sequenceName = 'digits'; $sequenceSpace = 10; } else { $sequenceName = 'unicode'; $sequenceSpace = 26; } $matches[] = new static($password, $begin, $end, $token, [ 'sequenceName' => $sequenceName, 'sequenceSpace' => $sequenceSpace, 'ascending' => $delta > 0, ]); } } } #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] public function getFeedback(bool $isSoleMatch): array { return [ 'warning' => "Sequences like abc or 6543 are easy to guess", 'suggestions' => [ 'Avoid sequences' ] ]; } /** * @param string $password * @param int $begin * @param int $end * @param string $token * @param array $params An array with keys: [sequenceName, sequenceSpace, ascending]. */ public function __construct(string $password, int $begin, int $end, string $token, array $params = []) { parent::__construct($password, $begin, $end, $token); if (!empty($params)) { $this->sequenceName = $params['sequenceName'] ?? ''; $this->sequenceSpace = $params['sequenceSpace'] ?? 0; $this->ascending = $params['ascending'] ?? false; } } protected function getRawGuesses(): float { $firstCharacter = mb_substr($this->token, 0, 1); $guesses = 0; if (in_array($firstCharacter, array('a', 'A', 'z', 'Z', '0', '1', '9'), true)) { $guesses += 4; // lower guesses for obvious starting points } elseif (ctype_digit($firstCharacter)) { $guesses += 10; // digits } else { // could give a higher base for uppercase, // assigning 26 to both upper and lower sequences is more conservative $guesses += 26; } if (!$this->ascending) { // need to try a descending sequence in addition to every ascending sequence -> // 2x guesses $guesses *= 2; } return $guesses * mb_strlen($this->token); } }