Reference manual

The Passay API consists of 3 core components:

  1. Rule - one or more rules define a password policy rule set
  2. PasswordValidator - validates a password against a rule set
  3. PasswordGenerator - produces passwords that satisfy a given rule set

Rule overview

Rules are the building blocks for both password validation and generation, and it is helpful to review the ruleset that passay provides out of the box. There are two broad categories of rules:

  1. Positive match require that passwords satisfy a rule
  2. Negative match reject passwords that satisfy a rule

The following sections briefly describe the available rules in both categories.

Positive matching rules

  1. AllowedCharacterRule - requires passwords to contain all of a set of characters
  2. AllowedRegexRule - requires passwords to conform to a regular expression
  3. CharacterCharacteristicsRule - requires passwords to contain M of N classes of characters; for example, 3 of 4 of the following: digit, upper-case letters, lower-case letters, symbols
  4. CharacterRule - requires passwords to contain at least N characters from a given character set (e.g. digits, upper-case letters, lowercase-letters, symbols)
  5. LengthRule - requires passwords to meet a minimum required length
  6. LengthComplexityRule - requires passwords to meet a specific set of rules based on the length of the password. For example, passwords between 8-12 characters long must contain both a number and symbol. Passwords 13 characters and longer must only contain alphabetical characters

Negative matching rules

  1. Dictionary rules
    1. DictionaryRule - rejects passwords that match an entry in a dictionary (exact match semantics)
    2. DictionarySubstringRule - rejects passwords that contain an entry in a dictionary (substring match semantics)
    3. DigestDictionaryRule - rejects passwords that match a digested entry in a dictionary (hash/digest comparison)
  2. History rules
    1. HistoryRule - rejects passwords that match previous passwords (cleartext comparison)
    2. DigestHistoryRule - rejects passwords that match previous password digests (hash/digest comparison)
  3. CharacterOccurrencesRule - rejects passwords that contain too many occurances of the same character
  4. IllegalCharacterRule - rejects passwords that contain any of a set of characters
  5. IllegalRegexRule - rejects passwords that conform to a regular expression
  6. IllegalSequenceRule - rejects passwords that contain a sequence of N characters (e.g. 12345)
  7. NumberRangeRule - rejects passwords that contain any number within a defined range (e.g. 1000-9999)
  8. Source rules
    1. SourceRule - rejects passwords that match those from another source (cleartext comparison)
    2. DigestSourceRule - rejects passwords that match the digest of those from another source (hash/digest comparison)
  9. RepeatCharacterRegexRule - rejects passwords that contain a repeated ASCII character
  10. RepeatCharactersRule - rejects passwords that contain multiple sequences of repeating characters
  11. UsernameRule - rejects passwords that contain the username of the user providing the password
  12. WhitespaceRule - rejects passwords that contain whitespace characters

Password validation

Password validation involves creating a PasswordValidator from a rule set, which is simply a list of Rule objects. Consider the following simple password policy:

  1. Length of 8 to 16 characters
  2. Must contain at least one of the following: upper case, lower case, digit, and symbol
  3. No whitespace characters

The following code excerpt constructs a validator that enforces the policy.

PasswordValidator validator = new PasswordValidator(
  // length between 8 and 16 characters
  new LengthRule(8, 16),

  // at least one upper-case character
  new CharacterRule(EnglishCharacterData.UpperCase, 1),

  // at least one lower-case character
  new CharacterRule(EnglishCharacterData.LowerCase, 1),

  // at least one digit character
  new CharacterRule(EnglishCharacterData.Digit, 1),

  // at least one symbol (special character)
  new CharacterRule(EnglishCharacterData.Special, 1),

  // define some illegal sequences that will fail when >= 5 chars long
  // alphabetical is of the form 'abcde', numerical is '34567', qwery is 'asdfg'
  // the false parameter indicates that wrapped sequences are allowed; e.g. 'xyzabc'
  new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false),
  new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false),
  new IllegalSequenceRule(EnglishSequenceData.USQwerty, 5, false),

  // no whitespace
  new WhitespaceRule());

final char[] password = System.console().readPassword("Password: ");
RuleResult result = validator.validate(new PasswordData(new String(password)));
if (result.isValid()) {
  System.out.println("Password is valid");
} else {
  System.out.println("Invalid password:");
  for (String msg : validator.getMessages(result)) {
    System.out.println(msg);
  }
}

Advanced validation: customizing messages

Passay provides the MessageResolver interface to allow arbitrary conversion of password validation results to meaningful text intended for display to users. The default mechanism uses a message bundle to define validation messages whose default values are shown below.

