はじめに
多要素認証(Multi-factor authentication, MFA)は、複数の証明を要求することでアカウント侵害のリスクを低減します。パスワードは「ユーザーが知っているもの」です。二つ目の要素には、別のデバイス、アプリ、ファイル、またはハードウェアトークンから取得するコードなどが利用されます。
この実験では、パスワードのみで動作する Python ログインスクリプトから始め、シンプルな二つ目の要素を追加します。初心者にも分かりやすく、かつ結果が予測可能なものにするため、二つ目の要素には保護されたシークレットファイルから生成される HMAC ベースのコードを使用します。
パスワードのみのログインをテストする
このステップでは、初期状態のログインスクリプトを実行します。これはユーザー名とパスワードのみを確認するもので、証明が「ユーザーが知っているもの」一つしかないため、単要素認証となります。
プロジェクトディレクトリに移動します:
cd /home/labex/project
準備されたファイルを確認します:
ls -l
ユーザーデータベースを表示します:
cat users.json
パスワードのみのログインを成功させてみます:
python3 login.py alice wonderland
出力は以下のようになります:
login ok
次に、間違ったパスワードを試します:
python3 login.py alice wrong
このコマンドは login failed と表示するはずです。ログイン失敗時はゼロ以外のステータスで終了しますが、このテストでは正常な挙動です。
初期状態のチェッカーを実行します:
python3 mfa-check.py password-only
期待される結果は以下の通りです:
password-only: complete
二要素認証用のシークレットを作成する
このステップでは、二つ目の要素のために個別のシークレットを作成します。実際の MFA システムでは、このシークレットは認証アプリやハードウェアトークン内に保持されます。この実験では、動作を簡単に確認・検証できるようにローカルファイルとして扱います。
シークレット値を作成します:
printf 'labex-mfa-secret-2026\n' > mfa_secret.txt
所有者のみが読み書きできるようにファイルの権限を制限します:
chmod 600 mfa_secret.txt
権限を確認します:
ls -l mfa_secret.txt
権限ビットが -rw------- で始まっているはずです:
-rw------- ... mfa_secret.txt
シークレットのチェッカーを実行します:
python3 mfa-check.py secret
期待される結果は以下の通りです:
secret: complete
ログインコードを生成する
このステップでは、小さなコード生成ツールを作成します。HMAC-SHA256 を使用して、シークレットファイルとユーザー名を組み合わせます。HMAC は、同じ入力とシークレットからは同じ短いコードが生成される一方で、シークレットを知らない第三者が簡単にコードを作成できないため、この用途に適しています。
generate-code.py を作成します:
cat > generate-code.py <<'PY'
#!/usr/bin/env python3
import hmac
import hashlib
import sys
from pathlib import Path
if len(sys.argv) != 2:
print("usage: generate-code.py USERNAME")
raise SystemExit(2)
username = sys.argv[1]
secret = Path("mfa_secret.txt").read_text().strip().encode()
digest = hmac.new(secret, username.encode(), hashlib.sha256).hexdigest()
print(digest[:6])
PY
実行権限を付与します:
chmod +x generate-code.py
alice 用のコードを生成します:
python3 generate-code.py alice
以下のような 6 文字の確定的なコードが表示されるはずです:
21f18f
ジェネレーターのチェッカーを実行します:
python3 mfa-check.py generator
期待される結果は以下の通りです:
generator: complete
二要素認証を強制する
このステップでは、パスワードと生成されたコードの両方を要求するようにログインスクリプトを置き換えます。これが MFA における重要な変更点であり、パスワードだけでは不十分になります。
login.py を MFA 対応版に置き換えます:
cat > login.py <<'PY'
#!/usr/bin/env python3
import hmac
import hashlib
import json
import sys
from pathlib import Path
USERS = Path("users.json")
SECRET = Path("mfa_secret.txt")
def expected_code(username):
secret = SECRET.read_text().strip().encode()
digest = hmac.new(secret, username.encode(), hashlib.sha256).hexdigest()
return digest[:6]
def main():
if len(sys.argv) != 4:
print("usage: login.py USERNAME PASSWORD MFA_CODE")
return 2
username, password, code = sys.argv[1], sys.argv[2], sys.argv[3]
users = json.loads(USERS.read_text())
if users.get(username, {}).get("password") != password:
print("login failed")
return 1
if not hmac.compare_digest(code, expected_code(username)):
print("mfa failed")
return 1
print("login ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())
PY
スクリプトに実行権限があることを確認します:
chmod +x login.py
新しいコードを生成し、シェル変数に保存します:
CODE="$(python3 generate-code.py alice)"
正しいパスワードとコードを組み合わせて使用します:
python3 login.py alice wonderland "$CODE"
出力は以下のようになります:
login ok
MFA 強制のチェッカーを実行します:
python3 mfa-check.py enforced
期待される結果は以下の通りです:
enforced: complete
MFA の失敗と成功をテストする
このステップでは、最終的なログイン動作をテストします。正しいパスワードでも二つ目の要素がない場合は失敗し、間違ったコードでも失敗し、正しいパスワードと正しいコードの組み合わせのみが成功することを確認します。
まず、以前のパスワードのみのコマンドを試します:
python3 login.py alice wonderland || true
MFA コードが不足しているため、新しい使用方法(usage)メッセージが表示されるはずです:
usage: login.py USERNAME PASSWORD MFA_CODE
間違ったコードを試します:
python3 login.py alice wonderland 000000 || true
出力は以下のようになります:
mfa failed
正しいコードを生成してログインします:
CODE="$(python3 generate-code.py alice)"
python3 login.py alice wonderland "$CODE"
出力は以下のようになります:
login ok
すべてのチェッカーを実行します:
python3 mfa-check.py all
最終的な出力には、すべての MFA コンポーネントが完了したことが表示されます:
secret: complete
generator: complete
enforced: complete
まとめ
パスワードのみのログインスクリプトを、シンプルな多要素認証フローに変更しました。個別の MFA シークレットを作成し、HMAC ベースのコードを生成し、ログインスクリプトでそのコードを必須とし、失敗と成功の両方のケースをテストしました。
セキュリティの主要な考え方は、独立した二つ目の証明が必要な場合、パスワードが盗まれただけでは不十分であるということです。



