パスワードセキュリティとは?強固なパスワード管理の基本

パスワードセキュリティとは?

パスワードセキュリティとは、アカウントへの不正アクセスを防ぐために、パスワードの作成・管理・保護を適切に行うセキュリティ対策の基本です。

パスワードは、デジタル世界における「鍵」のような存在ですが、多くの人が簡単すぎるパスワードを使用したり、複数のサービスで同じパスワードを使い回ししたりしています。

2024年の脅威統計

  • 80%以上のデータ漏洩がパスワード関連の問題に起因
  • 平均的なユーザーは100以上のオンラインアカウントを保有
  • "123456"や"password"が依然として最も使用されるパスワード
  • 91%のユーザーがパスワードの使い回しを行っている

これらの統計が示すように、パスワードセキュリティは個人だけでなく、企業や組織全体のセキュリティに直結する重要な課題となっています。

弱いパスワードのリスク

1. 一般的な弱いパスワードの例

# 最も危険なパスワード(2024年版)
1. 123456
2. password
3. 123456789
4. 12345678
5. qwerty
6. abc123
7. password123
8. admin
9. 1234567890
10. welcome

# 日本でよく使われる危険なパスワード
- nihon123
- sakura2024
- tokyo123
- user123
- guest

2. ブルートフォース攻撃への脆弱性

# 簡単なパスワードがどれだけ早く破られるかの例
import itertools
import time

def brute_force_simulation(target_password, charset, max_length):
    """ブルートフォース攻撃のシミュレーション"""
    attempts = 0
    start_time = time.time()

    for length in range(1, max_length + 1):
        for combination in itertools.product(charset, repeat=length):
            attempts += 1
            candidate = ''.join(combination)

            if candidate == target_password:
                elapsed = time.time() - start_time
                return attempts, elapsed

    return attempts, time.time() - start_time

# 数字のみのパスワード(4桁)
charset_numbers = '0123456789'
attempts, time_taken = brute_force_simulation('1234', charset_numbers, 4)
print(f"4桁数字パスワード '1234': {attempts}回の試行で{time_taken:.2f}秒")

# 英小文字のみ(6文字)
charset_letters = 'abcdefghijklmnopqrstuvwxyz'
attempts, time_taken = brute_force_simulation('password'[:6], charset_letters, 6)
print(f"英小文字6文字: 最大{26**6:,}通り")

# 混合文字(12文字)
charset_mixed = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()'
print(f"混合文字12文字: 約{len(charset_mixed)**12:.2e}通り(事実上破ることは不可能)")

解読時間の比較表

パスワードタイプ 文字数 組み合わせ数 破られる時間(最悪の場合)
数字のみ 6桁 1,000,000 瞬時
英小文字のみ 8文字 208億 数分
英数字混合 8文字 2.8兆 数時間
英数字+記号 12文字 4.7×10²³ 数百万年

3. 辞書攻撃への脆弱性

# よく使われるパスワードリスト(辞書攻撃)
common_passwords = [
    "password", "123456", "password123", "admin", "qwerty",
    "letmein", "welcome", "monkey", "1234567890", "dragon",
    "123123", "111111", "654321", "sunshine", "master",
    "ashley", "bailey", "charlie", "jordan", "michael"
]

def dictionary_attack_simulation(target_password):
    """辞書攻撃のシミュレーション"""
    for i, password in enumerate(common_passwords):
        if password == target_password:
            return f"発見!{i+1}回目の試行で '{password}' が見つかりました"
    return "辞書にありませんでした"

# テスト
print(dictionary_attack_simulation("password"))  # すぐに見つかる
print(dictionary_attack_simulation("Tr0ub4dor&3"))  # 見つからない

強いパスワードの作成方法

1. パスフレーズ方式

# パスフレーズ生成の例
import random

# 日本語の単語リスト例
japanese_words = [
    "さくら", "やま", "うみ", "そら", "つき", "ほし", "かぜ", "ゆき",
    "はな", "みどり", "あお", "あか", "きいろ", "しろ", "くろ"
]

