前言
passay 是一个 Java 开源的密码安全策略库,可用于生成和验证密码。它提供了全面的规则类以验证/生成密码,并且高度可配置。
Passay API由3个核心组件组成:
Rule
针对密码强度规则的接口。用于定义了一个密码策略规则集,包含一个或多个规则PasswordValidator
密码校验器。用于对一个候选密码评估多个密码规则的中心组件,可根据规则集验证密码PasswordGenerator
密码生成器。生成满足给定规则集的密码
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 普通校验
-
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));
-
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 高级校验
-
密码黑名单字典
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!'.]
-
和历史密码比对
密码存在数据库一般会加密,和历史密码对比,需要引入加密库
<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.]
-
字符序列
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