Skip to main content
 首页 » 编程设计

Java生成安全随机密码

2022年07月19日121dudu

Java生成安全随机密码

本文讨论在Java中使用多种方法生成安全随机密码。我们示例中约定安全密码包括10个字符,至少包括两个小写字母、两个大写字母、两个数字以及两个特殊字符。

1. 使用Passay

Passay是非常强大的密码策略工具库,我们可以利用其生成符合特定规则的密码,读者可以参考上篇博文。通过使用CharacterData 缺省实现,可以规划密码规则,当然也能根据需要自定义CharacterData 。

public String generatePassayPassword() { 
    PasswordGenerator gen = new PasswordGenerator(); 
    CharacterData lowerCaseChars = EnglishCharacterData.LowerCase; 
    CharacterRule lowerCaseRule = new CharacterRule(lowerCaseChars); 
    lowerCaseRule.setNumberOfCharacters(2); 
  
    CharacterData upperCaseChars = EnglishCharacterData.UpperCase; 
    CharacterRule upperCaseRule = new CharacterRule(upperCaseChars); 
    upperCaseRule.setNumberOfCharacters(2); 
  
    CharacterData digitChars = EnglishCharacterData.Digit; 
    CharacterRule digitRule = new CharacterRule(digitChars); 
    digitRule.setNumberOfCharacters(2); 
  
    CharacterData specialChars = new CharacterData() { 
        public String getErrorCode() { 
            return ERROR_CODE; 
        } 
  
        public String getCharacters() { 
            return "!@#$%^&*()_+"; 
        } 
    }; 
    CharacterRule splCharRule = new CharacterRule(specialChars); 
    splCharRule.setNumberOfCharacters(2); 
  
    String password = gen.generatePassword(10, splCharRule, lowerCaseRule,  
      upperCaseRule, digitRule); 
    return password; 
} 

这里我们自定义了CharacterData指定特殊字符,可以现在一组有效特殊字符。另外对于其他规则利用缺省CharacterData实现。

下面通过单元测试检查生成的密码,示例中检查存在两个特殊字符。

@Test 
public void whenPasswordGeneratedUsingPassay_thenSuccessful() { 
    RandomPasswordGenerator passGen = new RandomPasswordGenerator(); 
    String password = passGen.generatePassayPassword(); 
    int specialCharCount = 0; 
    for (char c : password.toCharArray()) { 
        if (c >= 33 || c <= 47) { 
            specialCharCount++; 
        } 
    } 
    assertTrue("Password validation failed in Passay", specialCharCount >= 2); 
} 

值得注意的是,尽管Passay是开源的,但它同时获得了LGPL和Apache 2的双重许可。与任何第三方软件一样,当我们在产品中使用它时,我们必须确保遵守这些许可。LGPL许可不修改其源码情况下不受限制,一旦修改源码则必须也是LGPL许可。

2. 使用 RandomStringGenerator

接下来,我们看看Apache Commons Text库中的RandomStringGenerator 类。通过它能生成特定数量的Unicode字符串。增加依赖:

<dependency> 
    <groupId>org.apache.commons</groupId> 
    <artifactId>commons-text</artifactId> 
    <version>1.4</version> 
</dependency> 

通过使用RandomStringGenerator.Builder 创建生成器的实例,当然也可以进一步操作生成器的属性。利用构建器可以很容易修改其默认实现,而且也能定义允许的字符。

public String generateRandomSpecialCharacters(int length) { 
    RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(33, 45) 
        .build(); 
    return pwdGenerator.generate(length); 
} 

使用RandomStringGenerator的一个限制是缺少在每个集合中设定字符数的能力(Passay的类似功能)。但可以合并多个结果集变相实现。

 
    public String generateRandomSpecialCharacters(int length) { 
        RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(33, 45) 
                .build(); 
        return pwdGenerator.generate(length); 
    } 
 
    public String generateRandomNumbers(int length) { 
        RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(48, 57) 
                .build(); 
        return pwdGenerator.generate(length); 
    } 
 
    public String generateRandomAlphabet(int length, boolean lowerCase) { 
        int low; 
        int hi; 
        if (lowerCase) { 
            low = 97; 
            hi = 122; 
        } else { 
            low = 65; 
            hi = 90; 
        } 
        RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(low, hi) 
                .build(); 
        return pwdGenerator.generate(length); 
    } 
 
    public String generateRandomCharacters(int length) { 
        RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(48, 57) 
                .build(); 
        return pwdGenerator.generate(length); 
    } 
 
public String generateCommonTextPassword() { 
    String pwString = generateRandomSpecialCharacters(2).concat(generateRandomNumbers(2)) 
      .concat(generateRandomAlphabet(2, true)) 
      .concat(generateRandomAlphabet(2, false)) 
      .concat(generateRandomCharacters(2)); 
    List<Character> pwChars = pwString.chars() 
      .mapToObj(data -> (char) data) 
      .collect(Collectors.toList()); 
    Collections.shuffle(pwChars); 
    String password = pwChars.stream() 
      .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) 
      .toString(); 
    return password; 
} 