# 英語の単語リスト例
english_words = [
    "correct", "horse", "battery", "staple", "elephant", "rainbow",
    "keyboard", "mountain", "ocean", "forest", "castle", "bridge"
]

def generate_passphrase(words, count=4, separator="-", add_numbers=True):
    """パスフレーズの生成"""
    selected_words = random.sample(words, count)

    if add_numbers:
        # 数字を追加
        selected_words.append(str(random.randint(10, 99)))

    return separator.join(selected_words)

# 例
print("日本語パスフレーズ:", generate_passphrase(japanese_words, separator=""))
print("英語パスフレーズ:", generate_passphrase(english_words))
print("混合パスフレーズ:", generate_passphrase(english_words + japanese_words, 5))

# 出力例:
# 日本語パスフレーズ: さくらやまうみそら47
# 英語パスフレーズ: correct-horse-battery-staple-73
# 混合パスフレーズ: rainbow-やま-castle-みどり-keyboard-91

2. 高エントロピーパスワード生成

import secrets
import string

class SecurePasswordGenerator:
    def __init__(self):
        self.lowercase = string.ascii_lowercase
        self.uppercase = string.ascii_uppercase
        self.digits = string.digits
        self.symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?"

    def generate_password(self, length=16, include_symbols=True):
        """セキュアなパスワード生成"""
        charset = self.lowercase + self.uppercase + self.digits
        if include_symbols:
            charset += self.symbols

        # 各文字種を最低1つ含むことを保証
        password = [
            secrets.choice(self.lowercase),
            secrets.choice(self.uppercase),
            secrets.choice(self.digits)
        ]

        if include_symbols:
            password.append(secrets.choice(self.symbols))

        # 残りの文字をランダムに選択
        for _ in range(length - len(password)):
            password.append(secrets.choice(charset))

        # シャッフル
        secrets.SystemRandom().shuffle(password)

        return ''.join(password)

    def check_password_strength(self, password):
        """パスワード強度チェック"""
        score = 0
        feedback = []

        # 長さチェック
        if len(password) >= 12:
            score += 2
        elif len(password) >= 8:
            score += 1
        else:
            feedback.append("8文字以上にしてください")

        # 文字種チェック
        if any(c.islower() for c in password):
            score += 1
        else:
            feedback.append("小文字を含めてください")

        if any(c.isupper() for c in password):
            score += 1
        else:
            feedback.append("大文字を含めてください")

        if any(c.isdigit() for c in password):
            score += 1
        else:
            feedback.append("数字を含めてください")

        if any(c in self.symbols for c in password):
            score += 1
        else:
            feedback.append("記号を含めてください")

        # 一般的なパスワードチェック
        common_patterns = ['123', 'abc', 'password', 'qwerty']
        if any(pattern in password.lower() for pattern in common_patterns):
            score -= 2
            feedback.append("一般的なパターンを避けてください")

        # 強度判定
        if score >= 6:
            strength = "非常に強い"
        elif score >= 4:
            strength = "強い"
        elif score >= 2:
            strength = "普通"
        else:
            strength = "弱い"

        return {
            'strength': strength,
            'score': score,
            'feedback': feedback
        }

# 使用例
generator = SecurePasswordGenerator()

# セキュアなパスワード生成
strong_password = generator.generate_password(16, True)
print(f"生成されたパスワード: {strong_password}")

# パスワード強度チェック
result = generator.check_password_strength(strong_password)
print(f"強度: {result['strength']} (スコア: {result['score']})")

# 弱いパスワードのチェック
weak_result = generator.check_password_strength("password123")
print(f"弱いパスワードの評価: {weak_result}")

パスワード管理のベストプラクティス

1. パスワードマネージャーの活用

推奨パスワードマネージャー

個人向け:
- 1Password
- Bitwarden(オープンソース)
- LastPass
- Dashlane
- Keeper

