16 min readPython
【安心安全!?】Pythonを使ってパスワードを管理してみる
突然ですが、皆さんはどのようにパスワード管理していますか?パスワードを忘れたり、過去にアカウントを乗っ取られたりされた方もいるのではないでしょうか?そこで今回は、Pythonを使って安心安全にパスワードを管理する方法を解説いたします。
用意するもの
- ローカルにてPython環境が叩ける状態であること(ターミナルにて
python -V
でバージョンが表示されていること) - pipが入っていない場合はpipを入れておく(Python 3.4以降では標準で入っている。参考)
- コードを編集する、個人に合うエディタが使える状態であること(おすすめはVsCode)
フローチャート
実行手順
上記のフローチャートを見ただけではわからない部分も多々あるかと思います。まずは以下の手順を踏まえて、ローカル環境(自身のパソコン)にて実装してみましょう。
ローカル環境上にファイルを置く
以下の作業は、私の方で実際に作成したコードを、自身のパソコンへファイルを置く作業になります。
- こちらのページに訪問ください。
- 緑の「Code」と書かれているボタンをクリックして、 「Download ZIP」をクリックください。
- ダウンロードしたZipファイルをドラック&ドロップを使って、デスクトップへファイル移動して下さい。
- Zipファイルを展開するために、デスクトップに置いた先ほどのファイルをダブルクリックして下さい。
Python を実行してみる
必要なパッケージ(Pythonを実行する際に汎用的に使われるコード群)をインストールして、Pythonを実行します。
- ターミナルを開いて、password-masterのディレクトリへ移動します。(
cd
を利用する。) - ターミナル上で
pip install -r requirements.txt
と入力する。(password-masterディレクトリ内にrequirements.txtファイルを用意していて、ファイルを実行するために必要なパッケージ情報を記載しています。その情報を元に自身のパソコン内へパッケージを格納しています。) - 最後に
python password.py
を実行すると、対話形式でパスワードの設定等を行うことができます。(一度以下の完成動画を確認しながら、操作してみると理解が深まります。)
完成動画
よくわかるコード解説
以下のコードは、メイン関数(一番最初に実行される関数)です。メイン関数のコードをじっくり読み解いていくと、フローチャートの意味がわかってきます。上から順番にみていきましょう。
- appName()関数を実行します。
- sys.argv[1]で
python password.py xxx
のxxx(第一引数)に当たる値を取得しています。もしもpython password.py
と実行して、xxx(第一引数)が存在しない場合、本来ならエラーになります。ただ、例外を発生させる(try except)ことでエラーを回避し、except内のannounce()関数を実行するように命令しています。 - pwShelf = shelve.open('pw') によって、管理するパスワードファイルを開いています。
- xxx(第一引数)がlistの場合、displayList()関数を実行します。
- pwShelf.keys()の中には、今まで自身がパスワード登録した名前一覧が入っていて(例 : ['amazon', 'google'])、名前一覧内にxxx(第一引数)が含まれているのか検討しています。含まれていたら、pwCopy(name) 関数を実行します。
- selectInput = input()を使うことで、コマンドラインから対話形式で値を入力することが可能になります。yを入力した場合に、新しくパスワードを作る工程に進みます。
- characterNum = int(input())でさらにコマンドラインから対話形式で値の入力を求めています。パスワードの文字数を設定するために、実装されております。
1if __name__ == "__main__":
2 appName()
3 try:
4 name = sys.argv[1]
5 # 第一引数が存在しない場合は、password.pyを実行するための案内処理を行う。
6 except:
7 announce()
8
9 pwShelf = shelve.open('pw')
10 if name == 'list':
11 displayList()
12
13 # 第一引数で指定した名前に紐づくパスワードが存在する場合
14 if name in pwShelf.keys():
15 pwCopy(name)
16
17 print(name + 'のパスワードが登録されていません\nパスワードを生成して登録しますか?(y/n)')
18 selectInput = input()
19 if selectInput == 'y':
20 print('パスワードを生成します\nパスワードの文字数を8文字以上で入力してください')
21 # コマンドラインにて、パスワードの文字数の入力を受け付ける。
22 # コマンドライン : https://wa3.i-3-i.info/word11158.html
23 try:
24 characterNum = int(input())
25 except:
26 inputError('パスワードの文字数入力に、数字以外の文字列が含まれていました。')
27
28 if characterNum < 8:
29 inputError('文字数入力が8以上ではありませんでした。')
30
31 while True:
32 # string.ascii_letters : https://docs.python.org/ja/3/library/string.html#string.ascii_letters
33 # string.digits : https://docs.python.org/ja/3/library/string.html#string.digits
34 # string.punctuation : https://docs.python.org/ja/3/library/string.html#string.punctuation
35 # sample() : https://note.nkmk.me/python-random-choice-sample-choices/
36 # 文字列の長さ分だけ配列型で1文字づつランダム取得する。文字の重複なし
37 password = random.sample(string.ascii_letters + string.digits + string.punctuation, characterNum)
38 # 配列の文字を結合する
39 # join() : https://note.nkmk.me/python-string-concat/
40 password = ''.join(password)
41
42 # 強いパスワードが生成された場合、ループを抜ける
43 if pwStrengthTest(password):
44 registerPw(name, password)
45 else:
46 print('新規でパスワードを生成することなく終了しました。')
以下のコードでは、アプリ情報を出力するコードになります。こちらの記事を参考に、YELLOWの箇所を変更すると、自分好みの色合いを楽しめます。
1# アプリ情報を出力する関数
2# ファイル実行した場合に、ターミナル上へpasswordと表示される。
3def appName():
4 YELLOW = '\033[33m'
5 END = '\033[0m'
6 print()
7 # 標準出力に関する、色の設定(https://hacknote.jp/archives/51672/)
8 print(YELLOW + " ######## ### ###### ###### ## # ## ##### ######## ######" + END)
9 print(YELLOW + " ## ## ## ## ## ## ## ### ## ## ## ## ## ## ##" + END)
10 print(YELLOW + " ######## ######### ###### ###### ## ## ## ## ## ## ######## ## ##" + END)
11 print(YELLOW + " ## ## ## ## ## ### ### ## ## ## ## ## ##" + END)
12 print(YELLOW + " ## ## ## ###### ###### ## ## ##### ## ## ######" + END)
13 print()
以下のコードでは、python password.py xxx
のxxx(第一引数)が存在しない場合に呼び出される関数です。ファイル実行に関する詳細説明を確認できます。
sys.exit()を活用することで、後続処理を行うことなく正常終了します。
1# コマンドラインに関する案内を行う関数
2# コマンドライン : https://wa3.i-3-i.info/word11158.html
3# 第一引数が入力されていない場合に呼び出される。
4def announce():
5 print('※ 第一引数へ操作したい文字列を入力ください。以下入力例になります。')
6 print(' ◯ python password.py list: 現在登録中のパスワードに紐づく名前一覧を出力します。')
7 # クリップボードの意味(https://www.724685.com/word/wd140423.htm#:~:text=%E3%83%91%E3%82%BD%E3%82%B3%E3%83%B3%E7%94%A8%E8%AA%9E%E3%81%A7%E3%80%8C%E3%82%AF%E3%83%AA%E3%83%83%E3%83%97%E3%83%9C%E3%83%BC%E3%83%89%E3%80%8D%E3%81%A8,%E9%A0%98%E5%9F%9F%EF%BC%89%E3%80%8D%E3%81%AE%E3%81%93%E3%81%A8%E3%81%A7%E3%81%99%E3%80%82&text=%E3%80%8C%E3%82%B3%E3%83%94%E3%83%BC%E3%80%8D%E3%81%97%E3%81%A6%E3%80%8C%E8%B2%BC%E3%82%8A,%E3%81%B0%E3%80%8C%E7%A7%BB%E5%8B%95%E3%80%8D%E3%81%A8%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82)
8 print(' ◯ python password.py xxx: xxxに紐づくパスワードが登録済の場合、保存されているパスワードをコピーします。未登録の場合、新規でパスワード登録するのか、設問されます。yと選択する場合、パスワード登録してコピーします。')
9 sys.exit()
以下のコードでは生成されたパスワードに対し、安全性を確かめる関数になります。確認する方法として、
- パスワード内にはA~Zの値が少なくとも1文字含む
- パスワード内にはa-zの値が少なくとも1文字含む
- パスワード内には0-9の値が少なくとも1文字含む
- パスワード内には[!-/:-@[-`{-~]の中から少なくとも1文字含む
の条件を満たしている場合にのみパスワード生成を行い、人為的に作られないパスワード作成を担保します。
1# 生成されたパスワード文字列が、セキュリティ的に問題ないのか判定する関数
2# passwordが強ければTrue, そうでなければFalseを返す
3def pwStrengthTest(password):
4 # search() : https://note.nkmk.me/python-re-match-search-findall-etc/
5 # 正規表現の先頭のr(raw文字列である。エスケープ文字列が存在しないことを意味する。) : https://docs.pyq.jp/python/library/string.html#raw
6 # 正規表現 : https://userweb.mnet.ne.jp/nakama/
7 # エスケープ文字 : https://e-words.jp/w/%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E5%87%A6%E7%90%86.html#:~:text=%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E5%87%A6%E7%90%86%E3%81%A8%E3%81%AF%E3%80%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0,%E3%82%92%E3%80%8C%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E6%96%87%E5%AD%97%E3%80%8D%E3%81%A8%E3%81%84%E3%81%86%E3%80%82
8 # re.search(re.compile(r'[A-Z]+'), password) : passwordの文字列内にA~Zの文字が1回以上存在するのか確認している
9 # re.search(re.compile(r'[a-z]+'), password) : passwordの文字列内にa~zの文字が1回以上存在するのか確認している
10 # re.search(re.compile(r'[0-9]+'), password) : passwordの文字列内に0~9の文字が1回以上存在するのか確認している
11 # re.search(re.compile('[!-/:-@[-`{-~]+'), password) : passwordの文字列内に[!-/:-@[-`{-~]内の記号文字が1回以上存在するのか確認している
12 if re.search(re.compile(r'[A-Z]+'), password) and re.search(re.compile(r'[a-z]+'), password) and re.search(re.compile(r'[0-9]+'), password) and re.search(re.compile('[!-/:-@[-`{-~]+'), password):
13 return True
14 else:
15 return False
以下のコードでは、パスワードをコピーする機能を実装しました。pwShelf[name]を使い、名前に紐づくパスワードを取得します。pyperclip.copy()を呼び出すことでクリップボードへパスワードをコピーします。
1# パスワードをコピーする関数
2def pwCopy(name):
3 print(name + 'のパスワードを確認しました')
4
5 # パスワードをクリップボードへコピーする。
6 # https://www.724685.com/word/wd140423.htm#:~:text=%E3%83%91%E3%82%BD%E3%82%B3%E3%83%B3%E7%94%A8%E8%AA%9E%E3%81%A7%E3%80%8C%E3%82%AF%E3%83%AA%E3%83%83%E3%83%97%E3%83%9C%E3%83%BC%E3%83%89%E3%80%8D%E3%81%A8,%E9%A0%98%E5%9F%9F%EF%BC%89%E3%80%8D%E3%81%AE%E3%81%93%E3%81%A8%E3%81%A7%E3%81%99%E3%80%82&text=%E3%80%8C%E3%82%B3%E3%83%94%E3%83%BC%E3%80%8D%E3%81%97%E3%81%A6%E3%80%8C%E8%B2%BC%E3%82%8A,%E3%81%B0%E3%80%8C%E7%A7%BB%E5%8B%95%E3%80%8D%E3%81%A8%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82
7 pyperclip.copy(pwShelf[name])
8
9 print('パスワードをコピーしました')
10
11 sys.exit()
今日のまとめ
- pyperclipを活用することで容易にコピーを再現できる。
- ランダムに生成された文字列を確認し、パスワードの安全性を担保できる。
- sys.argv[1]を利用することで、引数を利用できる。
こんなことにもチャレンジしてみよう
- パスワードをいくつか作ると、同じパスワードが生成される可能性が出てきます。単一パスワードの生成をどのように実現すべきだろう?
- パスワードの長さをコマンドラインにて対話することで実現しました。パスワードの長さへ大きな数を指定された場合にどのように対処すべきだろう?