前言

passay 是一个 Java 开源的密码安全策略库,可用于生成和验证密码。它提供了全面的规则类以验证/生成密码,并且高度可配置。

Passay API由3个核心组件组成:

  • Rule 针对密码强度规则的接口。用于定义了一个密码策略规则集,包含一个或多个规则
  • PasswordValidator 密码校验器。用于对一个候选密码评估多个密码规则的中心组件,可根据规则集验证密码
  • PasswordGenerator 密码生成器。生成满足给定规则集的密码

官网:http://www.passay.org/

github:https://github.com/vt-middleware/passay

pom 依赖:

<dependency>
    <groupId>org.passay</groupId>
    <artifactId>passay</artifactId>
    <version>1.6.2</version>
</dependency>

一、密码规则

规则是密码校验和生成的基础,规则可分为两种:

  • 正匹配,要求密码满足规则
  • 负匹配,拒绝满足规则的密码

1.1 正匹配规则

规则类含义
AllowedCharacterRule要求密码只包含在给定集合中的字符
AllowedRegexRule要求密码符合正则表达式
CharacterCharacteristicsRule要求密码包含M个N类字符;例如,以下4种字符中的3个:数字、大写字母、小写字母、符号
CharacterRule要求密码包含来自给定字符集的至少N个字符(例如,数字、大写字母、小写字母、符号)
LengthRule要求使用密码长度
LengthComplexityRule要求密码满足基于密码长度的特定规则。例如,长度为8-12个字符的密码必须同时包含一个数字和符号。密码13个字符及更长的密码只能包含字母字符

1.2 负匹配规则

规则类含义
DictionaryRule拒绝与字典中的条目相匹配的密码(精确匹配语义)
DictionarySubstringRule拒绝包含字典中的条目的密码(子字符串匹配语义)
DigestDictionaryRule拒绝与字典中摘要条目匹配的密码(哈希/摘要比较)
HistoryRule拒绝与以前的密码相匹配的密码(明文比较)
DigestHistoryRule拒绝与以前的密码摘要相匹配的密码(散列/摘要比较)
CharacterOccurrencesRule拒绝包含过多相同字符的密码
IllegalCharacterRule拒绝包含集合中任意字符的密码
IllegalRegexRule拒绝符合正则表达式的密码
IllegalSequenceRule拒绝包含N个字符序列的密码(例如,12345)
NumberRangeRule拒绝包含在定义范围内的任何数字的密码(例如,1000-9999)
SourceRule拒绝与来自其他来源的密码相匹配的密码(明文比较)
DigestSourceRule拒绝与其他来源的摘要匹配的密码(散希/摘要比较)
RepeatCharacterRegexRule拒绝包含重复的ASCII字符的密码,默认的序列长度为5个字符。
RepeatCharactersRule拒绝包含多个重复字符序列的密码
UsernameRule拒绝包含提供该密码的用户的用户名的密码
WhitespaceRule拒绝包含空白字符的密码

二、校验密码

2.1 普通校验

  1. qq 密码要求

    • 长度为8-16位
    • 必须包含字母、数字、符号中至少两位
    • 不包含空格
    List<Rule> rules = new ArrayList<>();
    rules.add(new LengthRule(8, 16));
    CharacterCharacteristicsRule characteristicsRule = new CharacterCharacteristicsRule(2,
            new CharacterRule(EnglishCharacterData.Alphabetical, 1),
            new CharacterRule(EnglishCharacterData.Digit, 1),
            new CharacterRule(EnglishCharacterData.Special, 1));
    rules.add(characteristicsRule);
    rules.add(new WhitespaceRule(new char[]{0x20}));
    
    PasswordValidator qqValidator = new PasswordValidator(rules);
    
    String pass = "12345678 a";
    RuleResult ruleResult = qqValidator.validate(new PasswordData(pass));
    assertThat(ruleResult.isValid(), is(false));
    
  2. gmail 密码要求

    • 长度为8-100位
    • 必须包含字母、数字、符号
    List<Rule> rules = new ArrayList<>();
    rules.add(new LengthRule(8, 100));
    CharacterCharacteristicsRule characteristicsRule = new CharacterCharacteristicsRule(3,
            new CharacterRule(EnglishCharacterData.Alphabetical, 1),
            new CharacterRule(EnglishCharacterData.Digit, 1),
            new CharacterRule(EnglishCharacterData.Special, 1));
    rules.add(characteristicsRule);
    
    PasswordValidator gmailValidator = new PasswordValidator(rules);
    
    String pass = "12345678 @a";
    RuleResult ruleResult = gmailValidator.validate(new PasswordData(pass));
    assertThat(ruleResult.isValid(), is(true));
    

