VBAで要件(ポリシー)に沿ったランダムパスワードを生成
VBAでのランダムパスワード生成なんて出尽くされていると思ったけれど、意外とスマートな実装がなかったので自分で書いてみた。
単純に使用可能な文字種全体からランダムに1文字ずつ選択して連結するだけでは、「英小文字、英大文字、数字を必ず1文字以上含む」などのポリシーに沿っていない文字列が生成されてしまう可能性があるが、 1回の処理で必ずポリシーに沿った文字列を生成する関数を作成した。
実装
とりあえず作ったものはこちら。
' パスワード用のランダム文字列を生成する
Public Function GeneratePassword(iLength As Integer, Optional bAllowLower As Boolean = True, Optional bAllowUpper As Boolean = True, _
Optional bAllowNumber As Boolean = True, Optional bAllowSymbol As Boolean = False, Optional bRequireLower As Boolean = True, _
Optional bRequireUpper As Boolean = True, Optional bRequireNumber As Boolean = True, Optional bRequireSymbol As Boolean = False, _
Optional sSymbols As String = "!#$%&()=^-|@[]{}:;,.") As String
Dim sPassword As String
Dim i As Long
Dim sTotalChars As String
Const S_LOWER As String = "abcdefghijklmnopqrstuvwxyz"
Const S_UPPER As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Const S_NUMBER As String = "0123456789"
' 使用可能文字種
If bAllowLower Then
sTotalChars = sTotalChars & S_LOWER
End If
If bAllowUpper Then
sTotalChars = sTotalChars & S_UPPER
End If
If bAllowNumber Then
sTotalChars = sTotalChars & S_NUMBER
End If
If bAllowSymbol Then
sTotalChars = sTotalChars & sSymbols
End If
' 任意の文字種が可能な部分を生成
For i = 1 To iLength - Abs(bRequireLower + bRequireUpper + bRequireNumber + bRequireSymbol)
sPassword = sPassword & Mid(sTotalChars, Int(Len(sTotalChars) * Rnd + 1), 1)
Next i
' 必須とされる文字種の文字を1文字ずつ挿入
If bRequireLower Then
i = Int((Len(sPassword) + 1) * Rnd + 1)
sPassword = Mid$(sPassword, 1, i - 1) & Mid(S_LOWER, Int(Len(S_LOWER) * Rnd + 1), 1) & Mid$(sPassword, i)
End If
If bRequireUpper Then
i = Int((Len(sPassword) + 1) * Rnd + 1)
sPassword = Mid$(sPassword, 1, i - 1) & Mid(S_UPPER, Int(Len(S_UPPER) * Rnd + 1), 1) & Mid$(sPassword, i)
End If
If bRequireNumber Then
i = Int((Len(sPassword) + 1) * Rnd + 1)
sPassword = Mid$(sPassword, 1, i - 1) & Mid(S_NUMBER, Int(Len(S_NUMBER) * Rnd + 1), 1) & Mid$(sPassword, i)
End If
If bRequireSymbol Then
i = Int((Len(sPassword) + 1) * Rnd + 1)
sPassword = Mid$(sPassword, 1, i - 1) & Mid(sSymbols, Int(Len(sSymbols) * Rnd + 1), 1) & Mid$(sPassword, i)
End If
GeneratePassword = sPassword
End Function
説明
各引数の説明は以下。
iLength
: 生成するパスワードの長さbAllowLower
: 省略可。英小文字を使用するか。デフォルトTrue
。bAllowUpper
: 省略可。英大文字を使用するか。デフォルトTrue
。bAllowNumber
: 省略可。数字を使用するか。デフォルトTrue
。bAllowSymbol
: 省略可。記号を使用するか。デフォルトFalse
。bRequireLower
: 省略可。英小文字を1文字以上使用することを要求するか。デフォルトTrue
。bRequireUpper
: 省略可。英大文字を1文字以上使用することを要求するか。デフォルトTrue
。bRequireNumber
: 省略可。数字を1文字以上使用することを要求するか。デフォルトTrue
。bRequireSymbol
: 省略可。記号を1文字以上使用することを要求するか。デフォルトFalse
。sSymbols
: 省略可。記号として使用する文字。デフォルト!#$%&()=^-|@[]{}:;,.
。
「英小文字、英大文字、数字をそれぞれ1文字以上含む」といったポリシーを満たすパスワード生成する単純なやり方としては、 全文字種から1文字ずつランダムに選択して結合し、その結果が各文字種が含まれているかをチェックして合格するまでやり直す、 という方法は考えられるが、あまりスマートとは言えない。
そこで必ず1度(正確には毎回同じ計算量)で要件に沿ったパスワードが生成できるよう、以下の手順で生成している。
例として「英小文字、英大文字、数字を必ず1文字以上含む、12文字のパスワード」を生成する場合、
- 12文字中の9文字については英小文字、英大文字、数字はそれぞれ0文字以上でよいので、単純に
[a-zA-Z0-9]
から1文字ずつランダムに取り出す方法が使って長さ9の文字列を生成。 - 必ず1文字以上含む文字種については、対象の文字種から1文字をランダムで選んで、それまでに生成した文字列のどこかに挿入する。この例では以下の処理を行う。
- 英小文字(
[a-z]
)から1つ選び、長さ9の文字列の先頭、末尾、および各文字間にある合計10個の挿入可能位置から1つを選んで挿入。 - 英大文字(
[A-Z]
)から1つ選び、長さ10の文字列の先頭、末尾、および各文字間にある合計11個の挿入可能位置から1つを選んで挿入。 - 数字(
[0-9]
)から1つ選び、長さ11の文字列の先頭、末尾、および各文字間にある合計12個の挿入可能位置から1つを選んで挿入。
- 英小文字(
という順序で生成すれば、必ず要件に沿ったパスワードを1回の処理で生成できる。
実行例
GeneratePassword(10)
: 英小文字、英大文字、数字を1文字以上含む、10文字。
B9ZJQVrseM
wYkkAE9cU0
feWc79QQ1S
jq1bu3ocVR
9MeCMCg6ls
7TAFCkC1zR
ZudA9RE6ZN
xv7xsXn3V3
vHZjXA9HeA
hbxE3k0ZOM
GeneratePassword(10, True, True, True, True, True, True, True, True)
: 英小文字、英大文字、数字に加えて記号を1文字以上含む、10文字。
b:t8r@KG^n
&Wp}vjT1bA
q(5u,o1[Kw
7W,^y2MUtb
eXO-NKb4)$
[3rW48O3.=
i73zhvZS)F
=#%8FpVEIQ
nS0troQ:G(
31.w.FDLM6
なお、引数のTrue
、False
の組み合わせチェックはしていないので悪しからず。
例えばbAllowSymbol
にFalse
、bRequireSymbol
にTrue
を指定した場合、必ず記号が1文字だけ含まれた文字列が生成される。