パスワード生成機作成記

# 前枠
何か自分で調べて開発してみたかったのと，前からパスワードの生成について興味があったので作ろうということになりました．

私はPython がﾁｮｯﾄﾜｶﾙのでPython ライブラリを使って簡単に作っていきたいと思います．
開発に関しては素人なので，コードが汚いのはご了承ください．

# 目指す要件
- 最初はクラスにする，最終的にはWebアプリにもしてみたい．
- 文字数の選択ができる
- 含む文字の種類が選択できる
- 「大文字アルファベットが少なくとも1回出てくる」のようなことが選択できる．

# まず乱数について調べる
パスワードの大前提として**他人に推測されないようなものが推奨される**ため，文字をどのように選択していくかが重要．
なので，ランダムに文字を選択してくれる仕組みが必要．

Python にはrandomモジュールがある．
https://docs.python.org/ja/3/library/random.html#module-random

しかし上の公式ドキュメントにある通り，論理的には再現できてしまう・推測できてしまうような，ある程度ランダムな値を導出するに過ぎず，セキュリティ目的には向かない．

そこでセキュリティ目的の利用が推奨されているのが，OSが提供する乱数発生源をもとにしているsecretsモジュール
https://docs.python.org/ja/3/library/secrets.html#module-secrets
これだよ．こういうのでいいんだよ．

しかもよくみると，レシピとベストプラクティスが書いてある．

> アルファべットと数字からなり、小文字を少なくとも1つと数字を少なくとも3つ含む、10文字のパスワードを生成するには:

```generate_password_best_practice.py
import string
import secrets
alphabet = string.ascii_letters + string.digits
while True:
    password = ''.join(secrets.choice(alphabet) for i in range(10))
    if (any(c.islower() for c in password)
            and any(c.isupper() for c in password)
            and sum(c.isdigit() for c in password) >= 3):
        break
```
これをいじっていこう．

# 関数化する
## パスワード長を引数でとる
```generate_random.py
import string
import secrets

alphabet = string.ascii_letters + string.digits

def password_generater(password_length):
    while True:
        password = ''.join(secrets.choice(alphabet) for i in range(password_length))
        if (any(c.islower() for c in password) and any(c.isupper() for c in password) and sum(c.isdigit() for c in password) >= 3):
           break
    return password
```

```main.py
import generate_random 

# 引数でパスワード長を指定できる
result = generate_random.password_generater(16)

print(result)
```

実行してみると

```結果
7g29HnFJItLJoiB7
```

うん，いい感じ．

## 各文字の出現の最小数を引数でとる
```generate_random.py
import string
import secrets

alphabet = string.ascii_letters + string.digits

def password_generater(password_length, lower_letters_min=0, upper_letters_min=0, digit_min=0):
    while True:
        password = ''.join(secrets.choice(alphabet) for i in range(password_length))
        if (sum(c.islower() for c in password) >= lower_letters_min and sum(c.isupper() for c in password) >= upper_letters_min  and sum(c.isdigit() for c in password) >= digit_min):
           break
    return password
```
デフォルトで0としているので，特に指定がなければ一回で生成される．

実行してみると

```結果
zmoIBPQVU8MI3tJL
```
公式のレシピでの「数字が少なくとも3つ出現」という条件が確かに無くなっていそう．

## 引数をリストにしてみる
ここまでやってみて，出現数をリストでまとめておいた方が，「大文字アルファベットの最小数のみを指定したい！」みたいなときに便利そうと思いついた．

ひとまず，パスワード長のデフォルト値を設定

```generate_random.py
import string
import secrets

alphabet = string.ascii_letters + string.digits

def password_generater(password_length=8, lower_letters_min=0, upper_letters_min=0, digit_min=0):
    while True:
        password = ''.join(secrets.choice(alphabet) for i in range(password_length))
        if (sum(c.islower() for c in password) >= lower_letters_min and sum(c.isupper() for c in password) >= upper_letters_min  and sum(c.isdigit() for c in password) >= digit_min):
           break
    return password

if __name__ == "__main__":
    print(password_generater())

# 実行結果例
# gTiKxsEj (確かに8文字だし，数字出てなかったりする)
```

Python の関数は，引数にリストを指定するときに「*」を前に付ければ展開してくれるので，それを使おう．

```main.py
import generate_random 

required_parameter = [16, 3, 3, 3]
result = generate_random.password_generater(*required_parameter)

print(result)
# 実行結果例
# NV03AKUaGdsdG6Hr
```

いい感じな気がする．

# 後枠
ひとまず，自分で満足できたので，一区切り．

Webアプリとか，リストの初期化とか気にしなきゃいけないことがありそうなので勉強せねば．