Создание безопасного случайного пароля в Java с минимальными требованиями к специальным символам

Как создать случайный пароль, который соответствует требованиям длины и набора символов системы в Java?

Я должен создать случайный пароль длиной 10-14 символов и иметь по крайней мере один верхний регистр, один нижний регистр и один специальный символ. К сожалению, некоторые специальные символы слишком специальный и не может использоваться, поэтому я не могу использовать только напечатанный ASCII.

многие примеры на этом сайте генерируют случайный пароль или ключ сеанса без достаточной энтропии в персонажах или без реалистичных требований в деловой обстановке, подобной приведенным выше, поэтому я задаю более острый вопрос, чтобы получить лучший ответ.

мой набор символов, каждый специальный символ на стандартной клавиатуре США, за исключением космоса:

A-Z
a-z
0-9
~`!@#$%^&*()-_=+[{]}|;:'",<.>/?

3 ответов


Я предлагаю использовать Apache commons RandomStringUtils. Использовать то, что уже сделано.

String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\|;:\'\",<.>/?";
String pwd = RandomStringUtils.random( 15, characters );
System.out.println( pwd );

Если вы используете maven

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

в противном случае скачать jar

обновление Версия с безопасным случайным. Поэтому вопрос о необходимых символах остается и может быть решен как в комментарии, генерировать необходимые части отдельно и нормальные. Тогда присоединяйтесь к ним случайным образом.

char[] possibleCharacters = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\|;:\'\",<.>/?")).toCharArray();
String randomStr = RandomStringUtils.random( randomStrLength, 0, possibleCharacters.length-1, false, false, possibleCharacters, new SecureRandom() );
System.out.println( randomStr );

Я недавно узнал о Passay. Он обеспечивает необходимую функциональность, необходимую в его класс PasswordGenerator. Он случайным образом генерирует пароли, отвечающие требованиям, подобным тому, что написано ниже, используя CharacterRules, а не PasswordCharacterSets, как я сделал ниже. Вместо того, чтобы держать список неиспользуемых индексов для случайной вставки символов, он просто перетасовывает буфер символов после вставки символов, которые соответствуют требования.

ниже осталось от Раньше, я рекомендую использовать Passay если ваше лицензирование позволяет это, этот код должен работать иначе и предоставляет подробную информацию о том, почему сгенерированные пароли являются критографически сильными

Я закончил писать этот код дважды. Один раз получить случайный результат символа, но оказалось, что распределение символов зависит от размера набора символов(упс!). Я переписал ее, и теперь ты должна просто ... скопируйте / вставьте код и измените Main.Java для наборов символов, которые вы хотите. Хотя это можно было бы сделать по-другому, я думаю, что это относительно простой подход для получения правильного результата, и я призываю к повторному использованию, комментариям, критике и хорошо продуманным изменениям.

управление кодом PasswordGenerator выглядит следующим образом:

  • Минимальная / Максимальная Длина: установить с помощью случайных чисел!--24-->
  • PasswordCharacterSet: это предполагается, что все PasswordCharacterSets, переданные в PasswordGenerator, состоят из уникальных наборов символов, если нет, то случайные символы будут иметь перекос в сторону дубликатов.
  • PasswordCharacterSet Мин Символов: минимальные символы, используемые для этого набора символов.

главные биты для фактического поколения пароля:

  • случайность случайности: мы используем SecureRandom, который поддерживается криптостойкого ГПСЧ, а не случайный класс, который не.
  • случайный порядок символов для пароля: все индексы массива PW char добавляются в массив remainingIndexes. Когда мы вызываем addRandomCharacters, он удаляет индекс случайным образом, и мы используем удаленный индекс для заполнения массива.
  • случайные символы: в addRandomCharacters выбирается и добавляется случайный индекс из индекса символов, который мы используем массив pw.
  • гарантируя минимальные характеры каждого типа установлены: мы просто вырезать минимальное количество символов в первую очередь. Мы выбираем минимальное количество случайных значений из каждого набора символов, а затем двигаться дальше.
  • случайное распределение для оставшихся символов: после установки минимальных значений мы хотим сделать остальные символы случайными для всех наборов символов. Все символы добавляются в один матрица. Остальные слоты заполняются с использованием той же стратегии для предыдущих наборов символов.

описание сложности пароля: сложность пароля обычно обсуждается в битах энтропии. Вот количество возможностей для вашего keyspace:

существует по крайней мере один заглавный Альфа-символ (из 26), один строчный Альфа-символ(из 26), одна цифра (из 10) и один специальный символ (из 32), как вы вычисляете количество возможности-это количество возможностей для каждого символа, умноженное на количество символов, поскольку они случайным образом помещаются в строку. Таким образом, мы знаем, что возможности для четырех символов:

Required Characters = 26*26*10*32=216,320

все остальные персонажи 94 (26+26+10+32) возможности каждого

наш расчет:

Characters  Possibilities                                       Bits of Entropy
10 chars    216,320*94^6  = 149,232,631,038,033,920             ~2^57
11 chars    216,320*94^7  = 14,027,867,317,575,188,480          ~2^63
12 chars    216,320*94^8  = 1,318,619,527,852,067,717,120       ~2^70
13 chars    216,320*94^9  = 123,950,235,618,094,365,409,280     ~2^76
14 chars    216,320*94^10 = 11,651,322,148,100,870,348,472,320  ~2^83

С этим умом, если вы хотите самые безопасные пароли, вы всегда должны выбрать максимально возможное количество символов, которые в данном случае-14.

Main.Ява

package org.redtown.pw;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import org.redtown.pw.PasswordGenerator.PasswordCharacterSet;

public class Main {

    public static void main(String[] args) {
        Set<PasswordCharacterSet> values = new HashSet<PasswordCharacterSet>(EnumSet.allOf(SummerCharacterSets.class));
        PasswordGenerator pwGenerator = new PasswordGenerator(values, 10, 14);
        for(int i=0; i < 10; ++i) {
            System.out.println(pwGenerator.generatePassword());
        }
    }

    private static final char[] ALPHA_UPPER_CHARACTERS = { 'A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
    private static final char[] ALPHA_LOWER_CHARACTERS = { 'a', 'b', 'c', 'd',
            'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
    private static final char[] NUMERIC_CHARACTERS = { '0', '1', '2', '3', '4',
            '5', '6', '7', '8', '9' };
    private static final char[] SPECIAL_CHARACTERS = { '~', '`', '!', '@', '#',
            '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', '{',
            ']', '}', '\', '|', ';', ':', '\'', '"', ',', '<', '.', '>', '/',
            '?' };

    private enum SummerCharacterSets implements PasswordCharacterSet {
        ALPHA_UPPER(ALPHA_UPPER_CHARACTERS, 1),
        ALPHA_LOWER(ALPHA_LOWER_CHARACTERS, 1),
        NUMERIC(NUMERIC_CHARACTERS, 1),
        SPECIAL(SPECIAL_CHARACTERS, 1);

        private final char[] chars;
        private final int minUsage;

        private SummerCharacterSets(char[] chars, int minUsage) {
            this.chars = chars;
            this.minUsage = minUsage;
        }

        @Override
        public char[] getCharacters() {
            return chars;
        }

        @Override
        public int getMinCharacters() {
            return minUsage;
        }
    }
}