HISTORY_VIOLATION=Password matches one of %1$s previous passwords.
ILLEGAL_WORD=Password contains the dictionary word '%1$s'.
ILLEGAL_WORD_REVERSED=Password contains the reversed dictionary word '%1$s'.
ILLEGAL_DIGEST_WORD=Password contains a dictionary word.
ILLEGAL_DIGEST_WORD_REVERSED=Password contains a reversed dictionary word.
ILLEGAL_MATCH=Password matches the illegal pattern '%1$s'.
ALLOWED_MATCH=Password must match pattern '%1$s'.
ILLEGAL_CHAR=Password %2$s the illegal character '%1$s'.
ALLOWED_CHAR=Password %2$s the illegal character '%1$s'.
ILLEGAL_QWERTY_SEQUENCE=Password contains the illegal QWERTY sequence '%1$s'.
ILLEGAL_ALPHABETICAL_SEQUENCE=Password contains the illegal alphabetical sequence '%1$s'.
ILLEGAL_NUMERICAL_SEQUENCE=Password contains the illegal numerical sequence '%1$s'.
ILLEGAL_USERNAME=Password %2$s the user id '%1$s'.
ILLEGAL_USERNAME_REVERSED=Password %2$s the user id '%1$s' in reverse.
ILLEGAL_WHITESPACE=Password %2$s a whitespace character.
ILLEGAL_NUMBER_RANGE=Password %2$s the number '%1$s'.
ILLEGAL_REPEATED_CHARS=Password contains %3$s sequences of %1$s or more repeated characters, but only %2$s allowed: %4$s.
INSUFFICIENT_UPPERCASE=Password must contain %1$s or more uppercase characters.
INSUFFICIENT_LOWERCASE=Password must contain %1$s or more lowercase characters.
INSUFFICIENT_ALPHABETICAL=Password must contain %1$s or more alphabetical characters.
INSUFFICIENT_DIGIT=Password must contain %1$s or more digit characters.
INSUFFICIENT_SPECIAL=Password must contain %1$s or more special characters.
INSUFFICIENT_CHARACTERISTICS=Password matches %1$s of %3$s character rules, but %2$s are required.
INSUFFICIENT_COMPLEXITY=Password meets %2$s complexity rules, but %3$s are required.
INSUFFICIENT_COMPLEXITY_RULES=No rules have been configured for a password of length %1$s.
SOURCE_VIOLATION=Password cannot be the same as your %1$s password.
TOO_LONG=Password must be no more than %2$s characters in length.
TOO_SHORT=Password must be %1$s or more characters in length.
TOO_MANY_OCCURRENCES=Password contains %2$s occurrences of the character '%1$s', but at most %3$s are allowed.

The following example demonstrates how to replace the default message bundle with a custom/localized properties file.

Properties props = new Properties();
props.load(new FileInputStream("/path/to/passay.properties"));
MessageResolver resolver = new PropertiesMessageResolver(props);
PasswordValidator validator = new PasswordValidator(
  resolver, new LengthRule(8, 16), new WhitespaceRule());

Advanced validation: M of N rules

Many password policies contain a rule of the form password must contain at least M of the following N categories. The CharacterCharacteristicsRule component supports this use case. Consider the following policy:

  1. Length of 8 to 16 characters
  2. Must contain characters from at least 3 of the following: upper, lower, digit, symbol
  3. No whitespace characters

This policy is implemented in the following code excerpt.

LengthRule r1 = new LengthRule(8, 16);

CharacterCharacteristicsRule r2 = new CharacterCharacteristicsRule();

// Define M (3 in this case)
r2.setNumberOfCharacteristics(3);

// Define elements of N (upper, lower, digit, symbol)
r2.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, 1));
r2.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, 1));
r2.getRules().add(new CharacterRule(EnglishCharacterData.Digit, 1));
r2.getRules().add(new CharacterRule(EnglishCharacterData.Special, 1));

WhitespaceRule r3 = new WhitespaceRule();

PasswordValidator validator = new PasswordValidator(r1, r2, r3);

Advanced validation: dictionary rules

Many password policies seek to prevent common words (e.g. password) from appearing in passwords. Passay ships with two rules that can be used with arbitrary word lists to enforce dictionary policies:

  1. DictionaryRule - exact matching semantics
  2. DictionarySubstringRule - contains matching semantics

DictionarySubstringRule should be used judiciously since a configuration with a sizeable common word list would prevent strong passwords like correcthorsebatterystaple and random.words@31415. A reasonable use case might be a relatively short list of reserved words forbidden to appear in passwords.

DictionaryRule, on the other hand, has a valuable common use case: preventing known weak passwords. Configuring this rule with a published list of popular passwords, such as those from the Adobe breach, can dramatically increase password security by preventing common, and therefore insecure, passwords.

DictionaryRule rule = new DictionaryRule(
  new WordListDictionary(WordLists.createFromReader(
    // Reader around the word list file
    new FileReader[] {new FileReader("path/to/top100.txt")},
    // True for case sensitivity, false otherwise
    false,
    // Dictionaries must be sorted
    new ArraysSort())));

