Нужна рекомендация безопасного генератора паролей

Кто-нибудь может порекомендовать безопасный генератор паролей, доступный под лицензией Apache или LGPL для Java?

7 ответов

Решение

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

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

Я бы выбрал Apache Commons RandomStringUtils и пусть пароль будет состоять из 10-15 символов букв и цифр.

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

Это в.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();
}

UPDATE RandomUtils.nextInt возвращает число от 0 (включительно) до указанного значения (исключая), поэтому, чтобы получить значение от 8 до 20 символов включительно, значение аргумента должно быть 13. Я исправил код выше.

ОБНОВЛЕНИЕ Как отмечено в комментарии ниже, это может быть написано без использования StringBuffer. Вот модифицированная однострочная версия:

return RandomStringUtils.randomAlphanumeric(RandomUtils.nextInt(13) + 8);

Для тех, кто заинтересован, вот код Мэтью, преобразованный в 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 имеет открытый исходный код (по лицензии Artistic) и включает в себя код генерации пароля.

Я добавил реализацию Golang, которая похожа на версии C#/Java. Он доступен под Apache 2.0. Источник находится здесь:

https://github.com/deftlabs/dlshared/blob/master/password_utils.go

Вы можете легко реализовать это, используя 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 ) ); 
    }
}
Другие вопросы по тегам