企業向け:
- 1Password Business
- Bitwarden Business
- LastPass Business
- Okta
- Azure AD Password Protection

自作パスワードマネージャーの基本概念

import json
import hashlib
import secrets
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64

class SimplePasswordManager:
    def __init__(self, master_password):
        self.master_password = master_password
        self.salt = b'stable_salt_for_demo'  # 実際は動的に生成すべき
        self.key = self._derive_key(master_password, self.salt)
        self.fernet = Fernet(self.key)
        self.passwords = {}

    def _derive_key(self, password, salt):
        """マスターパスワードから暗号化キーを導出"""
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
        return key

    def add_password(self, service, username, password):
        """パスワードを暗号化して保存"""
        data = {
            'username': username,
            'password': password,
            'created_at': secrets.token_hex(8)  # タイムスタンプの代わり
        }
        encrypted_data = self.fernet.encrypt(json.dumps(data).encode())
        self.passwords[service] = encrypted_data
        print(f"✅ {service} のパスワードを保存しました")

    def get_password(self, service):
        """パスワードを復号化して取得"""
        if service not in self.passwords:
            print(f"❌ {service} のパスワードは見つかりません")
            return None

        try:
            encrypted_data = self.passwords[service]
            decrypted_data = self.fernet.decrypt(encrypted_data)
            data = json.loads(decrypted_data.decode())
            return data
        except Exception as e:
            print(f"❌ パスワードの復号化に失敗: {e}")
            return None

    def list_services(self):
        """保存されているサービス一覧"""
        return list(self.passwords.keys())

    def generate_password(self, length=16):
        """セキュアなパスワード生成"""
        charset = (
            string.ascii_lowercase + 
            string.ascii_uppercase + 
            string.digits + 
            "!@#$%^&*"
        )
        return ''.join(secrets.choice(charset) for _ in range(length))

    def export_encrypted(self):
        """暗号化されたデータのエクスポート"""
        return {service: base64.b64encode(data).decode() 
                for service, data in self.passwords.items()}

    def import_encrypted(self, data):
        """暗号化されたデータのインポート"""
        self.passwords = {service: base64.b64decode(encrypted_data.encode()) 
                         for service, encrypted_data in data.items()}

# 使用例
master_password = "My-Super-Secure-Master-Password-2024!"
pm = SimplePasswordManager(master_password)

# パスワード生成と保存
generated_pw = pm.generate_password(20)
pm.add_password("GitHub", "myusername", generated_pw)
pm.add_password("Gmail", "myemail@gmail.com", "Another-Strong-Password!")

# パスワード取得
github_creds = pm.get_password("GitHub")
if github_creds:
    print(f"GitHub - ユーザー名: {github_creds['username']}")
    print(f"GitHub - パスワード: {github_creds['password']}")

# サービス一覧
print(f"保存されているサービス: {pm.list_services()}")

2. 二要素認証(2FA)の実装

# TOTP(Time-based One-time Password)の実装
import pyotp
import qrcode
from io import BytesIO
import base64

class TwoFactorAuth:
    def __init__(self):
        self.secret = pyotp.random_base32()
        self.totp = pyotp.TOTP(self.secret)

    def get_qr_code(self, account_name, issuer_name):
        """QRコード生成"""
        provisioning_uri = self.totp.provisioning_uri(
            name=account_name,
            issuer_name=issuer_name
        )

        qr = qrcode.QRCode(version=1, box_size=10, border=5)
        qr.add_data(provisioning_uri)
        qr.make(fit=True)

        return qr.make_image(fill_color="black", back_color="white")

    def generate_token(self):
        """現在のTOTPトークン生成"""
        return self.totp.now()

    def verify_token(self, token):
        """TOTPトークン検証"""
        return self.totp.verify(token, valid_window=1)

    def get_backup_codes(self, count=10):
        """バックアップコード生成"""
        import secrets
        return [secrets.token_hex(4) for _ in range(count)]