PasswordGenerator.Ява

package org.redtown.pw;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class PasswordGenerator {

    private final List<PasswordCharacterSet> pwSets;
    private final char[] allCharacters;
    private final int minLength;
    private final int maxLength;
    private final int presetCharacterCount;

    public PasswordGenerator(Collection<PasswordCharacterSet> origPwSets, int minLength, int maxLength) {
        this.minLength = minLength;
        this.maxLength = maxLength;

        // Make a copy of the character arrays and min-values so they cannot be changed after initialization
        int pwCharacters = 0;
        int preallocatedCharacters = 0;
        List<PasswordCharacterSet> pwSets = new ArrayList<PasswordCharacterSet>(origPwSets.size());
        for(PasswordCharacterSet origpwSet : origPwSets) {
            PasswordCharacterSet newPwSet = new PwSet(origpwSet);
            pwSets.add(newPwSet);
            pwCharacters += newPwSet.getCharacters().length;
            preallocatedCharacters += newPwSet.getMinCharacters();
        }
        this.presetCharacterCount = preallocatedCharacters;
        this.pwSets = Collections.unmodifiableList(pwSets);

        if (minLength < presetCharacterCount) {
            throw new IllegalArgumentException("Combined minimum lengths "
                    + presetCharacterCount
                    + " are greater than the minLength of " + minLength);
        }

        // Copy all characters into single array so we can evenly access all members when accessing this array
        char[] allChars = new char[pwCharacters];
        int currentIndex = 0;
        for(PasswordCharacterSet pwSet : pwSets) {
            char[] chars = pwSet.getCharacters();
            System.arraycopy(chars, 0, allChars, currentIndex, chars.length);
            currentIndex += chars.length;
        }
        this.allCharacters = allChars;
    }


    public char[] generatePassword() {
        SecureRandom rand = new SecureRandom();

        // Set pw length to minLength <= pwLength <= maxLength
        int pwLength = minLength + rand.nextInt(maxLength - minLength + 1);
        int randomCharacterCount = pwLength - presetCharacterCount;


        // Place each index in an array then remove them randomly to assign positions in the pw array
        List<Integer> remainingIndexes = new ArrayList<Integer>(pwLength);
        for(int i=0; i < pwLength; ++i) {
            remainingIndexes.add(i);
        }

        // Fill pw array
        char[] pw = new char[pwLength];
        for(PasswordCharacterSet pwSet : pwSets) {
            addRandomCharacters(pw, pwSet.getCharacters(), pwSet.getMinCharacters(), remainingIndexes, rand);
        }
        addRandomCharacters(pw, allCharacters, randomCharacterCount, remainingIndexes, rand);
        return pw;
    }

    private static void addRandomCharacters(char[] pw, char[] characterSet,
            int numCharacters, List<Integer> remainingIndexes, Random rand) {
        for(int i=0; i < numCharacters; ++i) {
            // Get and remove random index from the remaining indexes
            int pwIndex = remainingIndexes.remove(rand.nextInt(remainingIndexes.size()));

            // Set random character from character index to pwIndex
            int randCharIndex = rand.nextInt(characterSet.length);
            pw[pwIndex] = characterSet[randCharIndex];
        }
    }

    public static interface PasswordCharacterSet {
        char[] getCharacters();
        int getMinCharacters();
    }

    /**
     * Defensive copy of a passed-in PasswordCharacterSet
     */
    private static final class PwSet implements PasswordCharacterSet {
        private final char[] chars;
        private final int minChars;

        public PwSet(PasswordCharacterSet pwSet) {
            this.minChars = pwSet.getMinCharacters();
            char[] pwSetChars = pwSet.getCharacters();
            // Defensive copy
            this.chars = Arrays.copyOf(pwSetChars, pwSetChars.length);
        }

        @Override
        public char[] getCharacters() {
            return chars;
        }

        @Override
        public int getMinCharacters() {
            return minChars;
        }
    }
}

