• Java开源工具库使用之密码安全策略库passay


    前言

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

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

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

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

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

    pom 依赖:

    <dependency>
        <groupId>org.passaygroupId>
        <artifactId>passayartifactId>
        <version>1.6.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一、密码规则

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

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

    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));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    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));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    2.2 高级校验

    1. 密码黑名单字典

      passwordblack.txt

      12345678
      1qaz2wsx
      password
      !password!
      
      • 1
      • 2
      • 3
      • 4
      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!'.]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    2. 和历史密码比对

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

      <dependency>
          <groupId>org.cryptaculargroupId>
          <artifactId>cryptacularartifactId>
          <version>1.2.5version>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      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.]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
    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));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    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次
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    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个或更多特殊字符.
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    三、生成密码

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    参考

    1. http://www.passay.org/reference/
  • 相关阅读:
    个人网站接入Google Ads的一点心得
    【mysql官方文档】死锁
    vulnhub之cereal
    Python 函数进阶-高阶函数
    Linux安装详解
    CSDN 编程竞赛五十五期题解
    测试开发都这么厉害了?为啥不直接转业务开发?
    Linux用户管理— 用户相关文件-passwd文件- shadow文件-其他相关文件
    ensp实操浮动静态路由
    ElasticSearch 狂神说
  • 原文地址:https://blog.csdn.net/qq_23091073/article/details/126847782