PythonのID関数の使い方
inty98-admin
inty98
Pythonを使ってパスワードを生成する方法を詳しく解説していきます。
本ページに記載のコードは常識の範囲内であれば、商用利用問わず利用(改変)していただいて問題ありません。
NG(常識の範囲外)の例として、本ページのコードをそのまま or 概ね同じ状態でパッケージ(ライブラリ)として公開するなどはご遠慮願います。(会社内、組織内の閉じられた環境であれば問題ありません)
作成したコードは下記になります。
生成するパスワードに小文字、大文字、数字、記号を含めるかどうかを引数で設定できるようにしています。
大文字・小文字、数字などの文字列はstringモジュールの定数から取得します。
パスワードの生成にはsecretsモジュールは暗号学的に強い乱数を生成することができるとのことですので、randomモジュールではなく、こちらを使用します。
処理の流れとしては、REQUIREDに指定されている種類の文字を先に生成し、その後指定した文字数になるまで、INCLUDE or REQUIREDに設定されている文字を生成します。
REQUIREDをまとめて先に生成しているため、先頭に固まっていますので、最後にシャッフルします。
import string
from enum import Enum, auto
import secrets
import random
class Policy(Enum):
REQUIRED = auto()
INCLUDE = auto()
NOT_INCLUDE = auto()
def create_password(
len: int,
lowercase: Policy = Policy.REQUIRED,
uppercase: Policy = Policy.REQUIRED,
digits: Policy = Policy.REQUIRED,
symbol: Policy = Policy.INCLUDE,
) -> str:
chars: list[str] = []
password: list[str] = []
created_len: int = 0
for policy, target_string in [
(lowercase, string.ascii_lowercase),
(uppercase, string.ascii_uppercase),
(digits, string.digits),
(symbol, string.punctuation),
]:
if policy != Policy.NOT_INCLUDE:
target_list: list[str] = list(target_string)
chars.extend(target_list)
if policy == Policy.REQUIRED:
password.append(secrets.choice(target_list))
created_len += 1
# 必須としている種類が生成する桁数より大きい場合はエラー
if len < created_len:
raise ValueError("桁数が不足しています")
for _ in range(len - created_len):
password.append(secrets.choice(chars))
# 必須を先に生成しているため、シャッフルする
random.shuffle(password)
return "".join(password)
# 確認用のコード ※削除して問題ありません
if __name__ == "__main__":
# 10桁 (小文字・大文字・数字は各種1つ以上を含める必要あり)
print(create_password(10, symbol=Policy.REQUIRED))
# 16桁 (小文字・大文字・数字は各種1つ以上を含める必要あり)
print(create_password(16))
作成したパスワード生成関数を検証するためにユニットテストを実施します。
パスワードの文字数が指定した通りになっているか、使用する文字種が想定範囲をカバーされているかをチェックします。
※ 下記のファイルはpassword.pyと同じ階層に置いてください。
from password import create_password, Policy
import string
import unittest
TEST_INCLUDE_PASSWORD_LEN: int = 32
TEST_REQUIRED_PASSWORD_LEN: int = 2
class TestPassword(unittest.TestCase):
def test_create_password_include_lowercase(self):
"""小文字 (含めない)."""
for _ in range(100):
password: str = create_password(
TEST_INCLUDE_PASSWORD_LEN,
lowercase=Policy.NOT_INCLUDE,
uppercase=Policy.INCLUDE,
digits=Policy.INCLUDE,
symbol=Policy.INCLUDE,
)
assert not self._check_include(
password=password, check_chars=string.ascii_lowercase
)
assert len(password) == TEST_INCLUDE_PASSWORD_LEN
def test_create_password_required_lowercase(self):
"""小文字 (必須)."""
password: str = create_password(
TEST_REQUIRED_PASSWORD_LEN,
lowercase=Policy.REQUIRED,
uppercase=Policy.NOT_INCLUDE,
digits=Policy.NOT_INCLUDE,
symbol=Policy.NOT_INCLUDE,
)
assert self._check_include(
password=password, check_chars=string.ascii_lowercase
)
assert len(password) == TEST_REQUIRED_PASSWORD_LEN
def test_create_password_include_uppercase(self):
"""大文字 (含めない)."""
for _ in range(100):
password: str = create_password(
TEST_INCLUDE_PASSWORD_LEN,
lowercase=Policy.INCLUDE,
uppercase=Policy.NOT_INCLUDE,
digits=Policy.INCLUDE,
symbol=Policy.INCLUDE,
)
assert not self._check_include(
password=password, check_chars=string.ascii_uppercase
)
assert len(password) == TEST_INCLUDE_PASSWORD_LEN
def test_create_password_required_uppercase(self):
"""大文字 (必須)."""
password: str = create_password(
TEST_REQUIRED_PASSWORD_LEN,
lowercase=Policy.NOT_INCLUDE,
uppercase=Policy.REQUIRED,
digits=Policy.NOT_INCLUDE,
symbol=Policy.NOT_INCLUDE,
)
assert self._check_include(
password=password, check_chars=string.ascii_uppercase
)
assert len(password) == TEST_REQUIRED_PASSWORD_LEN
def test_create_password_include_digits(self):
"""数字 (含めない)."""
for _ in range(100):
password: str = create_password(
TEST_INCLUDE_PASSWORD_LEN,
lowercase=Policy.INCLUDE,
uppercase=Policy.INCLUDE,
digits=Policy.NOT_INCLUDE,
symbol=Policy.INCLUDE,
)
assert not self._check_include(password=password, check_chars=string.digits)
assert len(password) == TEST_INCLUDE_PASSWORD_LEN
def test_create_password_required_digits(self):
"""数字 (必須)."""
password: str = create_password(
TEST_REQUIRED_PASSWORD_LEN,
lowercase=Policy.NOT_INCLUDE,
uppercase=Policy.NOT_INCLUDE,
digits=Policy.REQUIRED,
symbol=Policy.NOT_INCLUDE,
)
assert self._check_include(password=password, check_chars=string.digits)
assert len(password) == TEST_REQUIRED_PASSWORD_LEN
def test_create_password_include_symbol(self):
"""記号 (含めない)."""
for _ in range(100):
password: str = create_password(
TEST_INCLUDE_PASSWORD_LEN,
lowercase=Policy.INCLUDE,
uppercase=Policy.INCLUDE,
digits=Policy.INCLUDE,
symbol=Policy.NOT_INCLUDE,
)
assert not self._check_include(
password=password, check_chars=string.punctuation
)
assert len(password) == TEST_INCLUDE_PASSWORD_LEN
def test_create_password_required_symbol(self):
"""記号 (必須)."""
password: str = create_password(
TEST_REQUIRED_PASSWORD_LEN,
lowercase=Policy.NOT_INCLUDE,
uppercase=Policy.NOT_INCLUDE,
digits=Policy.NOT_INCLUDE,
symbol=Policy.REQUIRED,
)
assert self._check_include(password=password, check_chars=string.punctuation)
assert len(password) == TEST_REQUIRED_PASSWORD_LEN
def test_create_password_len_error(self):
pass
@staticmethod
def _check_include(password: str, check_chars: str) -> bool:
return any([check_char in password for check_char in list(check_chars)])
if __name__ == "__main__":
unittest.main()