在 java 中生成有很多限制的随机密码

generating random password with lot of restriction in java

我想创建一个密码生成器,根据用户设置的限制创建密码。限制是:

  1. 最小密码长度
  2. 最大密码长度
  3. 最小字母和数字
  4. 最小字母
  5. 最小大写字母
  6. 最小小写字母
  7. 最小数字
  8. 最大重复字符数

我查看了google,大部分示例代码都不符合我需要的要求。所以我即兴创作了这样的代码:

private char[] GeneratePassword(int minLength, int maxLength,
        int maxRepeatCharacter, int minLetterAndDigit, int minLetter,
        int minLowerCaseLetter, int minUpperCaseLetter, int minDigit) {

    final String LETTER = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
    final String DIGIT = "0123456789";
    final String[] randomSelector = {LETTER,UPPERCASE,LOWERCASE,DIGIT};

    int len = getRandomNumber(minLength, maxLength);
    char[] passwordGenerated = new char[len];
    char[] characterUsed = new char[len];
    int selection;
    int letterAndDigitUsed = 0;
    int letterUsed = 0;
    int lowerCaseLetterUsed = 0;
    int upperCaseLetterUsed = 0;
    int digitUsed = 0;
    int index = 0;

    if (minLength > maxLength) {
      // throw new IllegalArgumentException("Min.Length > Max.Length!");
    }

    if (minLetter + minDigit > minLetterAndDigit) {
      // throw new IllegalArgumentException("Error!");
    }

    while (index != len) {
        selection = getRandomNumber(0, randomSelector.length);
        if (selection == 0) {
            passwordGenerated[index] = LETTER.charAt(RandomUtils.nextInt(0,
                    LETTER.length()));
            if (checkRepeatCharacter(passwordGenerated[index],
                    characterUsed, index, maxRepeatCharacter) == false) {
                characterUsed[index] = passwordGenerated[index];
                index++;
                letterUsed++;
                letterAndDigitUsed++;
                break;
            }
        } else if (selection == 1) {
            passwordGenerated[index] = UPPERCASE.charAt(RandomUtils
                    .nextInt(0, UPPERCASE.length()));
            if (checkRepeatCharacter(passwordGenerated[index],
                    characterUsed, index, maxRepeatCharacter) == false) {
                characterUsed[index] = passwordGenerated[index];
                index++;
                upperCaseLetterUsed++;
                letterAndDigitUsed++;
                break;
            }
        } else if (selection == 2) {
            passwordGenerated[index] = LOWERCASE.charAt(RandomUtils
                    .nextInt(0, LOWERCASE.length()));
            if (checkRepeatCharacter(passwordGenerated[index],
                    characterUsed, index, maxRepeatCharacter) == false) {
                characterUsed[index] = passwordGenerated[index];
                index++;
                lowerCaseLetterUsed++;
                letterAndDigitUsed++;
                break;
            }
        } else if (selection == 3) {
            passwordGenerated[index] = DIGIT.charAt(RandomUtils.nextInt(0,
                    DIGIT.length()));
            if (checkRepeatCharacter(passwordGenerated[index],
                    characterUsed, index, maxRepeatCharacter) == false) {
                characterUsed[index] = passwordGenerated[index];
                index++;
                digitUsed++;
                letterAndDigitUsed++;
                break;
            }
        }
    }

    return passwordGenerated;
}

private boolean checkRepeatCharacter(char passwordGenerated,
        char[] passwordUsed, int index, int maxRepeatCharacter) {
    int characterRepeated = 0;
    for (int i = 0; i < index; i++) {
        if (String.valueOf(passwordUsed[i]).equals(
                String.valueOf(passwordGenerated))) {
            characterRepeated++;
            if (characterRepeated == maxRepeatCharacter) {
                return true;
            }
        }
    }
    return false;
}

private int getRandomNumber(int minLength, int maxLength) {
    Random r = new Random();
    return r.nextInt(maxLength - minLength) + minLength;
}