2.2 高级校验

  1. 密码黑名单字典

    passwordblack.txt

    12345678
    1qaz2wsx
    password
    !password!
    
    DictionaryRule rule = new DictionaryRule(
            new WordListDictionary(WordLists.createFromReader(
                    // Reader around the word list file
                    new FileReader[] {new FileReader("src/main/resources/passwordblack.txt")},
                    // True for case sensitivity, false otherwise
                    false,
                    // Dictionaries must be sorted
                    new ArraysSort())));
    
    PasswordValidator dValidator = new PasswordValidator(rule);
    
    RuleResult result1 = dValidator.validate(new PasswordData("!password!"));
    assertThat(result1.isValid(), is(false));
    logger.info("{}", dValidator.getMessages(result1)); // [Password contains the dictionary word '!password!'.]
    
  2. 和历史密码比对

    密码存在数据库一般会加密,和历史密码对比,需要引入加密库

    <dependency>
        <groupId>org.cryptacular</groupId>
        <artifactId>cryptacular</artifactId>
        <version>1.2.5</version>
    </dependency>
    
    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")
    );
    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> historyRules = Arrays.asList(
            // ...
            // Insert other rules as needed
            // ...
            new DigestHistoryRule(hasher));
    
    PasswordValidator historyValidator = new PasswordValidator(historyRules);
    PasswordData data = new PasswordData("username", "P@ssword1");
    data.setPasswordReferences(history);
    RuleResult result2 = historyValidator.validate(data);
    assertThat(result2.isValid(), is(false));
    logger.info("{}", historyValidator.getMessages(result2)); // [Password matches one of 3 previous passwords.]
    
  3. 字符序列

    passay 还支持校验字符序列,字母序列,数字序列,键盘字符qwerty序列

    List<Rule> ruleList = Arrays.asList(
            new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false),
            new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false),
            new IllegalSequenceRule(EnglishSequenceData.USQwerty, 5, false));
    
    PasswordValidator seqValidator = new PasswordValidator(ruleList);
    RuleResult result3 = seqValidator.validate(new PasswordData("qwerty"));
    logger.info("{}, {}", result3.isValid(), seqValidator.getMessages(result3));
    

2.3 自定义校验信息

从上面密码校验信息输出可以看到都是英文,passay 支持国际化,可配置为中文

message.properties,没有全部翻译

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=密码必须包含%1s个或更多大写字符.
INSUFFICIENT_LOWERCASE=密码必须包含%1s个或更多小写字符.
INSUFFICIENT_ALPHABETICAL=密码必须包含%1s个或更多字母字符.
INSUFFICIENT_DIGIT=密码必须包含%1s个或更多数字字符.
INSUFFICIENT_SPECIAL=密码必须包含%1s个或更多特殊字符.
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=密码不能与你的%1s密码相同.
TOO_LONG=密码长度不得超过%2s个字符.
TOO_SHORT=密码的长度必须为%1$s个或更多的字符.
TOO_MANY_OCCURRENCES=密码包含%2s次出现的字符%1s,但最多允许出现%3s次
Properties props = new Properties();
InputStream url = Files.newInputStream(Paths.get("src/main/resources/message.properties"));
props.load(new InputStreamReader(url, StandardCharsets.UTF_8));
MessageResolver resolver = new PropertiesMessageResolver(props);
PasswordValidator validator = new PasswordValidator(resolver,
        // 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());

RuleResult result = validator.validate(new PasswordData("aaaaa"));

if (result.isValid()) {
    logger.info("合法密码");
} else {
    logger.info("不合法密码");
    for (String s: validator.getMessages(result)) {
        logger.info("{}", s);
        // 密码的长度必须为8个或更多的字符.
		// 密码必须包含1个或更多大写字符.
		// 密码必须包含1个或更多数字字符.
		// 密码必须包含1个或更多特殊字符.
    }
}

三、生成密码

PasswordGenerator 能够通过特定的字符集和字符规则,来实现构造密码

3.1 英文相关字符密码

List<CharacterRule> characterRuleList = Arrays.asList(
        new CharacterRule(EnglishCharacterData.UpperCase, 1),
        new CharacterRule(EnglishCharacterData.Digit, 2)
);

PasswordGenerator generator = new PasswordGenerator();
String s = generator.generatePassword(10, characterRuleList);
logger.info("{}", s); // O4BUKJ620G

3.2 自定义字符集密码

支持提供自定义字符集

List<CharacterRule> characterRuleList = Arrays.asList(
    	// 至少1个自定义中文字符集合字符
        new CharacterRule(new CharacterData() {
            @Override
            public String getErrorCode() {
                return "中文error";
            }

            @Override
            public String getCharacters() {
                return "中文字符测试集合";
            }
        }, 1),
    	// 至少2个数字
        new CharacterRule(EnglishCharacterData.Digit, 2)
);

PasswordGenerator generator = new PasswordGenerator();
String s = generator.generatePassword(10, characterRuleList);
logger.info("{}", s);// 65集0002中91

参考

  1. http://www.passay.org/reference/
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