Python3 を子供に教えるためにパスワード生成機を作ってみた（オマケ）

# マイナンバーカードの暗証番号変えるのが流行ってるみたいなのでパスワード生成機を作ってみた 更新：2020/05/30

　本当は仕事で任意の桁数、1000個以上のパスワードが必要になったからなんですけどね･･･
折角なので、これも Python のお勉強と以下のような仕様でパスワード生成機を作ってみました。

## パスワード生成機の仕様

1. 任意の桁数のパスワード
2. 英語の小文字を混ぜる（か選択できる）
3. 英語の大文字を混ぜる（か選択できる）
4. 記号を混ぜる（か選択できる）
5. ３文字連続した文字ははじく（123 , abc , 987 , XYZ , 121 などはダメ！）
6. 記号ありにした場合は必ず記号が含まれるようにする
7. 記号や0やOなど入力しづらいものや見間違えそうなものを除外できるようする（例示している文字は私がパスワードとして嫌いな文字を入れてありますｗ）
8. 上記ルールのパスワードを指定数分作成する
9. 画面に出力する

## ちょっとだけ解説

　大したことはやってないですね。`random.choices(population,weights=weights,k=password_length))`で、記号の重み付けをするように変更しました。（なんかパスワードの中の記号大杉！という気がしたため） 重み付けについては `make_weights(population)` で行っているので、バランス悪いなと思われる場合はこちらを変更してくださいね。

　「子供に教える」という観点では、オセロよりこっちのプログラムの方が難解ですね。なぜなら文字コードという概念が出てくるからです。「君が今観てる画面に表示されている A は16進数の 0x41 なんですよ」なんて言っても理解不能ですよね。

```python
# Password generator
# created 2020 (C) tsFox
import random , string , re

class password_generator_class:
    def __init__(self):
        self.punctuation = string.punctuation
        self.generate_count = 0
        self.password_length = 0
        self.population = string.digits
        self.password = ""
        self.punctation_yn = ""
        self.specific_characters = ""
        self.weights = list()
        self.passwords =list()
	
    # キーボード入力
    def yndinput(self,yndstr,yndsw):
        while True:
            akey = input(yndstr)
            if yndsw and akey.isdigit():
                return akey
            if yndsw == False and ( akey.lower() == 'n' or akey.lower() == 'y' ):
                return akey.lower()

    def set_population(self,string_population):
        self.population = self.population + string_population

    # 除外したい文字を入力するして新しい population を返す
    def remove_specific_characters(self):
        yn = self.yndinput("除外したい文字がありますか？(y/n)=", False)
        if yn == 'n':
            return
        # 除外文字を指定する
        while True:
            self.specific_characters = input("使用したく無い文字を全部入力してください（例：O`\\'l）=")
            if self.specific_characters != "":
                break
        # 一旦リストにして不要な記号を消す
        list_population = list(self.population)
        for c in self.specific_characters:
            list_population.remove(c)
        # また文字列に戻す
        self.population = "".join(list_population)

    # 重み付けを作成する
    def make_weights(self):
        list_weights = list()
        for c in self.population:
            list_weights.append(2 if c in self.punctuation else 6)
        return list_weights

    # パスワードの妥当性
    def check_validity(self,tmp_password,punctation_check):
        for x in range(len(tmp_password)-2):
            a = ord(tmp_password[x])
            b = ord(tmp_password[x+1])
            c = ord(tmp_password[x+2])
            if ( -1 <= int(a - b) <= 1 ) and ( -1 <= int(b - c) <= 1 ):
                return 1
            # 重み付けのせいでたまに記号が入らないことがあるので正規表現で記号を検索して無ければやり直す
            start_punctation = re.search( "[{}]".format(self.punctuation) ,tmp_password)
            if punctation_check == "y" and start_punctation == None:
                return -1
        return 0

    def create_password(self):
        for n in range(self.generate_count):
            while True:
                self.password = "".join(random.choices(self.population,weights=self.weights,k=self.password_length))
                if self.check_validity(self.password,self.punctation_yn):
                    continue
                # 重複チェック
                if self.password not in self.passwords:
                    break
            # パスワードを追加
            self.passwords.append(self.password)

if __name__ == '__main__':
    # メインロジック
    pg = password_generator_class()

    # 指定が無い場合は数字のみ
    pg.generate_count = int(pg.yndinput("生成するパスワードの個数(数値)=",True))
    pg.password_length = int(pg.yndinput("パスワードの桁数(数値)=",True))

    if pg.yndinput("英語小文字含める？(y/n)=", False) == 'y':
        pg.set_population(string.ascii_lowercase)

    if pg.yndinput("英語大文字含める？(y/n)=", False) == 'y':
        pg.set_population(string.ascii_uppercase)

    # 記号入れるかどうか
    pg.punctation_yn = pg.yndinput("記号含める？(y/n)=", False)
    if pg.punctation_yn == "y":
       pg.set_population(string.punctuation)

    # 除外文字があれば取り除いて新しい population に入れる
    pg.remove_specific_characters()

    # 記号の重み付けを減らす
    pg.weights = pg.make_weights()
   
    # 生成個数分のループ
    pg.create_password()

    # 画面に表示
    print(*pg.passwords , sep = "\n" )
```

というわけで、パスワード生成機のソース公開いたしました。またお会いしましょう！！

c u