🔐

【Java】条件に沿って記号英数字を含むランダムなパスワードを生成する

2024/08/06に公開

1. はじめに

今回はパスワードポリシーに沿って記号英数字を含むランダムなパスワードを生成してみたいと思います。

具体的な条件は以下の通りです。
こちらの記事を参考にパスワード生成でよく扱われる条件を追加してみました。

パスワードポリシー コード上での表現
大文字アルファベットを含むことを許可あるいは不許可とする uppercaseEnable = "true"/"false"
大文字アルファベットを必ず含む uppercaseEnable = "required"
小文字アルファベットを含むことを許可あるいは不許可とする lowercaseEnable = "true"/"false"
小文字アルファベットを必ず含む lowercaseEnable = "required"
数字を含むことを許可あるいは不許可とする numberEnable = "true"/"false"
数字を必ず含む numberEnable = "required"
記号を含むことを許可あるいは不許可とする symbolEnable = "true"/"false"
記号を必ず含む symbolEnable = "required"
連続した文字を許可あるいは不許可とする sameChrEnable = "true"/"false"

具体的には

  • 記号英数字全て必須(required)のパスワード:aGft@t0+
  • 記号を許可(true)・英数字を必須(required)の場合:ab7nIjss
    ※許可設定の文字種は無くてもよい

といったイメージです。

2.ソースコードと結果

先にソースコードとパスワードの出力例を紹介します。

■ CreatePassword.java

作成したソースコードです。

import java.util.Random;

public class CreatePassword {

	public static void main(String[] args) {
		String password;
		password = createPassword(8,"true","true","true","true", "true");
		System.out.println(password);
	}
	