# 使用例
def setup_2fa_for_user(username):
    """ユーザーの2FA設定"""
    tfa = TwoFactorAuth()

    print(f"🔐 {username} の2FA設定")
    print(f"シークレットキー: {tfa.secret}")

    # QRコード表示用(実際のアプリではファイル保存や画面表示)
    qr_image = tfa.get_qr_code(username, "MySecureApp")
    print("📱 認証アプリでQRコードをスキャンしてください")

    # バックアップコード生成
    backup_codes = tfa.get_backup_codes()
    print(f"🔑 バックアップコード(安全な場所に保管):")
    for i, code in enumerate(backup_codes, 1):
        print(f"  {i:2d}. {code}")

    return tfa

# Webアプリケーションでの2FA実装例
class SecureLoginSystem:
    def __init__(self):
        self.users = {}  # {username: {password_hash, totp_secret, backup_codes}}

    def register_user(self, username, password):
        """ユーザー登録"""
        import hashlib

        # パスワードハッシュ化
        password_hash = hashlib.pbkdf2_hmac(
            'sha256', 
            password.encode(), 
            username.encode(), 
            100000
        )

        # 2FA設定
        tfa = TwoFactorAuth()

        self.users[username] = {
            'password_hash': password_hash,
            'totp_secret': tfa.secret,
            'backup_codes': tfa.get_backup_codes(),
            'locked': False,
            'failed_attempts': 0
        }

        return tfa

    def authenticate(self, username, password, totp_token=None):
        """認証処理"""
        if username not in self.users:
            return {'success': False, 'message': 'ユーザーが存在しません'}

        user = self.users[username]

        # アカウントロックチェック
        if user['locked']:
            return {'success': False, 'message': 'アカウントがロックされています'}

        # パスワード確認
        import hashlib
        password_hash = hashlib.pbkdf2_hmac(
            'sha256', 
            password.encode(), 
            username.encode(), 
            100000
        )

        if password_hash != user['password_hash']:
            user['failed_attempts'] += 1
            if user['failed_attempts'] >= 5:
                user['locked'] = True
                return {'success': False, 'message': 'アカウントがロックされました'}
            return {'success': False, 'message': 'パスワードが incorrect'}

        # 2FA確認
        if totp_token:
            totp = pyotp.TOTP(user['totp_secret'])
            if not totp.verify(totp_token, valid_window=1):
                return {'success': False, 'message': '2FAトークンが無効です'}
        else:
            return {'success': False, 'message': '2FAトークンが必要です'}

        # 認証成功
        user['failed_attempts'] = 0
        return {'success': True, 'message': 'ログイン成功'}

# 使用例
login_system = SecureLoginSystem()

# ユーザー登録
tfa = login_system.register_user("alice", "StrongPassword123!")
current_token = tfa.generate_token()

# ログイン試行
result = login_system.authenticate("alice", "StrongPassword123!", current_token)
print(result)

3. Web開発者向けパスワード実装

# Flask + Argon2 での安全なパスワード実装
from flask import Flask, request, jsonify, session
from argon2 import PasswordHasher, exceptions
from argon2.low_level import Type
import secrets
import time
import redis

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

# Redis for rate limiting
redis_client = redis.Redis(host='localhost', port=6379, db=0)

class SecurePasswordManager:
    def __init__(self):
        # Argon2設定(2024年推奨値)
        self.ph = PasswordHasher(
            time_cost=3,       # 反復回数
            memory_cost=65536, # メモリ使用量 (64MB)
            parallelism=1,     # 並列度
            hash_len=32,       # ハッシュ長
            salt_len=16,       # ソルト長
            encoding='utf-8',  # エンコーディング
            type=Type.ID       # Argon2id
        )

    def hash_password(self, password):
        """パスワードのハッシュ化"""
        return self.ph.hash(password)

    def verify_password(self, hashed_password, password):
        """パスワード検証"""
        try:
            self.ph.verify(hashed_password, password)
            return True
        except exceptions.VerifyMismatchError:
            return False
        except exceptions.InvalidHash:
            return False

    def need_rehash(self, hashed_password):
        """ハッシュの再生成が必要かチェック"""
        return self.ph.check_needs_rehash(hashed_password)

