
パスワードをハードコーディングせずに運用したい
初ノートの癖に覚書。
Perplexity使いながら、いろいろ調べて、自分なりに考えた方法をまとめておく。
ローカルでAPI使ったアプリとか、自動売買システムとか構築&運用するときに使用するであろう。
概要
やりたいこと
サービスのID&パスワードをハードコーディングすることなく読み出す背景
APIを自分のローカルマシンで動かすときに、アカウントとか、パスワードをハードコーディングしたくないなーと思った。環境
Pythonやったこと
暗号化&復号化残課題
開発・デバッグ時に毎回パスワード打つのが不便。
もっといい方法があれば、是非ご紹介いただきたい。
採用した方法
(1)暗号化したID, パスワードをenvファイルに保存し、(2)運用側はID, パスワードを復号化して使用する。工夫点は以下。
パスワードは標準入力とすることでハードコーディングを防止
暗号&復号用キーは自分が記憶できるパスワードから生成する
ソルトを使ったハッシュキー化により解読難易度を上げる
(1)ID, パスワードを暗号化し、.envファイルとして保存する
キー生成と暗号化には、secretsとcryptographyを使用した。
envファイルの読み出しは、dotenvを使用した。
pipでcryptographyとpython-dotenvをインストールする。
pip install cryptography python-dotenv
暗号化コードは以下。鍵導出関数はPBKDF2方式を使用している。
import secrets
import base64
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.fernet import Fernet
"""暗号化するためのキー&オブジェクト生成"""
# 標準入力から暗号化用のパスワードを入力する getpassを使用し、伏字入力
input_password = getpass.getpass('password?=')
# キー用にエンコード
password_bytes = input_password.encode()
# ソルト値をランダム生成
salt = secrets.token_bytes(16)
salt_str = base64.urlsafe_b64encode(salt).decode('utf-8')
# PBKDF2を使用してキーを生成
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend()
)
# キーの生成
key = base64.urlsafe_b64encode(kdf.derive(password_bytes))
# Fernetオブジェクトを生成
fernet = Fernet(key)
"""サービスパスワードの暗号化"""
# 標準入力からサービスのID, パスワードを入力する
service_id = input("service_id?=")
service_password = getpass.getpass(prompt='service_password?=')
# farnetオブジェクトでID, パスワードを暗号化
encrypted_service_id = fernet.encrypt(service_id.encode())
print("Encrypted ID= ",encrypted_service_id.decode(encoding='utf-8'))
encrypted_service_password = fernet.encrypt(service_password.encode())
print("Encrypted password=",encrypted_service_password.decode(encoding='utf-8'))
acount_file = "account.env")
with open(account_file,mode="w") as f:
print(f"SALT={salt_str}", file=f)
print(f"ENCRYPTED_SERVICE_ID={encrypted_service_id.decode()}", file=f)
print(f"ENCRYPTED_SERVICE_PASSWORD={encrypted_service_password.decode()}", file=f)
実行結果
account.envに暗号化されたIDとパスワードが保存される。
以下はinput_password=a, service_id=b, service_password=cで実行した結果。
SALT=yuqWJ9nEhjI5iskhCK5F4w==
ENCRYPTED_SERVICE_ID=gAAAAABmvtjhzdQRd8iVdDgs255egzqOSHAyC8rdwnbYGqm3tMPBpTeeTPoIb6oC3RoUYJVa5mpyagUjkhkP8ArdrTDfRi4Z6g==
ENCRYPTED_SERVICE_PASSWORD=gAAAAABmvtjh1ovnwR7ntaJR_iPl-Ogohg-CdMHq6LzficS5R6dgolnvMaFwfSOU2MYyqDU-AeFlH8HourgLLzhXXZKadUAK3w==
(2)envファイルを読み込み、復号化する
復号化コードは以下。
import getpass
import base64
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.fernet import Fernet
# .envファイルを読み込む
env_path = r'config\test.env'
load_dotenv(dotenv_path=env_path)
# 暗号化されたパスワードを読み込む
salt_str = os.getenv("SALT").encode()
encrypted_service_id = os.getenv("ENCRYPTED_SERVICE_ID").encode()
encrypted_service_password = os.getenv("ENCRYPTED_SERVICE_PASSWORD").encode()
# ユーザーからパスワードを入力させる
input_password = getpass.getpass(prompt='Enter your password: ')
# 入力されたパスワードを復号化用のキーとして使用する
password_bytes = input_password.encode()
# saltをbyte文字に戻す
salt = base64.urlsafe_b64decode(salt_str)
# PBKDF2を使用して復号化用のキーを生成
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend()
)
# キーの生成
key = base64.urlsafe_b64encode(kdf.derive(password_bytes))
fernet = Fernet(key)
# 復号化
decrypted_service_id = fernet.decrypt(encrypted_service_id).decode()
decrypted_service_password = fernet.decrypt(encrypted_service_password).decode()
実行結果
復号化したidとパスワードを表示させると、以下になる。うまくいってる。
Decrypted ID= b
Decrypted password= c
思うところ
以下が欠点であり、不便。
結局、解読用のパスワードを自分で記憶する必要がある。
開発・デバッグ時にも毎回パスワードを打つことになる
一度パスワードを打ったら、しばらくは認証してくれるような運用がしたいのだけれど、良い方法はないものか。
この辺が私では限界なので、もっといい方法があれば教えていただきたい。