我遇到的问题是如何确保满足最低条件。同时,我不希望我的密码是通过重复相同类型的字符来生成的。

  1. 示例:如果我将最大密码长度设置为 10,并且我希望最小数字为 5。我更喜欢 1jP2k3o4m9 而不是 57812aJ9tP

  2. 第二个示例:如果我将最大密码长度设置为 5,并且我希望最小小写字母为 3。我更喜欢 Pj9mn 而不是 jkl5V

如您所见,第二次生成的密码试图先满足最低要求,然后才随机选择其他字符类型。这将使密码更容易受到攻击。这个算法有什么办法吗

参考:Generating a Random Password with Restrictions in Java

我会看一下类似 vt-password 的内容:

https://code.google.com/p/vt-middleware/wiki/vtpassword

除了基于一些相当标准的必需特征提供灵活的密码验证机制外,它还支持生成满足 "character rules" 列表定义的条件的密码:

https://code.google.com/p/vt-middleware/wiki/vtpassword#Generating_passwords

值得注意的是,该项目已经从孵化阶段毕业,现在被称为 Passay - http://www.passay.org/

当前的 password generator JavaDocs 不反映与 vt-password 可用的同一组字符规则。您可以 运行 暂时使用 vt-password,或者提供您自己的 CharacterRule 实现来与 Passay 生成器(+ 验证器,如果还需要的话)一起工作。

我已经修复了我的代码。到目前为止,我已经测试过,我可以按照要求完美地工作。我留下这段代码以防将来有人需要它。

private boolean checkRepeatCharacter(char randomCharacter,
        ArrayList<Character> passwordUsed, int usedLength,
        int maxRepeatCharacter) {
    int characterRepeated = 0;
    for (int i = 0; i < usedLength; i++) {
        if (String.valueOf(passwordUsed.get(i)).equals(
                String.valueOf(randomCharacter))) {
            characterRepeated++;
            if (characterRepeated == maxRepeatCharacter) {
                return false;
            }
        }
    }
    return true;
}

private boolean checkUsedIndex(int index, ArrayList<Integer> usedIndex) {
    for (int i = 0; i < usedIndex.size(); i++) {
        if (usedIndex.contains(index)) {
            return false;
        }
    }
    return true;
}

private int getRandomNumber(int minLength, int maxLength) {
    Random r = new Random();
    return r.nextInt(maxLength - minLength) + minLength;
}