password_manager = SecurePasswordManager()

class RateLimiter:
    def __init__(self, redis_client):
        self.redis = redis_client

    def is_rate_limited(self, identifier, max_attempts=5, window=300):
        """レート制限チェック(5回/5分)"""
        key = f"rate_limit:{identifier}"
        current = self.redis.get(key)

        if current is None:
            self.redis.setex(key, window, 1)
            return False

        if int(current) >= max_attempts:
            return True

        self.redis.incr(key)
        return False

    def reset_rate_limit(self, identifier):
        """レート制限リセット"""
        key = f"rate_limit:{identifier}"
        self.redis.delete(key)

rate_limiter = RateLimiter(redis_client)

@app.route('/register', methods=['POST'])
def register():
    """ユーザー登録エンドポイント"""
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    # 入力検証
    if not username or not password:
        return jsonify({'error': 'ユーザー名とパスワードが必要です'}), 400

    # パスワード強度チェック
    strength_result = check_password_strength(password)
    if strength_result['score'] < 4:
        return jsonify({
            'error': 'パスワードが弱すぎます',
            'feedback': strength_result['feedback']
        }), 400

    # レート制限
    if rate_limiter.is_rate_limited(request.remote_addr):
        return jsonify({'error': 'レート制限に達しました'}), 429

    try:
        # パスワードハッシュ化
        hashed_password = password_manager.hash_password(password)

        # データベース保存(例)
        # save_user_to_db(username, hashed_password)

        return jsonify({'message': 'ユーザー登録が完了しました'}), 201

    except Exception as e:
        return jsonify({'error': 'サーバーエラーが発生しました'}), 500

@app.route('/login', methods=['POST'])
def login():
    """ログインエンドポイント"""
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    # レート制限
    if rate_limiter.is_rate_limited(f"login:{username}"):
        return jsonify({'error': 'ログイン試行回数が上限に達しました'}), 429

    # ユーザー取得(例)
    # user = get_user_from_db(username)
    # stored_hash = user['password_hash']

    # デモ用(実際はDBから取得)
    stored_hash = "$argon2id$v=19$m=65536,t=3,p=1$..."

    # パスワード検証
    if password_manager.verify_password(stored_hash, password):
        # ログイン成功
        session['user_id'] = username
        rate_limiter.reset_rate_limit(f"login:{username}")

        # ハッシュ再生成チェック
        if password_manager.need_rehash(stored_hash):
            new_hash = password_manager.hash_password(password)
            # update_user_password_hash(username, new_hash)

        return jsonify({'message': 'ログイン成功'}), 200
    else:
        return jsonify({'error': 'ユーザー名またはパスワードが無効です'}), 401

def check_password_strength(password):
    """パスワード強度チェック関数"""
    score = 0
    feedback = []

    # 長さチェック
    if len(password) >= 12:
        score += 2
    elif len(password) >= 8:
        score += 1
    else:
        feedback.append("8文字以上にしてください")

    # 文字種チェック
    if any(c.islower() for c in password):
        score += 1
    else:
        feedback.append("小文字を含めてください")

    if any(c.isupper() for c in password):
        score += 1
    else:
        feedback.append("大文字を含めてください")

    if any(c.isdigit() for c in password):
        score += 1
    else:
        feedback.append("数字を含めてください")

    if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
        score += 1
    else:
        feedback.append("記号を含めてください")

    # 一般的なパスワードチェック
    common_passwords = ['password', '123456', 'qwerty', 'admin']
    if password.lower() in common_passwords:
        score -= 2
        feedback.append("一般的なパスワードは使用できません")

    return {'score': score, 'feedback': feedback}

if __name__ == '__main__':
    app.run(debug=True)

