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