1. 简介

在本文中,我们将探讨如何在 Java 中生成一个安全的随机密码。

我们的示例将生成一个长度为 10 的密码,其中至少包含两个小写字母、两个大写字母、两个数字和两个特殊字符。

2. 使用 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 实现,这样可以控制允许使用的字符范围。

接下来,我们可以通过单元测试来验证生成的密码是否符合要求。例如,验证特殊字符的数量:

@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 双重许可。在使用时,务必确保遵守相关许可证条款。更多信息可以参考 GNU 官网关于 LGPL 和 Java 的说明

3. 使用 RandomStringGenerator

接下来我们来看看 Apache Commons Text 中的 RandomStringGenerator。通过它可以生成包含指定数量代码点的 Unicode 字符串。

我们可以通过 RandomStringGenerator.Builder 类来创建生成器实例,并进一步配置其属性:

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

⚠️ 但是 RandomStringGenerator 有一个限制:它不像 Passay 那样可以明确指定每种字符的数量。不过,我们可以通过合并多个字符集合的方式来绕过这个限制:

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

4. 使用 RandomStringUtils

另一个选择是使用 Apache Commons Lang 库中的 RandomStringUtils 类。它提供了一系列静态方法来处理字符串生成。

我们可以指定字符范围来生成密码:

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;
}

验证密码中的数字字符数量:

@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 来增强安全性。但要注意,Apache 官方建议仅在简单场景下使用 RandomStringUtils

5. 使用自定义工具方法

我们也可以直接使用 SecureRandom 类来自定义一个密码生成工具类。

比如生成两个特殊字符:

public Stream<Character> getRandomSpecialChars(int count) {
    Random random = new SecureRandom();
    IntStream specialChars = random.ints(count, 33, 45);
    return specialChars.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;
}

验证密码中的特殊字符数量:

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

✅ 这种方式简单粗暴,适合对加密强度有较高要求的场景。

6. 总结

本文展示了多种在 Java 中生成符合特定规则的安全密码的方式:

  • ✅ Passay:功能强大,适合复杂的密码策略管理
  • ⚠️ Apache Commons Text:灵活性高,但需要手动控制字符分布
  • ❌ Apache Commons Lang:适合简单场景,官方不推荐复杂用途
  • ✅ 自定义 SecureRandom:灵活可控,加密安全

所有代码示例均可在 GitHub 仓库 获取。


原始标题:Generate a Secure Random Password in Java