Passay密码生成与验证

young 970 2022-06-21

官网

Passay是一个基于Java的密码生成和验证库。

Passay的API拥有3个核心组件

  • Rule:一个或多个的密码策略规则集合
  • PasswordValidator:密码验证器,根据规则对密码进行验证
  • PasswordGenerator:密码生成器,生成满足规则的密码

规则

规则是密码生成和验证的基础,分为两类

  1. 正匹配规则:要求密码满足规则
  2. 否定匹配规则:拒绝满足规则的密码

正匹配规则

AllowedCharacterRule 要求密码包含一组字符的所有字符

AllowedRegexRule要求密码符合正则表达式

CharacterCharacteristicsRule要求密码复核N类字符中的M,如以下4个中的三个:digit(数字), upper-case letters(大写字母), lower-case letters(小写字母), symbols(符号)

CharacterRule要求密码至少包含给定字符集中的 N 个字符

LengthRule 要求密码满足所需的最小长度

LengthComplexityRule 要求密码满足基于密码长度的特定规则集。例如,长度在 8-12 个字符之间的密码必须同时包含数字和符号。密码 13 个字符及以上,只能包含字母字符

否定匹配规则

  1. 字典规则
    1. 字典规则 - 拒绝与字典中的条目匹配的密码(完全匹配语义)
    2. 字典子字符串规则 - 拒绝包含字典中条目的密码(子字符串匹配语义)
    3. 摘要字典规则 - 拒绝与字典中摘要条目匹配的密码(哈希/摘要比较)
  2. 历史记录规则
    1. 历史记录规则 - 拒绝与以前密码匹配的密码(明文比较)
    2. 摘要历史记录规则 - 拒绝与以前的密码摘要匹配的密码(哈希/摘要比较)
  3. 字符事件规则 - 拒绝包含太多相同字符的密码
  4. 非法字符规则 - 拒绝包含一组字符中的任何一个的密码
  5. 非法正则表达式规则 - 拒绝符合正则表达式的密码
  6. 非法序列规则 - 拒绝包含 N 个字符序列的密码(例如 12345)
  7. NumberRangeRule - 拒绝包含定义范围内任何数字的密码(例如 1000-9999)
  8. 源规则
    1. SourceRule - 拒绝与来自其他源的密码匹配的密码(明文比较)
    2. DigestSourceRule - 拒绝与来自其他来源的摘要相匹配的密码(哈希/摘要比较)
  9. 重复字符正则规则 - 拒绝包含重复 ASCII 字符的密码
  10. 重复字符规则 - 拒绝包含多个重复字符序列的密码
  11. 用户名规则 - 拒绝包含提供密码的用户的用户名的密码
  12. 空格规则 - 拒绝包含空格字符的密码

密码校验

密码校验采用PasswordValidator进行

PasswordValidator passwordValidator = new PasswordValidator(
        // 长度 8-30位
        new LengthRule(8, 30),
        // 至少一个大写字母
        new CharacterRule(EnglishCharacterData.UpperCase, 1),
        // 至少一个小写字母
        new CharacterRule(EnglishCharacterData.LowerCase, 1),
        // 至少一个特殊字符
        new CharacterRule(EnglishCharacterData.Special, 1),
        // 至少一个数字
        new CharacterRule(EnglishCharacterData.Digit, 1),
        // 不允许连续5个字母
        new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false),
        // 不允许连续5个数字
        new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false),
        // 不允许键盘上的连续5个键
        new IllegalSequenceRule(EnglishSequenceData.USQwerty, 5, false),
        // 不允许空白字符
        new WhitespaceRule()
);
// 校验的密码要用PasswordData包装
RuleResult ruleResult = passwordValidator.validate(new PasswordData("sdfhusdifi"));
// 判断是否校验通过
if (ruleResult.isValid()) {
    System.out.println("password valid");
}else {
  	// 打印校验不通过的规则
    List<String> messages = passwordValidator.getMessages(ruleResult);
    System.out.println(String.join("\n",messages));
}
Password must contain 1 or more uppercase characters.
Password must contain 1 or more special characters.
Password must contain 1 or more digit characters.

自定义消息

Passay提供MessageResolver接口,允许将密码验证结果任意转换为自定义的文本,以便向用户显示。

默认Message

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.

加载自定义的消息配置

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());

passay也实现了基于Spring的SpringMessageResolver,可以将Spring的MessageSource直接配置进去

@Bean
public MessageResolver messageResolver() {
    return new SpringMessageResolver(messageSource);
}
@Resource
private SpringMessageResolver springMessageResolver;
...
new PasswordValidator(springMessageResolver,new LengthRule(8, 16), new WhitespaceRule());

高级验证

N个规则中的M个

CharacterCharacteristicsRule characterCharacteristicsRule = new CharacterCharacteristicsRule();
// 至少满足3个规则
characterCharacteristicsRule.setNumberOfCharacteristics(3);
// 至少一个大写字母
characterCharacteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, 1));
// 至少一个小写字母
characterCharacteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, 1));
// 至少一个特殊字符
characterCharacteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Special, 1));
// 至少一个数字
characterCharacteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Digit, 1));

PasswordValidator passwordValidator = new PasswordValidator(
        // 长度 8-30位
        new LengthRule(8, 30),
        // N个规则验证M个
        characterCharacteristicsRule,
        // 不允许空白字符
        new WhitespaceRule()
);
RuleResult ruleResult = passwordValidator.validate(new PasswordData("Aaaaaaaaaa"));
if (ruleResult.isValid()) {
    System.out.println("password valid");
}else {
    List<String> messages = passwordValidator.getMessages(ruleResult);
    System.out.println(String.join("\n",messages));
}

字典规则

许多密码策略旨在防止常用词(例如password)出现在密码中。

Passay附带了两个规则,可以与任意单词列表一起使用,以执行字典策略

  1. DictionaryRule - 精确匹配语义
  2. DictionarySubstringRule - 包含匹配的语义

DictionaryRule:防止已知的弱密码。使用已发布的常用密码列表(例如来自 Adobe 违规的密码)配置此规则,可以通过防止常见且因此不安全的密码来显著提高密码安全性。

DictionarySubstringRule应该明智地使用,只要密码中包含配置项,则会被拒绝。

DictionarySubstringRule dictionarySubstringRule =
        new DictionarySubstringRule(new WordListDictionary(new ArrayWordList(new String[]{"password"})));

PasswordValidator passwordValidator = new PasswordValidator(dictionarySubstringRule);
RuleResult ruleResult = passwordValidator.validate(new PasswordData("password123"));
System.out.println(ruleResult.isValid()); // false


DictionaryRule dictionaryRule =
        new DictionaryRule(new WordListDictionary(new ArrayWordList(new String[]{"password"})));
passwordValidator = new PasswordValidator(dictionaryRule);
ruleResult = passwordValidator.validate(new PasswordData("password123"));
System.out.println(ruleResult.isValid()); // true

官方示例:

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())));

密码生成

密码生成采用PasswordGenerator进行

List<CharacterRule> rules = Arrays.asList(
        // 最少一个大写字母
        new CharacterRule(EnglishCharacterData.UpperCase, 1),
        // 最少一个小写字母
        new CharacterRule(EnglishCharacterData.LowerCase, 1),
        // 最少一个数字
        new CharacterRule(EnglishCharacterData.Digit, 1)
);

PasswordGenerator generator = new PasswordGenerator();
// 15位长度
String password = generator.generatePassword(15, rules);
System.out.println(password);