?@[\]^_`{|}~'; private const string DIGIT_CHARACTERS = '1234567890'; /** * Generates cryptographic secure pseudo-random bytes */ public function generateRandomBytes(int $length): string { return random_bytes($length); } /** * Generates cryptographic secure pseudo-random integers */ public function generateRandomInteger(int $min, int $max): int { return random_int($min, $max); } /** * Generates cryptographic secure pseudo-random hex string */ public function generateRandomHexString(int $length): string { return substr(bin2hex($this->generateRandomBytes((int)(($length + 1) / 2))), 0, $length); } /** * Generates cryptographic secure pseudo-random base64 string */ public function generateRandomBase64String(int $length): string { return substr(StringUtility::base64urlEncode($this->generateRandomBytes((int)ceil(($length / 4) * 3))), 0, $length); } /** * Generates cryptographic secure pseudo-random password based on given password rules * * @internal Only to be used within TYPO3. Might change in the future. */ public function generateRandomPassword(array $passwordRules): string { $passwordLength = (int)($passwordRules['length'] ?? self::DEFAULT_PASSWORD_LENGTH); if ($passwordLength < 8) { throw new InvalidPasswordRulesException( 'Password rules are invalid. Length must be at least 8.', 1667557900 ); } $password = ''; if ($passwordRules['random'] ?? false) { $password = match ((string)$passwordRules['random']) { 'hex' => $this->generateRandomHexString($passwordLength), 'base64' => $this->generateRandomBase64String($passwordLength), default => throw new InvalidPasswordRulesException('Invalid value for special password rule \'random\'. Valid options are: \'hex\' and \'base64\'', 1667557901), }; } else { $characters = []; $characterSets = []; if (filter_var($passwordRules['lowerCaseCharacters'] ?? true, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $characters = array_merge($characters, str_split(self::LOWERCASE_CHARACTERS)); $characterSets[] = self::LOWERCASE_CHARACTERS; } if (filter_var($passwordRules['upperCaseCharacters'] ?? true, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $characters = array_merge($characters, str_split(self::UPPERCASE_CHARACTERS)); $characterSets[] = self::UPPERCASE_CHARACTERS; } if (filter_var($passwordRules['digitCharacters'] ?? true, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $characters = array_merge($characters, str_split(self::DIGIT_CHARACTERS)); $characterSets[] = self::DIGIT_CHARACTERS; } if (filter_var($passwordRules['specialCharacters'] ?? false, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $characters = array_merge($characters, str_split(self::SPECIAL_CHARACTERS)); $characterSets[] = self::SPECIAL_CHARACTERS; } if ($characterSets === []) { throw new InvalidPasswordRulesException( 'Password rules are invalid. At least one character set must be allowed.', 1667557902 ); } // enforces that at least one character matches the requirements foreach ($characterSets as $characterSet) { $password .= $characterSet[random_int(0, strlen($characterSet) - 1)]; } $charactersCount = count($characters); for ($i = 0; $i < $passwordLength - count($characterSets); $i++) { $password .= $characters[random_int(0, $charactersCount - 1)]; } $password = (new Randomizer())->shuffleBytes($password); } return $password; } }