вот утилита, которая использует только vanilla Java и реализует требования. Он в основном получает один из необходимых наборов символов. Затем заполняет остальные случайные символы из всего набора. Затем перетасовывает все это.

public class PasswordUtils {

    static char[] SYMBOLS = (new String("^$*.[]{}()?-\"!@#%&/\,><':;|_~`")).toCharArray();
    static char[] LOWERCASE = (new String("abcdefghijklmnopqrstuvwxyz")).toCharArray();
    static char[] UPPERCASE = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).toCharArray();
    static char[] NUMBERS = (new String("0123456789")).toCharArray();
    static char[] ALL_CHARS = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789^$*.[]{}()?-\"!@#%&/\,><':;|_~`")).toCharArray();
    static Random rand = new SecureRandom();

    public static String getPassword(int length) {
        assert length >= 4;
        char[] password = new char[length];

        //get the requirements out of the way
        password[0] = LOWERCASE[rand.nextInt(LOWERCASE.length)];
        password[1] = UPPERCASE[rand.nextInt(UPPERCASE.length)];
        password[2] = NUMBERS[rand.nextInt(NUMBERS.length)];
        password[3] = SYMBOLS[rand.nextInt(SYMBOLS.length)];

        //populate rest of the password with random chars
        for (int i = 4; i < length; i++) {
            password[i] = ALL_CHARS[rand.nextInt(ALL_CHARS.length)];
        }

        //shuffle it up
        for (int i = 0; i < password.length; i++) {
            int randomPosition = rand.nextInt(password.length);
            char temp = password[i];
            password[i] = password[randomPosition];
            password[randomPosition] = temp;
        }

        return new String(password);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(getPassword(8));
        }
    }
}