t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBAでランダムかつセキュリティポリシーを満たす8桁のパスワードを生成する

今回はパスワードを生成するコードを紹介する。
過去すでに書いた気がしてたのだけど、↓と勘違いしてた。
thom.hateblo.jp

簡易版のプログラム

さて、ただランダムなパスワードを作るだけなら簡単だ。
さっそくコードを書いてみた。

Sub ランダムパスワード簡易版()
    Dim i As Long
    For i = 1 To 8
        Debug.Print Chr(WorksheetFunction.RandBetween(33, 126));
    Next
    Debug.Print
End Sub

解説

コンピューター上で文字はコードで管理されており、特にアルファベット等の半角文字はASCIIコードと呼ばれるコードに含まれている。
ASCIIコードは0~127まであるが、デリートやタブなどの制御文字なども含まれるので、パスワードに使えそうな文字は33~126だ。
ASCII文字コード - IT用語辞典

そこで、ワークシート関数のRandBetweenで33~126までの数値をランダムに生成する。
あとはVBAのChr関数で文字コードを文字に変換して1文字分できあがり、これを8回繰り返すと8桁のパスワードになる。

ちなみにDebug.Printはセミコロンを最後に置くと改行せずに続けて出力することができる。
コードの最後にDebug.Printだけ入力しているのは、ここで改行だけ出力したい為だ。

参考
thom.hateblo.jp

出力されるパスワードにはアルファベットや数値以外にも特殊記号が含まれてしまうが、最近は特殊記号も含めなさいというポリシーが多いのでちょうどいい。

さて、このコードは簡易版なので、使えそうなパスワードが出るかどうかは運任せになる。
試しに8回実行してみた結果がこちら。
f:id:t-hom:20171129203251p:plain

最後の2つは使えそうだけれど、他のは特殊記号が多すぎたり、数値を含まなかったりとあまり宜しくない。
完全にランダムなので、セキュリティポリシーを満たさない場合もある。

確実にポリシーを満たすパスワードを生成する

最近はパスワードポリシーをアルファベット大文字・小文字・数字・特殊記号のすべてを含む8桁以上としているところが多いと思う。
そして私が利用するシステムによってはなぜか最初と最後の文字はアルファベットでなければならないという仕様がある。

また、文字種の少ない数字が多すぎると脆弱なイメージがあるし、特殊記号が多すぎると面倒くさい。数字と特殊記号は1つずつにしたい。
更に、他人に伝える初期パスワードの発行用途ではゼロとオーの違いなど、紛らわしい文字を回避したいという要件も出てくるだろう。

今回はこれらの複雑な要望を満たすパスワードを一発で生成できるプログラムを作る。

さて、今回は乱数を何度も利用するので、WorksheetFunctionではなく、VBAでRandBetween関数を作っておこう。

Function RandBetween(lower_bound, upper_bound)
    Call Randomize
    RandBetween = Int((upper_bound - lower_bound + 1) * Rnd + lower_bound)
End Function

次に本体を書き始める。
まずは文字種の定義を作りたいので、以下のような定数を準備する。

Function ランダムパスワード生成()
    Const UPPER_CASE = ""
    Const LOWER_CASE = ""
    Const NUMBERS = ""
    Const SYMBOLS = ""
End Function

定数の中身は手打ちだと面倒なので以下のコードで全文字種を出力して、切り取り&貼り付けする。

Sub hoge()
    For i = 33 To 126
        Debug.Print Chr(i);
    Next
    Debug.Print
End Sub

f:id:t-hom:20171129210315p:plain

一旦文字種の定義が完成。ダブルクォーテーションはそのままだとエラーになるのでエスケープしても良いが、今回は単に削った。

Function ランダムパスワード生成()
    Const UPPER_CASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    Const LOWER_CASE = "abcdefghijklmnopqrstuvwxyz"
    Const NUMBERS = "0123456789"
    Const SYMBOLS = "!#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
End Function

ここから紛らわしい文字を削っていく。

  • 見た目が紛らわしい文字「0Oij1lI|」を削除
  • 発音が間際らしい文字「bdBDQ9qGJgj」を削除
  • 視認性が悪い文字「',.:;`」を削除

その他、パスワードを電話や口頭で伝える必要がある場合は伝わりにくい文字を適宜削除すると良い。
また、発行パスワードが自分専用なら全て残しても問題ない。

できたのがこちら。

Function ランダムパスワード生成()
    Const UPPER_CASE = "ACEFHKLMNPRSTUVWXYZ"
    Const LOWER_CASE = "acefhkmnoprstuvwxyz"
    Const NUMBERS = "2345678"
    Const SYMBOLS = "!#$%&()*+-/<=>?@[\]^_{}~"
