PHPackages                             stadly/password-police - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. stadly/password-police

ActiveLibrary[Security](/categories/security)

stadly/password-police
======================

Password policy enforcement made easy.

v0.23.0(6y ago)42.7k1MITPHPPHP &gt;=7.1CI failing

Since Nov 15Pushed 6y ago1 watchersCompare

[ Source](https://github.com/Stadly/PasswordPolice)[ Packagist](https://packagist.org/packages/stadly/password-police)[ Docs](https://github.com/Stadly/PasswordPolice)[ RSS](/packages/stadly-password-police/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (15)Versions (25)Used By (0)

PasswordPolice
==============

[](#passwordpolice)

[![Latest Version on Packagist](https://camo.githubusercontent.com/c8bc554b9845799398389cbca285b8f6bfb80e33d17877efdb213036067f685f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f737461646c792f70617373776f72642d706f6c6963652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/stadly/password-police)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Build Status](https://camo.githubusercontent.com/6c1bf300cfb0187f393f0445afd20bc98042e2621c73bf062b4fd9591c7ebb27/68747470733a2f2f696d672e736869656c64732e696f2f7472617669732f537461646c792f50617373776f7264506f6c6963652f6d61737465722e7376673f7374796c653d666c61742d737175617265)](https://travis-ci.org/Stadly/PasswordPolice)[![Coverage Status](https://camo.githubusercontent.com/d01ca7ed6d0c03f16a746419545885b127a91298fd3f1b80e1108b7261eba7bb/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f636f7665726167652f672f537461646c792f50617373776f7264506f6c6963652e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/Stadly/PasswordPolice/code-structure)[![Quality Score](https://camo.githubusercontent.com/1962d87ee8d65cb3356e47a163ae7a6a57985ee1fccc38194521cad21ba0d089/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f537461646c792f50617373776f7264506f6c6963652e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/Stadly/PasswordPolice)[![Total Downloads](https://camo.githubusercontent.com/f4fedb4b6bda71fa0b4ce16f2a2e868e67bb05f597cefd3a9c7a38cd009216a0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f737461646c792f70617373776f72642d706f6c6963652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/stadly/password-police)

Password policy enforcement made easy.

Install
-------

[](#install)

Via Composer

```
composer require stadly/password-police
```

Usage
-----

[](#usage)

```
use Stadly\PasswordPolice\Policy;
use Stadly\PasswordPolice\Rule\DictionaryRule;
use Stadly\PasswordPolice\Rule\HaveIBeenPwnedRule;
use Stadly\PasswordPolice\Rule\LengthRule;
use Stadly\PasswordPolice\WordList\Pspell;

$policy = new Policy();

// Add rules to the password policy. See the Rules section below.
$policy->addRules(
    new LengthRule(8),                              // Password must be at least 8 characters long.
    new HaveIBeenPwnedRule(),                       // Password must not be exposed in data breaches.
    new DictionaryRule(Pspell::fromLocale('en'))    // Password must not be a word from the dictionary.
);

$password = 'password';

$validationErrors = $policy->validate($password);
if ($validationErrors !== []) {
    // The password is not in compliance with the policy.
    foreach ($validationErrors as $validationError) {
        // Show validation message to the user.
        echo $validationError->getMessage();
    }
}
```

### Rules

[](#rules)

Password policy rules specify the password requirements, and are used to determine whether a password is in compliance with the policy or not.

#### Length

[](#length)

The length rule sets lower and upper limits to the password length.

```
use Stadly\PasswordPolice\Rule\LengthRule;

$rule = new LengthRule(8);              // Password must be at least 8 characters long.
$rule = new LengthRule(8, 32);          // Password must be between 8 and 32 characters long.
```

#### Lower case letters

[](#lower-case-letters)

The lower case rule sets lower and upper limits to the number of lower case letters.

```
use Stadly\PasswordPolice\Rule\LowerCaseRule;

$rule = new LowerCaseRule();            // Password must contain lower case letters.
$rule = new LowerCaseRule(3);           // Password must contain at least 3 lower case letters.
$rule = new LowerCaseRule(3, 5);        // Password must contain between 3 and 5 lower case letters.
```

#### Upper case letters

[](#upper-case-letters)

The upper case rule sets lower and upper limits to the number of upper case letters.

```
use Stadly\PasswordPolice\Rule\UpperCaseRule;

$rule = new UpperCaseRule();            // Password must contain upper case letters.
$rule = new UpperCaseRule(3);           // Password must contain at least 3 upper case letters.
$rule = new UpperCaseRule(3, 5);        // Password must contain between 3 and 5 upper case letters.
```

#### Digits

[](#digits)

The digit rule sets lower and upper limits to the number of digits.

```
use Stadly\PasswordPolice\Rule\DigitRule;

$rule = new DigitRule();                // Password must contain digits.
$rule = new DigitRule(3);               // Password must contain at least 3 digits.
$rule = new DigitRule(3, 5);            // Password must contain between 3 and 5 digits.
```

#### Symbols

[](#symbols)

The symbol rule sets lower and upper limits to the number of symbols. The characters considered symbols are specified when creating the rule. Note that this rule counts the number of symbols, and not the number of distinct symbols. That `Hello!!!` contains one symbol three times, while `Hello!?&` contains three different symbols makes no difference—they both contain three symbols.

```
use Stadly\PasswordPolice\Rule\SymbolRule;

$rule = new SymbolRule('!#%&?');        // Password must contain symbols (!, #, %, &, or ?).
$rule = new SymbolRule('!#%&?', 3);     // Password must contain at least 3 symbols.
$rule = new SymbolRule('!#%&?', 3, 5);  // Password must contain between 3 and 5 symbols.
```

#### Have I Been Pwned

[](#have-i-been-pwned)

The Have I Been Pwned rule sets lower and upper limits to the number of times the password has been exposed in data breaches. This rule uses the [Have I Been Pwned](https://haveibeenpwned.com/Passwords) service. Passwords are never sent to the service. Instead [k-Anonymity](https://en.wikipedia.org/wiki/K-anonymity) is used to make the solution secure.

```
use Stadly\PasswordPolice\Rule\HaveIBeenPwnedRule;

$rule = new HaveIBeenPwnedRule();       // Password must not be exposed in data breaches.
$rule = new HaveIBeenPwnedRule(5);      // Password must not be exposed in data breaches more than 5 times.
$rule = new HaveIBeenPwnedRule(5, 3);   // Password must be exposed in data breachs between 3 and 5 times.
```

#### Dictionary

[](#dictionary)

The dictionary rule enforces that the password is not contained in a word list.

[Formatters](#formatters) can optionally be applied to the password before checking the word list. This makes it possible to decode for example [leetspeak](#leetspeak-decoder), so that the password `p4ssw0rd` would match a word list containing the word `password`, or to [split the password](#substring-generator) into multiple words, so that the password `SomeCombinedWords` would match a word list containing the word `Combined`.

```
use Stadly\PasswordPolice\Formatter\LeetspeakDecoder;
use Stadly\PasswordPolice\Rule\DictionaryRule;
use Stadly\PasswordPolice\WordList\Pspell;

$wordList = Pspell::fromLocale('en');
$rule = new DictionaryRule($wordList, [new LeetspeakDecoder()]);
```

##### Word lists

[](#word-lists)

The dictionary rule requires a word list. Currently, [Pspell](#pspell) is the only available word list. Support for other word lists can easily be implemented.

###### Pspell

[](#pspell)

The pspell word list uses [Pspell](https://www.php.net/manual/en/book.pspell.php), which can be built into php.

[Formatters](#formatters) can optionally be applied to the password before checking the word list. This is useful because the php version of pspell is case-sensitive. By using for example the [lower case converter](#lower-case-converter), the password `PaSsWoRd` would match a word list containing the word `password`.

```
use Stadly\PasswordPolice\Formatter\Capitalizer;
use Stadly\PasswordPolice\Formatter\LowerCaseConverter;
use Stadly\PasswordPolice\WordList\Pspell;

$wordList = Pspell::fromLocale('en', [new LowerCaseConverter(), new Capitalizer()]);
```

#### Guessable data

[](#guessable-data)

The guessable data rule enforces that the password doesn’t contain data that can easily be guessed. Easily guessable data is specified when creating the rule. It is possible to specify additional easily guessable data for each password. This way the guessable data rule can both prevent general easily guessable data like the service name from being used in any password, and also prevent users from using personal easily guessable data like their own name or birthday in their password.

To specify easily guessable data for a password, the password must be a `Password` object instead of a `string`.

[Formatters](#formatters) can optionally be applied to the password before validation. This makes it possible to decode for example [leetspeak](#leetspeak-decoder), so that the password `S74d1y` would match the easily guessable data `Stadly`. Note that the guessable data rule checks if the password contains easily guessable data, not that it matches it, so there is no need to use a formatter to split the password into [multiple words](#substring-generator), as may be useful with the [dictionary rule](#dictionary).

```
use Stadly\PasswordPolice\Formatter\LeetspeakDecoder;
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\Rule\GuessableDataRule;

// Easily guessable data for any password.
$globalGuessableData = [
    'company',
];
$rule = new GuessableDataRule($globalGuessableData, [new LeetspeakDecoder()]);

// Additional easily guessable data for this password.
$passwordGuessableData = [
    'first name',
    'spouse',
    new DateTimeImmutable('birthday'),
];
// Use a Password object instead of a string in order to specify the easily guessable data.
$password = new Password('password', $passwordGuessableData));
```

##### Date formatters

[](#date-formatters)

To check if a password contains an easily guessable date, the [guessable data rule](#guessable-data) must know the different formats that a date can have. This is the job of the date formatters. Custom date formatters can easily be implemented. A default date formatter is used when no date formatter is sepcified for the guessable data rule.

```
use Stadly\PasswordPolice\Rule\GuessableDataRule;

$dateFormatter = new MyCustomDateFormatter();

// Easily guessable data.
$guessableData = [
    new DateTimeImmutable('2018-08-04'),
];
$rule = new GuessableDataRule($guessableData, [], $dateFormatter);
```

#### No reuse

[](#no-reuse)

The no reuse rule prevents previously used passwords from being used again.

For the rule to know the previously used passwords, former passwords must be specified. To specify former passwords, the password must be a `Password` object instead of a string.

```
use Stadly\PasswordPolice\FormerPassword;
use Stadly\PasswordPolice\HashFunction\PasswordHasher;
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\Rule\NoReuseRule;

$hashFunction = new PasswordHasher();

$rule = new NoReuseRule($hashFunction);     // Passwords can never be reused.
$rule = new NoReuseRule($hashFunction, 5);  // The 5 most recently used password cannot be reused.

// Former passwords. The most recent one should be the current password.
$formerPasswords = [
    new FormerPassword(new DateTimeImmutable('2017-06-24'), 'hash of password'),
    new FormerPassword(new DateTimeImmutable('2018-08-04'), 'hash of password'),
    new FormerPassword(new DateTimeImmutable('2018-08-18'), 'hash of password'),
];
// Use a Password object instead of a string in order to specify former passwords.
$password = new Password('password', [], $formerPasswords));
```

##### Hash functions

[](#hash-functions)

Passwords should always be stored as secure hashes, making it impossible to determine the raw password from its stored representation. In order to check if a password matches a previously used password, the [no reuse rule](#no-reuse) must use the same algorithm as was used to create the password hash. This is the job of the hash functions, allowing comparison between raw passwords and hashed passwords. Currently, there is only one available hash function, supporting the built-in [php password hashing algorithms](#password-hasher). Support for other hash functions can easily be implemented.

###### Password hasher

[](#password-hasher)

The password hasher uses [Password Hashing Functions](https://www.php.net/manual/en/ref.password.php), which is built into php. If you use [`password_hash`](https://www.php.net/manual/en/function.password-hash.php) to store hashes of passwords, this is the right choice for the [no reuse rule](#no-reuse).

#### Change with interval

[](#change-with-interval)

The change with interval rule sets lower and upper limits to how often a password can be changed. A lower limit on the time from one password change to the next prevents passwords from being changed too often. This may be useful combined with the [no reuse rule](#no-reuse) enforcing that for example the 5 most recent passwords cannot be reused, since it prevents the user from just changing the password 5 times and then back to the original password. An upper limit on the time from one password change to the next enforces that passwords are changed on a regular basis.

For the rule to know when the password has been changed, former passwords must be specified. To specify former passwords, the password must be a `Password` object instead of a string.

```
use Stadly\PasswordPolice\FormerPassword;
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\Rule\ChangeWithIntervalRule;

// There must be at least 24 hours between password changes.
$rule = new ChangeWithIntervalRule(new DateInterval('PT24H'));

// There must be at most 30 days between password changes.
$rule = new ChangeWithIntervalRule(new DateInterval('PT0S'), new DateInterval('P30D'));

// Former passwords. The most recent one should be the current password.
$formerPasswords = [
    new FormerPassword(new DateTimeImmutable('2017-06-24')),
    new FormerPassword(new DateTimeImmutable('2018-08-04')),
    new FormerPassword(new DateTimeImmutable('2018-08-18')),
];
// Use a Password object instead of a string in order to specify former passwords.
$password = new Password('password', [], $formerPasswords));
```

#### Not set in interval

[](#not-set-in-interval)

The not set in interval rule enforces that the password was not set during the specified time period. This may be useful for example in the case of a data breach, after which all passwords should be changed, or for other security incidents, when only passwords set during the period of the security incident must be changed.

For the rule to know when the password was set, the current password must be specified as a former password. To specify former passwords, the password must be a `Password` object instead of a string.

```
use Stadly\PasswordPolice\FormerPassword;
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\Rule\NotSetInIntervalRule;

// Password must have been set after 2019-02-10.
$rule = new NotSetInIntervalRule(new DateTimeImmutable('2019-02-10'));

// Password cannot have been set between 2019-02-10 and 2019-02-13.
$rule = new NotSetInIntervalRule(new DateTimeImmutable('2019-02-13'), new DateTimeImmutable('2019-02-10'));

// Former passwords. The most recent one should be the current password.
$formerPasswords = [
    new FormerPassword(new DateTimeImmutable('2017-06-24')),
    new FormerPassword(new DateTimeImmutable('2018-08-04')),
    new FormerPassword(new DateTimeImmutable('2018-08-18')),
];
// Use a Password object instead of a string in order to specify former passwords.
$password = new Password('password', [], $formerPasswords));
```

#### Conditional rule

[](#conditional-rule)

The conditional rule is used to only conditionally apply another rule. The rule to apply conditionally, along with a condition function must be specified. The condition function should take a password (either `string` or `Password` object) as the only argument, and return `true` or `false`. If the condition function returns false, this rule does nothing. Otherwise, the specified rule is applied. This is useful for example to only apply the [Have I Been Pwned rule](#have-i-been-pwned) periodically. For example, to check at most once a month whether a password has been included in a data breach, instead of checking it every time the password is used.

```
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\Rule\ConditionalRule;

/**
 * @param Password|string $password
 * @return bool
 */
$conditionFunction = function($password): bool {
    return true; // Whether the rule should be applied.
}

$rule = new ConditionalRule($ruleToApplyConditionally, $conditionFunction);
```

### Formatters

[](#formatters)

Formatters are used to manipulate strings, and may be used in combination with the [dictionary](#dictionary) and [guessable data](#guessable-data) rules and with the [pspell](#pspell) word list. Formatters can be [chained](#chaining) so that the result of one formatter is fed into another formatter (the formatters are run in series). Formatters can also be [combined](#combiner), so that the results of multiple formatters are combined into one (the formatters are run in parallel).

#### Converters

[](#converters)

Converter formatters can convert characters in a string into other characters.

##### Capitalizer

[](#capitalizer)

The capitalizer converts the first character to upper case, and the rest to lower case.

```
use Stadly\PasswordPolice\Formatter\Capitalizer;

$formatter = new Capitalizer();
```

##### Leetspeak decoder

[](#leetspeak-decoder)

The leetspeak decoder decodes character sequences that can be interpreted as leetspeak. All decoding combinations are included in the result, so formatting the string `1337` results in the strings `1337`, `L337`, `1E37`, `13E7`, `133T`, `LE37`, `L3E7`, `L33T`, `1EE7`, `1E3T`, `13ET`, `LEE7`, `LE3T`, `L3ET`, `1EET`, `LEET`.

```
use Stadly\PasswordPolice\Formatter\LeetspeakDecoder;

$formatter = new LeetspeakDecoder();
```

##### Lower case converter

[](#lower-case-converter)

The lower case converter converts all characters to lower case.

```
use Stadly\PasswordPolice\Formatter\LowerCaseConverter;

$formatter = new LowerCaseConverter();
```

##### Mixed case converter

[](#mixed-case-converter)

The mixed case converter converts all characters to both lower case and upper case. All combinations are included in the result, so formatting the string `fOo` results in the strings `foo`, `Foo`, `fOo`, `foO`, `FOo`, `FoO`, `fOO`, `FOO`.

```
use Stadly\PasswordPolice\Formatter\MixedCaseConverter;

$formatter = new MixedCaseConverter();
```

##### Upper case converter

[](#upper-case-converter)

The upper case converter converts all characters to upper case.

```
use Stadly\PasswordPolice\Formatter\UpperCaseConverter;

$formatter = new UpperCaseConverter();
```

#### Splitters

[](#splitters)

Splitter formatters can extract parts of a string.

##### Substring generator

[](#substring-generator)

The substring generator generates all substrings. A minimum and maximum length for the substrings can be specified. Substring shorter than the minimum or longer than the maximum are not included in the result. The result only includes unique strings.

```
use Stadly\PasswordPolice\Formatter\SubstringGenerator;

// Ignore substring shorter than 3 characters or longer than 25 character.
$formatter = new SubstringGenerator(3, 25);
```

##### Truncator

[](#truncator)

The truncator truncates strings to a maximum length.

```
use Stadly\PasswordPolice\Formatter\Truncator;

$formatter = new Truncator(25); // Truncate strings so they contain no more than 25 characters.
```

#### Filters

[](#filters)

Filter formatters can filter out certain strings.

##### Length filter

[](#length-filter)

The length filter filters out strings that are shorter or longer than the limits.

```
use Stadly\PasswordPolice\Formatter\LengthFilter;

$formatter = new LengthFilter(3);       // Filter out strings shorter than 3 characters.
$formatter = new LengthFilter(0, 25);   // Filter out strings longer than 25 characters.
$formatter = new LengthFilter(3, 25);   // Filter out strings shorter than 3 or longer than 25 characters.
```

#### Combiner

[](#combiner)

The formatter combiner combines the results from multiple formatters into one (the formatters are run in parallel). Unformatted strings are also included in the result by default, but can be excluded. The result only includes unique strings.

```
use Stadly\PasswordPolice\Formatter\Combiner;
use Stadly\PasswordPolice\Formatter\LowerCaseConverter;
use Stadly\PasswordPolice\Formatter\UpperCaseConverter;

$lower = new LowerCaseConverter();
$upper = new UpperCaseConverter();

$formatter = new Combiner($lower, $upper);          // Lower case, upper case and unformatted strings.
$formatter = new Combiner($lower, $upper, false);   // Lower case and upper case strings.
```

#### Chaining

[](#chaining)

Formatters can be chained so that the result of one formatter is fed into another formatter (the formatters are run in series).

```
use Stadly\PasswordPolice\Formatter\LeetspeakDecoder;
use Stadly\PasswordPolice\Formatter\SubstringGenerator;

$formatter = new LeetspeakDecoder();

// First decode leetspeak, and then generate all substrings.
$formatter->setNext(new SubstringGenerator());
```

### Rule weights

[](#rule-weights)

All rules have an associated weight. The default weight is 1. By using weights, it is possible to differenciate between hard rules that cannot be circumvented, and rules that are merely intended as advice and may be ignored.

#### Rule weights when testing a rule or policy

[](#rule-weights-when-testing-a-rule-or-policy)

When testing a rule or policy, an optional weight can be specified. Rules with lower weights than the testing weight are ignored.

```
use Stadly\PasswordPolice\Policy;
use Stadly\PasswordPolice\Rule\DigitRule;
use Stadly\PasswordPolice\Rule\LengthRule;

$policy = new Policy();

$policy->addRules(new LengthRule(8, null, 1));  // Rule weight: 1.
$policy->addRules(new DigitRule(1, null, 2));   // Rule weight: 2.

$password = '123';

$policy->test($password, 1);    // False, since the password is too short.
$policy->test($password, 2);    // True, since the length rule is ignored.
```

#### Rule weights when validating a rule or policy

[](#rule-weights-when-validating-a-rule-or-policy)

When validating a rule or policy, all rules are validated, regardless of their weight. Each validation error contains the weight of the broken rule, which can be used to ignore validation errors of low weight.

```
use Stadly\PasswordPolice\Policy;
use Stadly\PasswordPolice\Rule\DigitRule;
use Stadly\PasswordPolice\Rule\LengthRule;

$policy = new Policy();

$policy->addRules(new LengthRule(8, null, 1));  // Rule weight: 1.
$policy->addRules(new DigitRule(1, null, 2));   // Rule weight: 2.

$password = '123';

$validationErrors = $policy->validate($password);
foreach ($validationErrors as $validationError) {
    // Ignore validation errors of weight lower than or equal to 1.
    if ($validationError->getWeight() > 1) {
        // Show validation message to the user.
        echo $validationError->getMessage();
    }
}
```

#### Constraints

[](#constraints)

In addition to specifying different weights to different rules, most rules can contain multiple constraints with different weights. This makes it possible to create rules with strict constraints having low weights and looser constrains with higher weights.

#### Constraint weights when testing a rule or policy

[](#constraint-weights-when-testing-a-rule-or-policy)

When testing a rule or policy, an optional weight can be specified. Rule constraints with lower weights than the testing weight are ignored.

```
use Stadly\PasswordPolice\Rule\LengthRule;

$rule = new LengthRule(12, null, 1);    // Constraint weight: 1.
$rule->addConstraint(8, null, 2);       // Constraint weight: 2.

$password = 'password';

$rule->test($password, 1);  // False, since the password is too short.
$rule->test($password, 2);  // True, since the strict constraint is ignored.
```

#### Constraint weights when validating a rule or policy

[](#constraint-weights-when-validating-a-rule-or-policy)

When validating a rule or policy, all rule constraints are validated, regardless of their weight. Each validation error contains the weight of the unsatisfied rule constraint, which can be used to ignore validation errors of low weight.

```
use Stadly\PasswordPolice\Rule\LengthRule;

$rule = new LengthRule(12, null, 1);    // Constraint weight: 1.
$rule->addConstraint(8, null, 2);       // Constraint weight: 2.

$password = 'password';

$validationErrors = $policy->validate($password);
foreach ($validationErrors as $validationError) {
    // Ignore validation errors of weight lower than or equal to 1.
    if ($validationError->getWeight() > 1) {
        // Show validation messages to the user.
        echo $validationError->getMessage();
    }
}
```

#### Example usage of weight

[](#example-usage-of-weight)

With version 2 of the [Have I Been Pwned](https://haveibeenpwned.com/Passwords) service, the number of times a password appears in data breaches was introduced. When writing about the new release, Troy Hunt gave an example of [how this number could be utilized](https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/#eachpasswordnowhasacountnexttoit):

> Having visibility to the prevalence means, for example, you might outright block every password that’s appeared 100 times or more and force the user to choose another one (there are 1,858,690 of those in the data set), strongly recommend they choose a different password where it’s appeared between 20 and 99 times (there’s a further 9,985,150 of those), and merely flag the record if it’s in the source data less than 20 times.

Such a password policy can be implemented by creating 3 rule constraints with different weights:

```
use Stadly\PasswordPolice\Rule\HaveIBeenPwnedRule;

// Weight 1 when password has appeared in data breaches 100 times or more.
$rule = new HaveIBeenPwnedRule(99, 0, 1);

// Weight 0 when password has appeared in data breaches between 20 and 99 times.
$rule->addConstraint(19, 0, 0);

// Weight -1 when password has appeared in data breaches between 1 and 19 times.
$rule->addConstraint(0, 0, -1);
```

When validating a password, the weight of the validation error can be used to determine which action to take:

```
$validationErrors = $policy->validate($password);
foreach ($validationErrors as $validationError) {
    if ($validationError->getWeight() === 1) {
        // Reject the password.
    } elseif ($validationError->getWeight() === 0) {
        // Recommend choosing a different password.
    } elseif ($validationError->getWeight() === -1) {
        // Flag the password.
    }
}
```

### Best practices for password policies

[](#best-practices-for-password-policies)

General guidelines for good password policies:

- Require passwords to be at least 8 characters [long](#length).
- Do not allow passwords that have been exposed in [data breaches](#have-i-been-pwned).
- Do not allow passwords that can be found in a [dictionary](#dictionary).
- Do not allow passwords to include words that are [easy to guess](#guessable-data), such as the service name or the user’s name.
- Do not allow passwords with repetitive or sequential characters (e.g. “aaaaaa”, “1234”, “abcd”, or “qwerty”).
- Do not require combinations of [lower case letters](#lower-case-letters), [upper case letters](#upper-case-letters), [digits](#digits), and [symbols](#symbols).
- Do not require passwords to be [changed periodically](#change-with-interval).

You can read more about password policy recommendations in [NIST SP 800-63B](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf), section 5.1.1 and Appendix A.

#### When to validate passwords

[](#when-to-validate-passwords)

There are two events in which a password can be validated agains a password policy:

1. When the password is set.
2. When the password is used.

Passwords should always be stored as secure hashes, making it impossible to determine the raw password from its stored representation. Therefore, the raw password is only available when it is being set or used, and hence the password can be validated only then. The only exception is password policy rules that do not need the password, such as the [change with interval rule](#change-with-interval) and the [not set in interval rule](#not-set-in-interval).

It is recommended to validate passwords both when they are set and when they are used, but the rules to apply are usually different in the two cases.

##### Validating passwords that are being set

[](#validating-passwords-that-are-being-set)

When the password is being set is the right time to validate the password format, such as [length](#length), [lower case letters](#lower-case-letters), [upper case letters](#upper-case-letters), [digits](#digits), and [symbols](#symbols). In addition, the password content should be validated, using rules such as [Have I Been Pwned](#have-i-been-pwned), [dictionary](#dictionary), and [guessable data](#guessable-data). Rules that consider former passwords should also be checked, such as [no reuse](#no-reuse) and [change with interval](#change-with-interval) with lower limit.

##### Validating passwords that are being used

[](#validating-passwords-that-are-being-used)

When the password is being used, there is no need to validate the format of the password. Assuming that the password policy has not changed, the password will pass validation when it is used if it passed validation when it was set. When the password is used, the password only needs to be validated agains rules whose validation outcome can change without the password being changed. Such rules are for example [Have I Been Pwned](#have-i-been-pwned) (a formerly valid password can become invalid after a new databreach), [change with interval](#change-with-interval) with upper limit, and [not set in interval](#not-set-in-interval).

Change log
----------

[](#change-log)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Testing
-------

[](#testing)

```
composer test
```

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) and [CODE\_OF\_CONDUCT](CODE_OF_CONDUCT.md) for details.

Security
--------

[](#security)

If you discover any security related issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Magnar Ovedal Myrtveit](https://github.com/Stadly)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity23

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 99.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~14 days

Recently: every ~46 days

Total

24

Last Release

2393d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f5d9e0c770d26a4a636b51657f9f89b076bb401e8f16b2f7aac9dfa1341f6d69?d=identicon)[Stadly](/maintainers/Stadly)

---

Top Contributors

[![Stadly](https://avatars.githubusercontent.com/u/7263579?v=4)](https://github.com/Stadly "Stadly (474 commits)")[![muglug](https://avatars.githubusercontent.com/u/2292638?v=4)](https://github.com/muglug "muglug (2 commits)")

---

Tags

have-i-been-pwnedhaveibeenpwnedpasswordpassword-policysecuritysecuritypasswordPolicypassword policy

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/stadly-password-police/health.svg)

```
[![Health](https://phpackages.com/badges/stadly-password-police/health.svg)](https://phpackages.com/packages/stadly-password-police)
```

###  Alternatives

[rych/phpass

PHP Password Library: Easy, secure password management for PHP

248801.7k4](/packages/rych-phpass)[bordoni/phpass

Portable PHP password hashing framework

244.4M26](/packages/bordoni-phpass)[jeremykendall/password-validator

Password Validator validates password\_hash generated passwords, rehashes passwords as necessary, and will upgrade legacy passwords.

14469.9k3](/packages/jeremykendall-password-validator)[mxrxdxn/pwned-passwords

A library to query Troy Hunt's Pwned Passwords service to see whether or not a password has been included in a public breach.

3270.9k2](/packages/mxrxdxn-pwned-passwords)[spooner-web/be_secure_pw

You can set password conventions to force secure passwords for BE users.

10461.3k](/packages/spooner-web-be-secure-pw)[firehed/security

Security tools for PHP

2374.9k2](/packages/firehed-security)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