パスワードポリシーの設計

1. 組織向けパスワードポリシー例

# password-policy.yml
organization: "MyCompany"
effective_date: "2024-01-01"

password_requirements:
  minimum_length: 12
  maximum_length: 128
  character_sets:
    require_lowercase: true
    require_uppercase: true
    require_digits: true
    require_symbols: true
    allowed_symbols: "!@#$%^&*()_+-=[]{}|;:,.<>?"

  complexity:
    no_dictionary_words: true
    no_personal_info: true  # 名前、誕生日等
    no_keyboard_patterns: true  # qwerty, 123456等
    no_repeated_characters: 3  # 同じ文字3回連続禁止

  history:
    remember_previous: 12  # 過去12個のパスワードは再利用禁止

  expiration:
    max_age_days: 90  # 90日で強制変更
    warning_days: 14  # 14日前から警告表示

  lockout:
    max_failed_attempts: 5
    lockout_duration_minutes: 30
    observation_window_minutes: 15

multi_factor_authentication:
  required_for: ["admin", "finance", "hr"]
  methods: ["totp", "sms", "hardware_token"]
  backup_codes: 10

privileged_accounts:
  password_requirements:
    minimum_length: 16
    max_age_days: 30
  mfa_required: true
  session_timeout_minutes: 30

2. パスワードポリシーエンジン

import re
import datetime
from typing import List, Dict

class PasswordPolicy:
    def __init__(self, policy_config):
        self.config = policy_config
        self.common_passwords = self._load_common_passwords()

    def _load_common_passwords(self):
        """一般的なパスワードリストの読み込み"""
        # 実際は外部ファイルから読み込み
        return [
            "password", "123456", "password123", "admin", "qwerty",
            "letmein", "welcome", "monkey", "1234567890", "dragon"
        ]

    def validate_password(self, password: str, user_info: Dict = None) -> Dict:
        """包括的なパスワード検証"""
        errors = []
        warnings = []
        score = 0

        # 長さチェック
        min_len = self.config['password_requirements']['minimum_length']
        max_len = self.config['password_requirements']['maximum_length']

        if len(password) < min_len:
            errors.append(f"パスワードは{min_len}文字以上である必要があります")
        elif len(password) > max_len:
            errors.append(f"パスワードは{max_len}文字以下である必要があります")
        else:
            score += 2

        # 文字種チェック
        char_sets = self.config['password_requirements']['character_sets']

        if char_sets['require_lowercase'] and not re.search(r'[a-z]', password):
            errors.append("小文字を含める必要があります")
        else:
            score += 1

        if char_sets['require_uppercase'] and not re.search(r'[A-Z]', password):
            errors.append("大文字を含める必要があります")
        else:
            score += 1

        if char_sets['require_digits'] and not re.search(r'[0-9]', password):
            errors.append("数字を含める必要があります")
        else:
            score += 1

        if char_sets['require_symbols']:
            allowed_symbols = char_sets['allowed_symbols']
            if not re.search(f'[{re.escape(allowed_symbols)}]', password):
                errors.append(f"記号({allowed_symbols})を含める必要があります")
            else:
                score += 1

        # 複雑性チェック
        complexity = self.config['password_requirements']['complexity']

        if complexity['no_dictionary_words']:
            if password.lower() in self.common_passwords:
                errors.append("一般的なパスワードは使用できません")

        if complexity['no_keyboard_patterns']:
            keyboard_patterns = ['qwerty', '123456', 'asdfgh', 'zxcvbn']
            if any(pattern in password.lower() for pattern in keyboard_patterns):
                errors.append("キーボードパターンは使用できません")

        if complexity['no_repeated_characters']:
            max_repeat = complexity['no_repeated_characters']
            if re.search(f'(.)\\1{{{max_repeat-1},}}', password):
                errors.append(f"同じ文字を{max_repeat}回以上連続して使用できません")

        # 個人情報チェック
        if user_info and complexity['no_personal_info']:
            personal_data = [
                user_info.get('username', ''),
                user_info.get('email', '').split('@')[0],
                user_info.get('first_name', ''),
                user_info.get('last_name', ''),
                str(user_info.get('birth_year', ''))
            ]

            for data in personal_data:
                if data and data.lower() in password.lower():
                    errors.append("個人情報を含むパスワードは使用できません")
                    break

        # エントロピー計算
        entropy = self._calculate_entropy(password)
        if entropy < 50:
            warnings.append("パスワードの複雑さが不十分です")
        else:
            score += 1

        # 総合評価
        if len(errors) == 0:
            if score >= 6:
                strength = "非常に強い"
            elif score >= 4:
                strength = "強い"
            else:
                strength = "普通"
        else:
            strength = "弱い"

        return {
            'valid': len(errors) == 0,
            'strength': strength,
            'score': score,
            'entropy': entropy,
            'errors': errors,
            'warnings': warnings
        }

    def _calculate_entropy(self, password: str) -> float:
        """パスワードのエントロピー計算"""
        charset_size = 0

        if re.search(r'[a-z]', password):
            charset_size += 26
        if re.search(r'[A-Z]', password):
            charset_size += 26
        if re.search(r'[0-9]', password):
            charset_size += 10
        if re.search(r'[^a-zA-Z0-9]', password):
            charset_size += 32  # 一般的な記号の数

        import math
        if charset_size > 0:
            return len(password) * math.log2(charset_size)
        return 0

    def check_password_age(self, created_date: datetime.datetime) -> Dict:
        """パスワード有効期限チェック"""
        max_age = self.config['password_requirements']['expiration']['max_age_days']
        warning_days = self.config['password_requirements']['expiration']['warning_days']

        age = (datetime.datetime.now() - created_date).days
        days_until_expiry = max_age - age

        if days_until_expiry <= 0:
            return {'expired': True, 'days_until_expiry': days_until_expiry}
        elif days_until_expiry <= warning_days:
            return {'expired': False, 'warning': True, 'days_until_expiry': days_until_expiry}
        else:
            return {'expired': False, 'warning': False, 'days_until_expiry': days_until_expiry}

