1. 序章

このチュートリアルでは、Javaで安全なランダムパスワードを生成するために使用できるさまざまな方法を見ていきます。

この例では、10文字のパスワードを生成します。各パスワードには、少なくとも2つの小文字、2つの大文字、2つの数字、および2つの特殊文字が含まれます。

2. 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のデフォルトの実装を利用しています。

それでは、ジェネレーターを単体テストと照合してみましょう。 たとえば、2つの特殊文字の存在を確認できます。

@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とApache2の両方でデュアルライセンスされていることに注意してください。 他のサードパーティソフトウェアと同様に、製品で使用する場合は、これらのライセンスに準拠する必要があります。 GNUのWebサイトには、LGPLおよびJavaに関する詳細情報があります。

3. RandomStringGeneratorを使用する

次に、 Apache CommonsTextRandomStringGeneratorを見てみましょう。 RandomStringGenerator、を使用すると、指定された数のコードポイントを含むUnicode文字列を生成できます。

次に、 RandomStringGenerator.Builder クラスを使用して、ジェネレーターのインスタンスを作成します。 もちろん、ジェネレータのプロパティをさらに操作することもできます。

ビルダーの助けを借りて、ランダム性のデフォルトの実装を簡単に変更できます。 さらに、文字列で許可される文字を定義することもできます。

public String generateRandomSpecialCharacters(int length) {
    RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(33, 45)
        .build();
    return pwdGenerator.generate(length);
}

現在、 RandomStringGenerator を使用する際の制限の1つは、Passayのように、各セットの文字数を指定する機能がないことです。ただし、複数のセット:

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);
}

4. RandomStringUtilsを使用する

使用できるもう1つのオプションは、 Apache Commons LangLibraryRandomStringUtilsクラスです。 このクラスは、問題ステートメントに使用できるいくつかの静的メソッドを公開します。

パスワードに受け入れられるコードポイントの範囲を提供する方法を見てみましょう。

 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;
}

生成されたパスワードを検証するために、数字の数を確認しましょう。

@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のインスタンスを使用して暗号化セキュリティを確保できました。 ただし、この機能をライブラリ内の他のメソッドに拡張することはできません。 ちなみに、 Apacheは、単純なユースケースでのみRandomStringUtilsの使用を推奨しています。

5. カスタムユーティリティメソッドの使用

SecureRandom クラスを使用して、シナリオのカスタムユーティリティクラスを作成することもできます。 手始めに、長さ2の特殊文字の文字列を生成しましょう。

public Stream<Character> getRandomSpecialChars(int count) {
    Random random = new SecureRandom();
    IntStream specialChars = random.ints(count, 33, 45);
    return specialChars.mapToObj(data -> (char) data);
}

また、3345は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;
}

次に、生成されたパスワードの特殊文字数を検証しましょう。

@Test
public void whenPasswordGeneratedUsingSecureRandom_thenSuccessful() {
    RandomPasswordGenerator passGen = new RandomPasswordGenerator();
    String password = passGen.generateSecureRandomPassword();
    int specialCharCount = 0;
    for (char c : password.toCharArray()) {
        if (c >= 33 || c <= 47) {
            specialCharCount++;
        }
    }
    assertTrue("Password validation failed in Secure Random", specialCharCount >= 2);
}

6. 結論

このチュートリアルでは、さまざまなライブラリを使用して、要件に準拠したパスワードを生成することができました。

いつものように、この記事で使用されているコードサンプルは、GitHubから入手できます。