	public static String createPassword(int passwordMinLength,
		String lowercaseEnable,
		String uppercaseEnable,
		String numberEnable,
		String symbolEnable,
		String sameChrEnable) {

	    StringBuilder passwordBase = new StringBuilder();
	    StringBuilder lowercaseBase = new StringBuilder("abcdefghijklmnopqrstuvwxyz");
	    StringBuilder uppercaseBase = new StringBuilder("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
	    StringBuilder numberBase = new StringBuilder("0123456789");
	    StringBuilder symbolBase = new StringBuilder("`˜!@#$%^&*()_+-={}[]|:;\"'<>,.?/");
	    StringBuilder password = new StringBuilder();
	     
	    
	    if (lowercaseEnable == "true"|| lowercaseEnable == "required") {
	        passwordBase.append(lowercaseBase);
	    }
	    if (uppercaseEnable == "true" || uppercaseEnable == "required") {
	        passwordBase.append(uppercaseBase);
	    }
	    if (numberEnable == "true" || numberEnable == "required") {
	        passwordBase.append(numberBase);
	    }
	    if (symbolEnable == "true" || symbolEnable == "required") {
	        passwordBase.append(symbolBase);
	    }
	     
	    // requiredが指定されている場合、必ず指定された文字種が含まれるようにする
	    if (lowercaseEnable == "required") {
	        password.append(lowercaseBase.charAt(new Random().nextInt(lowercaseBase.length())));
	    }
	    if (uppercaseEnable == "required") {
	        password.append(uppercaseBase.charAt(new Random().nextInt(uppercaseBase.length())));
	    }
	    if (numberEnable == "required") {
	        password.append(numberBase.charAt(new Random().nextInt(numberBase.length())));
	    }
	    if (symbolEnable == "required") {
	        password.append(symbolBase.charAt(new Random().nextInt(symbolBase.length())));
	    }
	 
	    char prevChar = '\0'; // 1つ前に生成された値を格納する変数
	    // passwordに文字列が格納されている場合、末尾の文字を格納する
	    if (password.length() != 0) {
	        prevChar = password.charAt(password.length() - 1) ;
	    }
	 
	    // パスワード生成
	    Random rand = new Random();
	    for (int i = password.length(); i < passwordMinLength; i++) {
	        int num = rand.nextInt(passwordBase.length());
	        char c = passwordBase.charAt(num);
	         
	        // 連続文字許可設定が有効でない場合、連続した文字が生成されないようにする
	        while (sameChrEnable != "true"  && c == prevChar) {
	            num = rand.nextInt(passwordBase.length());
	            c = passwordBase.charAt(num);
	        }
	 
	    password.append(c);
	    prevChar = c;
	    }
	 
	    return password.toString();
	}
}

■ 出力結果

今回は全文字種を必ず含むように生成してみました。

gH6<l-!x

3.解説

ソースコードの解説です。

■ パスワード生成で扱う文字の定義

まず、パスワード候補となる文字をStringBuilderクラスを使い定義します。

StringBuilder passwordBase = new StringBuilder();
StringBuilder lowercaseBase = new StringBuilder("abcdefghijklmnopqrstuvwxyz");
StringBuilder uppercaseBase = new StringBuilder("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
StringBuilder numberBase = new StringBuilder("0123456789");
StringBuilder symbolBase = new StringBuilder("`˜!@#$%^&*()_+-={}[]|:;\"'<>,.?/");
StringBuilder password = new StringBuilder();

■ 文字種の許可/不許可処理

各文字種の許可/不許可設定に基づいてpasswordBaseに使用する文字を挿入していきます。
passwordBaseは生成されるパスワード候補となる文字を格納するための変数です。

if (lowercaseEnable == "true"|| lowercaseEnable == "required") {
    passwordBase.append(lowercaseBase);
}
if (uppercaseEnable == "true" || uppercaseEnable == "required") {
    passwordBase.append(uppercaseBase);
}
if (numberEnable == "true" || numberEnable == "required") {
    passwordBase.append(numberBase);
}
if (symbolEnable == "true" || symbolEnable == "required") {
    passwordBase.append(symbolBase);
}

【具体例】小文字アルファベットと数字がTrueまたはreauiredの場合

passwordBaseは以下の文字列が格納されます。

passwordBase = "abcdefghijklmnopqrstuvwxyz0123456789"

■ 必ず特定の文字種を含める場合の処理

必ず特定文字種を含める場合(required指定)の処理です。

ここでは、(lowercase/uppercase/number/symbol)Baseに対してランダム関数で文字を抽出し、
生成パスワードとなるpasswordに直接特定文字種を挿入しています。

// requiredが指定されている場合、必ず指定された文字種が含まれるようにする
if (lowercaseEnable == "required") {
    password.append(lowercaseBase.charAt(new Random().nextInt(lowercaseBase.length())));
}
if (uppercaseEnable == "required") {
    password.append(uppercaseBase.charAt(new Random().nextInt(uppercaseBase.length())));
}
if (numberEnable == "required") {
    password.append(numberBase.charAt(new Random().nextInt(numberBase.length())));
}
if (symbolEnable == "required") {
    password.append(symbolBase.charAt(new Random().nextInt(symbolBase.length())));
}

【具体例】小文字アルファベットと数字がreauiredの場合、

  • lowercaseBase = "abcdefghijklmnopqrstuvwxyz"
  • numberBase = "0123456789"

上記からそれぞれランダムに文字を1文字ずつ抽出しpasswordに格納します。
結果として、passwordは以下のようになります。

password = "j6"

■ 連続文字許可/不許可設定のための事前準備

パスワード生成処理内で行われる連続した文字の許可/不許可処理のための事前準備をします。

連続する文字の比較を行うためにあらかじめ生成される文字の1つ前の文字prevCharを定義します。
また、requiredが指定され、既にpasswordに文字が挿入されている場合、最後尾の文字をprevCharに設定します。

char prevChar = '\0'; // 1つ前に生成された値を格納する変数
// passwordに文字列が格納されている場合、末尾の文字を格納する
if (password.length() != 0) {
    prevChar = password.charAt(password.length() - 1) ;
}

【具体例1】必須条件が無い場合

password = ""となり、prevChar = '\0'となります。

【具体例2】必須条件が有る場合

password = "j6"となり、prevChar = "6"となります。

■ パスワード生成

最後にパスワード生成部分です。

まず、生成候補文字列が格納されているpasswordBaseに対してランダム関数で文字の抽出を行います。
連続文字が許可されていない場合(sameChrEnable != "true")は、抽出した文字と1ステップ前の文字と比較し、同じだった場合、再度ランダム関数による文字の抽出を行います。

最終的に選ばれた、文字がpasswordに挿入されていきます。

Random rand = new Random();
for (int i = password.length(); i < passwordMinLength; i++) {
    int num = rand.nextInt(passwordBase.length());
    char c = passwordBase.charAt(num);
        
    // 連続文字許可設定が有効でない場合、連続した文字が生成されないようにする
    while (sameChrEnable != "true"  && c == prevChar) {
        num = rand.nextInt(passwordBase.length());
        c = passwordBase.charAt(num);
    }

password.append(c);
prevChar = c;
}

Discussion