■ はじめに
システムのパスワードを決めるために、 より安全な文字列を生成する必要ができたので Pythonで生成する方法を調べてみた。 ついでに、「トークン」や「UUID」も載せておく。 ちなみに、Java版で同じことを過去にやっていた。
【Java】セキュアなランダム文字列生成を考える
https://dk521123.hatenablog.com/entry/2016/10/04/233416
目次
【1】ランダムパスワード 1)生成方針 2)サンプル 3)使用上の注意 【2】トークン 1)生成方針 2)サンプル 【3】UUID 1)生成方針 2)サンプル
【1】ランダムパスワード
1)生成方針
[1] 生成する文字種(英数字、記号)を決める [2] 決めた文字種をランダムで選ぶ [3] [2] を文字数分繰り返す
[1] 生成する文字種(英字、数字、記号)を決める
import string # abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ print(string.ascii_letters) # 0123456789 print(string.digits) # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ print(string.punctuation)
[2] 決めた文字種をランダムで選ぶ
Pythonでのランダムでの選び方は、色々あるので それらを使って組み込む * random.choice * secrets.choice <= セキュアならこっち etc...
[3] [2] 文字数分繰り返す
# 以下「【2】サンプル」の↓部分 (secrets.choice(random_source) for i in range(length))
2)サンプル
import secrets import string def get_random_string(length): random_source = string.ascii_letters + string.digits + string.punctuation random_string = ''.join( (secrets.choice(random_source) for i in range(length))) return random_string random_string_length = 12 print(f"=== 12345678901234567890") print(f"[1] {get_random_string(random_string_length)}") print(f"[2] {get_random_string(random_string_length)}") print(f"[3] {get_random_string(random_string_length)}") print(f"[4] {get_random_string(random_string_length)}") print(f"[5] {get_random_string(random_string_length)}")
3)使用上の注意
* 上記のサンプルだと本当に決められたルールからランダムに生成される。 => よって、例えば「R:a|bG$acb=Q」のように数字を含まないパターンも 生成される場合もある => パスワード生成にルールがある(例えば、数字は最低1つは含む 等)場合 そのルールに当てはまらないパスワードが生成されることもあるので注意 => 運用上、複数回生成して、そこからルール通りのパスワードを ピックアップするのもいいが、できる限り頭を使いたくないので、 パスワード生成ルール考慮したコード例を以下に記す
サンプル・改善版
import secrets import string RANDOM_STRING_LENGTH = 12 PASSWORD_NUM_TO_NEED = 10 MAX_EXEC_NUM = 100 # Passwordルールに対する定数(例) MIN_PASSWORD_LENGTH = 8 MAX_PASSWORD_LENGTH = 32 def get_random_string(length: int) -> str: random_source = string.ascii_letters + string.digits + string.punctuation random_string = ''.join( (secrets.choice(random_source) for i in range(length))) return random_string # ★追加:使用しているパスワードのルールに従い実装する(以下、実装例) def is_valid_password(target_password: str) -> bool: # 1) 最低 8 文字から if len(target_password) < MIN_PASSWORD_LENGTH: return False # 2) 最高 32 文字まで if len(target_password) > MAX_PASSWORD_LENGTH: return False # 3) 最低数字が含まれている if not any(map(str.isdigit, target_password)): return False # 4) 最低大文字および小文字が含まれている if not (any(map(str.islower, target_password)) and any(map(str.isupper, target_password))): return False return True password_num = 0 print(f"==== 12345678901234567890") for i in range(MAX_EXEC_NUM): password = get_random_string(RANDOM_STRING_LENGTH) if is_valid_password(password): password_num = password_num + 1 print(f"[{password_num:2}] {password}") if PASSWORD_NUM_TO_NEED <= password_num: break print("Done")
【2】トークン
1)生成方針
https://docs.python.org/ja/3/library/secrets.html#generating-tokens
にあるsecretsモジュールの token_xxxx() を使うだけ(簡単!) [1] secrets.token_bytes([nbytes=None]) => nbytes バイトを含むバイト文字列を返す [2] secrets.token_hex([nbytes=None]) => 十六進数のランダムなテキスト文字列を返す [3] secrets.token_urlsafe([nbytes=None]) => nbytes のランダムなバイトを持つ URL 安全なテキスト文字列を返す
補足:トークンは何バイト使うべきか?
https://docs.python.org/ja/3/library/secrets.html#how-many-bytes-should-tokens-use
より抜粋 ~~~~~~~~~~~~~~~~~ 総当たり攻撃 に耐えるには、トークンは十分にランダムでなければなりません。 残念なことに、コンピュータの性能が向上し、より短時間により多くの推測が できるようになるにつれ、十分とされるランダムさというのは必然的に増えます。 2015 年の時点で、secrets モジュールに想定される通常の用途では、 32 バイト (256 ビット) のランダムさは十分と考えられています。 注釈 デフォルトはメンテナンスリリースの間を含め、いつでも変更される可能性があります ~~~~~~~~~~~~~~~~~
2)サンプル
import secrets target_byte = 32 print(f"===== 1234567890123456789012345678901234567890") print(f"Token {secrets.token_bytes(target_byte)}") print(f"Token {secrets.token_hex(target_byte)}") print(f"Token {secrets.token_urlsafe(target_byte)}")
出力結果
===== 1234567890123456789012345678901234567890 Token b'\xbf\xb9\xb5:K\xf7(\xb4yg\xb1H\x93\x0b\x16\x10\xeb\x1e\x1b;\xeb*Z:\xf5\x1d\x8b"\xafL\xb1\xe4' Token ea917e7d10d7f8edd195568a8c934a476ce5c4c2918492eced6c0582218a0b20 Token OdouCzAUDkjx9ORAN-Wsz1XaDaohn7TbvFZdE1QorOw
【3】UUID
1)生成方針
https://docs.python.org/ja/3/library/uuid.html
にあるuuidモジュールの uuid4() を使うだけ(簡単!)
2)サンプル
import uuid uuid = uuid.uuid4() print(f"UUID : {uuid}, is_safe = {uuid.is_safe}")
出力結果
UUID : 44062120-d060-4c21-9cb5-7d0a6819d543, is_safe = SafeUUID.unknown
参考文献
https://leben.mobi/blog/python_ramdom_password/python/
関連記事
Python ~ 基本編 / 文字列 ~
https://dk521123.hatenablog.com/entry/2019/10/12/075251
Python ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2014/08/07/231242
【Java】セキュアなランダム文字列生成を考える
https://dk521123.hatenablog.com/entry/2016/10/04/233416