# 使用例
policy_config = {
    'password_requirements': {
        'minimum_length': 12,
        'maximum_length': 128,
        'character_sets': {
            'require_lowercase': True,
            'require_uppercase': True,
            'require_digits': True,
            'require_symbols': True,
            'allowed_symbols': '!@#$%^&*()_+-=[]{}|;:,.<>?'
        },
        'complexity': {
            'no_dictionary_words': True,
            'no_personal_info': True,
            'no_keyboard_patterns': True,
            'no_repeated_characters': 3
        },
        'expiration': {
            'max_age_days': 90,
            'warning_days': 14
        }
    }
}

policy = PasswordPolicy(policy_config)

# テスト
user_info = {
    'username': 'johndoe',
    'email': 'john.doe@example.com',
    'first_name': 'John',
    'last_name': 'Doe',
    'birth_year': 1990
}

test_passwords = [
    "password123",  # 弱い
    "Password123!",  # 普通
    "MySuper$ecureP@ssw0rd2024!"  # 強い
]

for pwd in test_passwords:
    result = policy.validate_password(pwd, user_info)
    print(f"パスワード: {pwd}")
    print(f"有効: {result['valid']}, 強度: {result['strength']}")
    if result['errors']:
        print(f"エラー: {result['errors']}")
    if result['warnings']:
        print(f"警告: {result['warnings']}")
    print(f"エントロピー: {result['entropy']:.1f} bits\n")

セキュリティインシデントと対策

実際の被害事例

ケーススタディ1:クレデンシャルスタッフィング攻撃

# 攻撃検知システムの例
import collections
import time
from datetime import datetime, timedelta