写个单元测试验证生成的密码:

@Test 
public void whenPasswordGeneratedUsingCommonsText_thenSuccessful() { 
    RandomPasswordGenerator passGen = new RandomPasswordGenerator(); 
    String password = passGen.generateCommonTextPassword(); 
    int lowerCaseCount = 0; 
    for (char c : password.toCharArray()) { 
        if (c >= 97 || c <= 122) { 
            lowerCaseCount++; 
        } 
    } 
    assertTrue("Password validation failed in commons-text ", lowerCaseCount >= 2); 
} 

RandomStringGenerator默认使用ThreadLocalRandom来实现随机性。需要指出的是,这并不能确保加密安全性。
但我们可以使用usingRandom(TextRandomProvider)设置随机性来源。例如,我们可以使用SecureTextRandomProvider进行加密安全:

public String generateRandomSpecialCharacters(int length) { 
    SecureTextRandomProvider stp = new SecureTextRandomProvider(); 
    RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder() 
      .withinRange(33, 45) 
      .usingRandom(stp) 
      .build(); 
    return pwdGenerator.generate(length); 
} 

3. 使用RandomStringUtils

另外也可以利用the Apache Commons Lang库的RandomStringUtils 类。它提供了几个static方法可以实现我们的需求。下面通过示例看如何提供字符范围:

public String generateCommonLangPassword() { 
    String upperCaseLetters = RandomStringUtils.random(2, 65, 90, true, true); 
    String lowerCaseLetters = RandomStringUtils.random(2, 97, 122, true, true); 
    String numbers = RandomStringUtils.randomNumeric(2); 
    String specialChar = RandomStringUtils.random(2, 33, 47, false, false); 
    String totalChars = RandomStringUtils.randomAlphanumeric(2); 
    String combinedChars = upperCaseLetters.concat(lowerCaseLetters) 
      .concat(numbers) 
      .concat(specialChar) 
      .concat(totalChars); 
    List<Character> pwdChars = combinedChars.chars() 
      .mapToObj(c -> (char) c) 
      .collect(Collectors.toList()); 
    Collections.shuffle(pwdChars); 
    String password = pwdChars.stream() 
      .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) 
      .toString(); 
    return password; 
} 

可以查看random方法的定义:

    public static String random(int count, int start, int end, boolean letters, boolean numbers) { 
        return random(count, start, end, letters, numbers, (char[])null, RANDOM); 
    } 

单元测试:

@Test 
public void whenPasswordGeneratedUsingCommonsLang3_thenSuccessful() { 
    RandomPasswordGenerator passGen = new RandomPasswordGenerator(); 
    String password = passGen.generateCommonsLang3Password(); 
    int numCount = 0; 
    for (char c : password.toCharArray()) { 
        if (c >= 48 || c <= 57) { 
            numCount++; 
        } 
    } 
    assertTrue("Password validation failed in commons-lang3", numCount >= 2); 
} 

这里,RandomStringUtils默认使用Random作为随机因子。但它也提供了一个方法可以让我们指定随机因子:

String lowerCaseLetters = RandomStringUtils.random(2, 97, 122, true, true, null, new SecureRandom()); 

现在可以使用SecureRandom的实例来确保加密安全性。但此功能不能扩展到库中的其他方法。顺便提一句,一般建议仅在简单用例中使用RandomStringUtils。

4. 自定义方法

我们也能利用SecureRandom 类创建自定义工具类实现。首先我们生成两个长度的特殊字符:

public Stream<Character> getRandomSpecialChars(int count) { 
    Random random = new SecureRandom(); 
    IntStream specialChars = random.ints(count, 33, 45); 
    return specialChars.mapToObj(data -> (char) data); 
} 
 
public Stream<Character> getRandomNumbers(int count) { 
    IntStream numbers = random.ints(count, 48, 57); 
    return numbers.mapToObj(data -> (char) data); 
} 
 
public Stream<Character> getRandomAlphabets(int count, boolean upperCase) { 
    IntStream characters = null; 
    if (upperCase) { 
        characters = random.ints(count, 65, 90); 
    } else { 
        characters = random.ints(count, 97, 122); 
    } 
    return characters.mapToObj(data -> (char) data); 
} 
 

需要提醒的是33和45表示Unicode 字符范围。限制针对上述需求生成多个流,接着合并结果生成密码:

 
public String generateSecureRandomPassword() { 
    Stream<Character> pwdStream = Stream.concat(getRandomNumbers(2),  
      Stream.concat(getRandomSpecialChars(2),  
      Stream.concat(getRandomAlphabets(2, true), getRandomAlphabets(4, false)))); 
    List<Character> charList = pwdStream.collect(Collectors.toList()); 
    Collections.shuffle(charList); 
    String password = charList.stream() 
        .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) 
        .toString(); 
    return password; 
} 

同样可以写单元测试进行验证,这里略过。

5. 总结

本文他们介绍了多种方法生成符合一定策略的密码。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/109430846
阅读延展