任何人都可以推荐一个在 Apache 或 LGPL 许可下可用的安全密码生成器吗?
7 回答
我不会太担心生成令人难以置信的强大的一次性密码。使密码变长,如果您限制密码的有效期,那么暴力破解应该不是问题。如果密码仅在 1 小时内有效,那么如果密码仍未使用,则不会有问题。在那个时间跨度内,不太可能有人会使用蛮力破解它。
同样重要的是,您只让一次性密码只工作一次。这样,如果密码被截获,用户将在一次性密码过期时注意到并采取适当的措施。
我会选择 Apache Commons RandomStringUtils并让密码为 10-15 个字母和数字字符。
...尽管这始终是您想成为多偏执的问题。这个解决方案对于常规的 Web 应用程序来说很好,但对于银行来说还不够好......
它在 .net 中,但转换起来应该很简单。对大多数人来说可能有点太多了,但这是我一直在我的应用程序中使用的实现方式。这是我前段时间找到的一个实现,并做了一些修改,我不记得原作者了,但我会快速搜索一下,看看我是否可以给他适当的信任。
public static string GenerateRandomString(int minLength, int maxLength, int minLCaseCount, int minUCaseCount, int minNumCount, int minSpecialCount)
{
char[] randomString;
const string LCaseChars = "abcdefgijkmnopqrstwxyz";
const string UCaseChars = "ABCDEFGHJKLMNPQRSTWXYZ";
const string NumericChars = "23456789";
const string SpecialChars = "*$-+?_&=!%{}/";
Hashtable charGroupsUsed = new Hashtable();
charGroupsUsed.Add("lcase", minLCaseCount);
charGroupsUsed.Add("ucase", minUCaseCount);
charGroupsUsed.Add("num", minNumCount);
charGroupsUsed.Add("special", minSpecialCount);
// Because we cannot use the default randomizer, which is based on the
// current time (it will produce the same "random" number within a
// second), we will use a random number generator to seed the
// randomizer.
// Use a 4-byte array to fill it with random bytes and convert it then
// to an integer value.
byte[] randomBytes = new byte[4];
// Generate 4 random bytes.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomBytes);
// Convert 4 bytes into a 32-bit integer value.
int seed = (randomBytes[0] & 0x7f) << 24 |
randomBytes[1] << 16 |
randomBytes[2] << 8 |
randomBytes[3];
// Create a randomizer from the seed.
Random random = new Random(seed);
// Allocate appropriate memory for the password.
if (minLength < maxLength)
{
randomString = new char[random.Next(minLength, maxLength + 1)];
}
else
{
randomString = new char[minLength];
}
int requiredCharactersLeft = minLCaseCount + minUCaseCount + minNumCount + minSpecialCount;
// Build the password.
for (int i = 0; i < randomString.Length; i++)
{
string selectableChars = "";
// if we still have plenty of characters left to acheive our minimum requirements.
if (requiredCharactersLeft < randomString.Length - i)
{
// choose from any group at random
selectableChars = LCaseChars + UCaseChars + NumericChars + SpecialChars;
}
else // we are out of wiggle room, choose from a random group that still needs to have a minimum required.
{
// choose only from a group that we need to satisfy a minimum for.
foreach (DictionaryEntry charGroup in charGroupsUsed)
{
if ((int)charGroup.Value > 0)
{
switch (charGroup.Key.ToString())
{
case "lcase":
selectableChars += LCaseChars;
break;
case "ucase":
selectableChars += UCaseChars;
break;
case "num":
selectableChars += NumericChars;
break;
case "special":
selectableChars += SpecialChars;
break;
}
}
}
}
// Now that the string is built, get the next random character.
char nextChar = selectableChars[random.Next(0, selectableChars.Length - 1)];
// Tac it onto our password.
randomString[i] = nextChar;
// Now figure out where it came from, and decrement the appropriate minimum value.
if (LCaseChars.Contains(nextChar))
{
charGroupsUsed["lcase"] = (int)charGroupsUsed["lcase"] - 1;
if ((int)charGroupsUsed["lcase"] >= 0)
{
requiredCharactersLeft--;
}
}
else if (UCaseChars.Contains(nextChar))
{
charGroupsUsed["ucase"] = (int)charGroupsUsed["ucase"] - 1;
if ((int)charGroupsUsed["ucase"] >= 0)
{
requiredCharactersLeft--;
}
}
else if (NumericChars.Contains(nextChar))
{
charGroupsUsed["num"] = (int)charGroupsUsed["num"] - 1;
if ((int)charGroupsUsed["num"] >= 0)
{
requiredCharactersLeft--;
}
}
else if (SpecialChars.Contains(nextChar))
{
charGroupsUsed["special"] = (int)charGroupsUsed["special"] - 1;
if ((int)charGroupsUsed["special"] >= 0)
{
requiredCharactersLeft--;
}
}
}
return new string(randomString);
}
编辑
我相信我是从 http://www.obviex.com/Samples/Password.aspx上发布的代码开始的。尽管代码现在具有更多功能。
这是一个使用 Commons 的示例。它创建一个长度在 8 到 20 个字符之间的字母数字密码。
public String getRandomPassword() {
StringBuffer password = new StringBuffer(20);
int next = RandomUtils.nextInt(13) + 8;
password.append(RandomStringUtils.randomAlphanumeric(next));
return password.toString();
}
更新 RandomUtils.nextInt 返回一个介于 0(含)和指定值(不含)之间的数字,因此要获得 8 到 20 个字符(含)之间的值,参数值应为 13。我已经更正了上面的代码。
更新 正如下面的评论中所指出的,这可以在不使用 StringBuffer 的情况下编写。这是修改后的单行版本:
return RandomStringUtils.randomAlphanumeric(RandomUtils.nextInt(13) + 8);
对于那些感兴趣的人,这里是 Matthew 的代码,转换为 Java
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class PasswordGenerator {
public static String GenerateRandomString(int minLength, int maxLength, int minLCaseCount, int minUCaseCount, int minNumCount, int minSpecialCount)
{
char[] randomString;
String LCaseChars = "abcdefgijkmnopqrstwxyz";
String UCaseChars = "ABCDEFGHJKLMNPQRSTWXYZ";
String NumericChars = "23456789";
String SpecialChars = "*$-+?_&=!%{}/";
Map<String,Integer> charGroupsUsed = new HashMap<String,Integer>();
charGroupsUsed.put("lcase", minLCaseCount);
charGroupsUsed.put("ucase", minUCaseCount);
charGroupsUsed.put("num", minNumCount);
charGroupsUsed.put("special", minSpecialCount);
// Because we cannot use the default randomizer, which is based on the
// current time (it will produce the same "random" number within a
// second), we will use a random number generator to seed the
// randomizer.
// Use a 4-byte array to fill it with random bytes and convert it then
// to an integer value.
byte[] randomBytes = new byte[4];
// Generate 4 random bytes.
new Random().nextBytes(randomBytes);
// Convert 4 bytes into a 32-bit integer value.
int seed = (randomBytes[0] & 0x7f) << 24 |
randomBytes[1] << 16 |
randomBytes[2] << 8 |
randomBytes[3];
// Create a randomizer from the seed.
Random random = new Random(seed);
// Allocate appropriate memory for the password.
int randomIndex = -1;
if (minLength < maxLength)
{
randomIndex = random.nextInt((maxLength-minLength)+1)+minLength;
randomString = new char[randomIndex];
}
else
{
randomString = new char[minLength];
}
int requiredCharactersLeft = minLCaseCount + minUCaseCount + minNumCount + minSpecialCount;
// Build the password.
for (int i = 0; i < randomString.length; i++)
{
String selectableChars = "";
// if we still have plenty of characters left to acheive our minimum requirements.
if (requiredCharactersLeft < randomString.length - i)
{
// choose from any group at random
selectableChars = LCaseChars + UCaseChars + NumericChars + SpecialChars;
}
else // we are out of wiggle room, choose from a random group that still needs to have a minimum required.
{
// choose only from a group that we need to satisfy a minimum for.
for(Map.Entry<String, Integer> charGroup : charGroupsUsed.entrySet())
{
if ((int)charGroup.getValue() > 0)
{
if("lcase".equals(charGroup.getKey()) ){
selectableChars += LCaseChars;
}
else if("ucase".equals(charGroup.getKey())){
selectableChars += UCaseChars;
}
else if("num".equals(charGroup.getKey())){
selectableChars += NumericChars;
}
else if("special".equals(charGroup.getKey())){
selectableChars += SpecialChars;
}
}
}
}
// Now that the string is built, get the next random character.
randomIndex = random.nextInt((selectableChars.length())-1);
char nextChar = selectableChars.charAt(randomIndex);
// Tac it onto our password.
randomString[i] = nextChar;
// Now figure out where it came from, and decrement the appropriate minimum value.
if (LCaseChars.indexOf(nextChar) > -1)
{
charGroupsUsed.put("lcase",charGroupsUsed.get("lcase") - 1);
if (charGroupsUsed.get("lcase") >= 0)
{
requiredCharactersLeft--;
}
}
else if (UCaseChars.indexOf(nextChar) > -1)
{
charGroupsUsed.put("ucase",charGroupsUsed.get("ucase") - 1);
if (charGroupsUsed.get("ucase") >= 0)
{
requiredCharactersLeft--;
}
}
else if (NumericChars.indexOf(nextChar) > -1)
{
charGroupsUsed.put("num", charGroupsUsed.get("num") - 1);
if (charGroupsUsed.get("num") >= 0)
{
requiredCharactersLeft--;
}
}
else if (SpecialChars.indexOf(nextChar) > -1)
{
charGroupsUsed.put("special",charGroupsUsed.get("special") - 1);
if (charGroupsUsed.get("special") >= 0)
{
requiredCharactersLeft--;
}
}
}
return new String(randomString);
}
}
和一个单元测试
import org.junit.Test;
public class PasswordGeneratorTest {
@Test
public void testPasswordCreation(){
System.out.println(PasswordGenerator.GenerateRandomString(8,25,3,1,1,1));
}
}
Password Safe是开源的(在艺术许可下),包括密码生成代码。
您可以使用 Random 和内置的 MessageDigest 实现轻松实现它。
import java.util.Random;
import java.security.*;
import java.math.*;
public class RandPassGen {
public static String genPass( int chars ) {
Random r = new Random();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch ( NoSuchAlgorithmException e ) {
System.out.println( "Unsupported Algorithm!" );
return null;
}
byte[] entropy = new byte[1024];
r.nextBytes(entropy);
md.update( entropy , 0, 1024 );
return new BigInteger(1, md.digest()).toString(16).substring(0, chars);
}
public static void main( String[] av ) {
Integer chars = Integer.valueOf(av[0]);
if ((chars < 0) || (chars > 32)) {
System.out.println( "Generate between 0 and 32 characters." );
return;
}
System.out.println( genPass( chars ) );
}
}
我添加了一个类似于 C#/Java 版本的 Golang 实现。它在 Apache 2.0 下可用。源代码位于:
https://github.com/deftlabs/dlshared/blob/master/password_utils.go