End Function

次にランダムに文字を選択する関数が欲しいので作成。

Function RandomCharPicker(source)
    Dim location: location = RandBetween(1, Len(source))
    RandomCharPicker = Mid(source, location, 1)
End Function

このRandomCharPickerに文字列を渡すとその中から1文字をランダムに返す。

この関数を使って、とりあえず全文字種を1つずつ出力してみた。

Function ランダムパスワード生成()
    Const UPPER_CASE = "ACEFHKLMNPRSTUVWXYZ"
    Const LOWER_CASE = "acefhkmnoprstuvwxyz"
    Const NUMBERS = "2345678"
    Const SYMBOLS = "!#$%&()*+-/<=>?@[\]^_{}~"
    Dim password
    password = password & RandomCharPicker(UPPER_CASE)
    password = password & RandomCharPicker(LOWER_CASE)
    password = password & RandomCharPicker(NUMBERS)
    password = password & RandomCharPicker(SYMBOLS)
    Debug.Print password
End Function

ただこれだと1桁目が大文字、2桁目が小文字という風に規則性ができてしまうので強度が落ちてしまう。
そこで、文字列をシャッフルする関数を作成した。

Function ShuffleString(source)
    Dim c As Collection: Set c = New Collection
    Dim i As Long
    
    'まず1文字ずつコレクションに格納していく
    For i = 1 To Len(source)
        c.Add Mid(source, i, 1)
    Next
    
    'コレクションが空になるまで、ランダムに取り出す。
    Dim ret As String
    Dim location As Long
    Do While c.Count > 0
        location = RandBetween(1, c.Count)
        ret = ret & c(location)
        c.Remove location
    Loop
    ShuffleString = ret
End Function

この関数に文字列を渡すと、順番をバラバラにして返してくれる。

これを使って、メインコードを完成させる。

Function ランダムパスワード生成()
    Const UPPER_CASE = "ACEFHKLMNPRSTUVWXYZ"
    Const LOWER_CASE = "acefhkmnoprstuvwxyz"
    Const NUMBERS = "2345678"
    Const SYMBOLS = "!#$%&()*+-/<=>?@[\]^_{}~"
    Dim password
    
    '最初に全文字種を含める。
    password = password & RandomCharPicker(UPPER_CASE)
    password = password & RandomCharPicker(LOWER_CASE)
    password = password & RandomCharPicker(NUMBERS)
    password = password & RandomCharPicker(SYMBOLS)
    
    '次の2文字をアルファベットのいずれかとし、
    password = password & RandomCharPicker(UPPER_CASE & LOWER_CASE)
    password = password & RandomCharPicker(UPPER_CASE & LOWER_CASE)
    
    '6文字をシャッフル
    password = ShuffleString(password)
    
    '先頭と末尾にアルファベットを付加
    password = RandomCharPicker(UPPER_CASE & LOWER_CASE) & _
                        password & _
                        RandomCharPicker(UPPER_CASE & LOWER_CASE)
    
    'ポリシーを満たしたパスワードの完成
    Debug.Print password
End Function

これで、先頭・末尾がアルファベットかつ、必要な全文字種を含む8桁のパスワード生成プログラムの完成

以下、8回実行した結果。

ct8+PUuX
F~HfH3Pe
mK~Vmp4m
sa^s3MXo
T%EEw2Rw
UV(8LuEZ
AP!aH7LW
oH/F3HzE

出力されるパスワードはすべてポリシーを満たしているので、あとは好きなものを選べば良い。

あとがき

今回はパスワードの生成方法を題材にしたけれど、本当に紹介したかったのは自分で新しい関数を発想するためのヒントだ。たとえば今回紹介したRandomCharPickerやShuffleStringであるが、どちらもその原点は「こんな関数があったらいいのに」という思いである。

最近、まだVBAをよく知らない初心者の方が、実は柔軟に発想できるということもあるんじゃないかと考えるようになった。

初心者はVBAにどんな関数があるのかを知らない。逆にいえば、「何ができないのか」を知らない。だから、自分がやりたいことをずばり解決してくれる関数があるんじゃないかと検索し、見つからずに悩む。

少し上達してくると、既存の関数(たとえばMidやRndなど)を使いこなせるようになる。逆に、「どんな関数が無いか」という現実も知ることになる。プログラミングは用意された関数の制約の中で、それらを組み合わせてやりたいことを実現していく作業である。しかし中級者になると、この制約が自分の発想の幅を狭めてしまっているのではないだろうか。

そこからもう一段上達するためには、初心の頃の自由な発想を取り戻す必要がある。やりたいことをずばり解決する関数はない。でも作ることはできる。さて、どんな関数が欲しい?

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。