class CredentialStuffingDetector:
    def __init__(self):
        self.login_attempts = collections.defaultdict(list)
        self.failed_attempts = collections.defaultdict(int)
        self.ip_attempts = collections.defaultdict(list)

    def record_login_attempt(self, username, ip_address, success, user_agent):
        """ログイン試行の記録"""
        timestamp = time.time()

        # ユーザー別の試行記録
        self.login_attempts[username].append({
            'timestamp': timestamp,
            'ip': ip_address,
            'success': success,
            'user_agent': user_agent
        })

        # IP別の試行記録
        self.ip_attempts[ip_address].append({
            'timestamp': timestamp,
            'username': username,
            'success': success
        })

        if not success:
            self.failed_attempts[username] += 1

        # 異常検知
        self._detect_anomalies(username, ip_address)

    def _detect_anomalies(self, username, ip_address):
        """異常パターンの検知"""
        alerts = []

        # 1. 短時間での大量ログイン試行
        recent_attempts = [
            attempt for attempt in self.ip_attempts[ip_address]
            if time.time() - attempt['timestamp'] < 300  # 5分以内
        ]

        if len(recent_attempts) > 20:
            alerts.append({
                'type': 'HIGH_FREQUENCY_ATTEMPTS',
                'ip': ip_address,
                'count': len(recent_attempts),
                'severity': 'HIGH'
            })

        # 2. 複数ユーザーアカウントへの試行
        unique_users = set(attempt['username'] for attempt in recent_attempts)
        if len(unique_users) > 5:
            alerts.append({
                'type': 'MULTIPLE_ACCOUNT_TARGETING',
                'ip': ip_address,
                'targeted_accounts': len(unique_users),
                'severity': 'HIGH'
            })

        # 3. 地理的異常(通常とは異なる場所からのアクセス)
        # 実装は省略(GeoIPデータベースが必要)

        # 4. User-Agentパターン分析
        user_agents = [
            attempt.get('user_agent', '') 
            for attempt in self.login_attempts[username][-10:]
        ]
        if len(set(user_agents)) == 1 and len(user_agents) > 5:
            alerts.append({
                'type': 'SUSPICIOUS_USER_AGENT',
                'username': username,
                'user_agent': user_agents[0],
                'severity': 'MEDIUM'
            })

        # アラート送信
        for alert in alerts:
            self._send_alert(alert)

    def _send_alert(self, alert):
        """アラート送信"""
        print(f"🚨 セキュリティアラート: {alert['type']}")
        print(f"重要度: {alert['severity']}")
        print(f"詳細: {alert}")

        # 実際の実装では、Slack、メール、SIEM等に送信

# 使用例
detector = CredentialStuffingDetector()

# 正常なログイン
detector.record_login_attempt("alice", "192.168.1.100", True, "Mozilla/5.0 ...")

# 攻撃パターンのシミュレーション
attack_ip = "10.0.0.1"
usernames = ["admin", "root", "user", "test", "guest", "alice", "bob"]
for username in usernames:
    for _ in range(5):
        detector.record_login_attempt(username, attack_ip, False, "python-requests/2.28.1")

まとめ

パスワードセキュリティは、すべてのデジタルセキュリティの基盤となる重要な要素です。

重要なポイント:

  1. 強力なパスワード作成:長さ、複雑さ、一意性の確保
  2. パスワードマネージャー使用:安全な保管と管理
  3. 二要素認証(2FA):パスワード単体に依存しない
  4. 適切なポリシー設計:組織レベルでの統一ルール
  5. 継続的な監視:異常なアクセスパターンの検知
  6. 定期的な更新:セキュリティ状況に応じた見直し

特に重要なのは、パスワードだけに頼らないセキュリティ設計です。多要素認証、行動分析、リスクベース認証などを組み合わせることで、より堅牢なセキュリティを実現できます。

Web開発者の皆さんは、ユーザーのパスワードを預かる責任の重さを理解し、最新のセキュリティ技術を活用して、安全なシステムを構築していきましょう。


コメント

タイトルとURLをコピーしました