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 仓库 获取。