Provided dictionaries

  1. JDBCDictionary - searches for words in a database
  2. WordListDictionary - searches for words in a list of Strings
    1. ArrayWordList - an in-memory list of words
    2. FileWordList - a file backed list of words (best for smaller file sizes)
    3. MemoryMappedFileWordList - a file backed list of words that leverages a MappedByteBuffer (best for larger file sizes)
  3. BloomFilterDictionary - searches for words in a Bloom Filter
    1. Uses the BloomFilter implementation provided in the Google Guava library. Note that bloom filters by nature report false positives, be sure to construct a filter with a thoughtful false positive probability.
  4. TernaryTreeDictionary - searches for words in an in-memory tree structure

Advanced validation: password history

The following rules support enforcement of unique passwords in the context of password history:

  1. HistoryRule - for passwords stored as cleartext (insecure, uncommon)
  2. DigestHistoryRule - for passwords stored as a hash/digest

Both rules require querying a data source for historical password data, but in practice DigestHistoryRule is the more useful component since passwords are typically stored as a hash/digest. Digest support requires the use of message digest components provided by the cryptacular crypto library, which is an optional dependency of this library. The example below demonstrates history-based validation for passwords stored in the following format:

  1. SHA-256 digest algorithm
  2. The hash is computed by digesting two values in turn:
    1. Password string as UTF-8 characters
    2. Random 16-byte salt value
  3. The salt is appended to the hash output to form a 48-byte value (32-byte hash + 16-byte salt)
  4. The hash output is encoded as base64 characters

This is a realistic scenario for passwords stored in an LDAP directory using the SSHA pseudo-standard.

// The historical data would be obtained from an authentication store in a
// real-world scenario. Each item consists of a label and the encoded password
// data. A common use case for labels is multiple password encodings where each
// label identifies a particular encoding.
// Salt=86ffd2e3521b5b169ec9a75678c92eed
List<PasswordData.Reference> history = Arrays.asList(
  // Password=P@ssword1
  new PasswordData.HistoricalReference(
    "SHA256",
    "j93vuQDT5ZpZ5L9FxSfeh87zznS3CM8govlLNHU8GRWG/9LjUhtbFp7Jp1Z4yS7t"),

  // Password=P@ssword2
  new PasswordData.HistoricalReference(
    "SHA256",
    "mhR+BHzcQXt2fOUWCy4f903AHA6LzNYKlSOQ7r9np02G/9LjUhtbFp7Jp1Z4yS7t"),

  // Password=P@ssword3
  new PasswordData.HistoricalReference(
    "SHA256",
    "BDr/pEo1eMmJoeP6gRKh6QMmiGAyGcddvfAHH+VJ05iG/9LjUhtbFp7Jp1Z4yS7t")
);

// Cryptacular components:
// org.cryptacular.bean.EncodingHashBean;
// org.cryptacular.spec.CodecSpec;
// org.cryptacular.spec.DigestSpec;
EncodingHashBean hasher = new EncodingHashBean(
  new CodecSpec("Base64"), // Handles base64 encoding
  new DigestSpec("SHA256"), // Digest algorithm
  1, // Number of hash rounds
  false); // Salted hash == false

List<Rule> rules = Arrays.asList(
  // ...
  // Insert other rules as needed
  // ...
  new DigestHistoryRule(hasher));

PasswordValidator validator = new PasswordValidator(rules);
PasswordData data = new PasswordData("username", "P@ssword1");
data.setPasswordReferences(history);
RuleResult result = validator.validate(data);

Password generation

The password generation API uses a specialized ruleset consisting exclusively of CharacterRule, a specialization of Rule, to define the requisite character classes in generated passwords. The example below demonstrates password generation for the following policy:

  1. Length of 8 to 16 characters
  2. Must contain at least one of the following: upper case, lower case and digit
  3. No whitespace characters
List<CharacterRule> rules = Arrays.asList(
  // at least one upper-case character
  new CharacterRule(EnglishCharacterData.UpperCase, 1),

  // at least one lower-case character
  new CharacterRule(EnglishCharacterData.LowerCase, 1),

  // at least one digit character
  new CharacterRule(EnglishCharacterData.Digit, 1));

PasswordGenerator generator = new PasswordGenerator();

// Generated password is 12 characters long, which complies with policy
String password = generator.generatePassword(12, rules);

Note that generated passwords in the above example don’t contain spaces since none of the character sets above includes a space character. It is trivial to add support for generating passwords with spaces by including an additional CharacterRule with a custom character set as follows.

new CharacterRule(new CharacterData() {
  @Override
  public String getErrorCode() {
    return "ERR_SPACE";
  }

  @Override
  public String getCharacters() {
    return " ";
  }
}, 1);