public String generatePassword(int minLength, int maxLength,
        int maxRepeatCharacter, int minLetterAndDigit, int minLetter,
        int minLowerCaseLetter, int minUpperCaseLetter, int minDigit,
        int minSpecialCharacter, String specialCharacter) {

    final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
    final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    final String DIGIT = "0123456789";
    StringBuilder ALL = new StringBuilder();
    ALL.append(LOWERCASE);
    ALL.append(UPPERCASE);
    ALL.append(DIGIT);
    ALL.append(specialCharacter);
    ALL.toString();

    char getRandom;
    int length = 0;
    StringBuilder passwordGenerated = new StringBuilder();
    ArrayList<Character> characterUsed = new ArrayList<Character>();
    ArrayList<Integer> indexUsed = new ArrayList<Integer>();

    int passwordLength = 0;
    int lowerCaseLetterUsed = 0;
    int upperCaseLetterUsed = 0;
    int letterUsed = 0;
    int digitUsed = 0;
    int letterAndDigitUsed = 0;
    int specialCharacterUsed = 0;

    if (minLength > maxLength) {
        throw new IllegalArgumentException("Min. Length > Max. Length!");
    }

    if (minUpperCaseLetter + minLowerCaseLetter > minLetter) {
        throw new RuntimeException(
                "mininimum Lower Case + Minimum Uppercase cannot exceed minLetter");
    }
    if (minLetter + minDigit > minLetterAndDigit) {
        throw new RuntimeException(
                "mininimum Letter + Minimum Digit cannot exceed minimum Letter And Digit");
    }
    if (minLetter + minDigit + minSpecialCharacter > maxLength) {
        throw new RuntimeException(
                "minimum Digit + minimum Letter + Minimum Special Character cannot excced maximum Length");
    }

    while ((length < minLetter) && (length < minLetterAndDigit)) {
        length = getRandomNumber(minLength, maxLength);
    }

    while (passwordLength != length) {
        while (letterAndDigitUsed < minLetterAndDigit) {
            while (letterUsed < minLetter) {
                lowerCaseLetterUsed = 0;
                for (int i = 0; lowerCaseLetterUsed < minLowerCaseLetter; i++) {
                    int index = getRandomNumber(0, length);
                    if (checkUsedIndex(index, indexUsed) == true) {
                        getRandom = LOWERCASE.charAt(getRandomNumber(0,
                                LOWERCASE.length()));
                        if (checkRepeatCharacter(getRandom, characterUsed,
                                characterUsed.size(), maxRepeatCharacter) == true) {
                            passwordGenerated.append(getRandom);
                            characterUsed.add(getRandom);
                            indexUsed.add(index);
                            lowerCaseLetterUsed++;
                            letterUsed++;
                            letterAndDigitUsed++;
                            passwordLength++;
                            if (letterUsed == minLetter) {
                                break;
                            }
                        }
                    }
                }
                if (letterAndDigitUsed == minLetterAndDigit) {
                    break;
                }
                upperCaseLetterUsed = 0;
                for (int i = 0; upperCaseLetterUsed < minUpperCaseLetter; i++) {
                    int index = getRandomNumber(0, length);
                    if (checkUsedIndex(index, indexUsed) == true) {
                        getRandom = UPPERCASE.charAt(getRandomNumber(0,
                                UPPERCASE.length()));
                        if (checkRepeatCharacter(getRandom, characterUsed,
                                characterUsed.size(), maxRepeatCharacter) == true) {
                            passwordGenerated.append(getRandom);
                            characterUsed.add(getRandom);
                            indexUsed.add(index);
                            lowerCaseLetterUsed++;
                            letterUsed++;
                            letterAndDigitUsed++;
                            passwordLength++;
                            if (letterUsed == minLetter) {
                                break;
                            }
                        }
                    }
                }
                if (letterAndDigitUsed == minLetterAndDigit) {
                    break;
                }
            }
            for (int i = 0; digitUsed < minDigit; i++) {
                int index = getRandomNumber(0, length);
                if (checkUsedIndex(index, indexUsed) == true) {
                    getRandom = DIGIT.charAt(getRandomNumber(0,
                            DIGIT.length()));
                    if (checkRepeatCharacter(getRandom, characterUsed,
                            characterUsed.size(), maxRepeatCharacter) == true) {
                        passwordGenerated.append(getRandom);
                        characterUsed.add(getRandom);
                        indexUsed.add(index);
                        digitUsed++;
                        letterAndDigitUsed++;
                        passwordLength++;
                    }
                }
            }
        }
        for (int i = 0; specialCharacterUsed < minSpecialCharacter; i++) {
            if (checkUsedIndex(i, indexUsed) == true) {
                getRandom = specialCharacter.charAt(getRandomNumber(0,
                        specialCharacter.length()));
                if (checkRepeatCharacter(getRandom, characterUsed,
                        characterUsed.size(), maxRepeatCharacter) == true) {
                    passwordGenerated.append(getRandom);
                    characterUsed.add(getRandom);
                    indexUsed.add(i);
                    specialCharacterUsed++;
                    passwordLength++;
                }
            }
        }
        for (int i = 0; i < length; i++) {
            if (checkUsedIndex(i, indexUsed) == true) {
                getRandom = ALL.charAt(getRandomNumber(0, ALL.length()));
                if (checkRepeatCharacter(getRandom, characterUsed,
                        characterUsed.size(), maxRepeatCharacter) == true) {
                    passwordGenerated.append(getRandom);
                    characterUsed.add(getRandom);
                    indexUsed.add(i);
                    passwordLength++;
                }
            }
        }
    }
    return passwordGenerated.toString();
}