[PHP]password_get_info関数を完全解説!パスワードハッシュの情報を取得する方法

PHP

こんにちは!今回はPHPのパスワード管理に欠かせない関数の一つ、「password_get_info」について詳しく解説していきます。

password_get_info関数とは?

password_get_infoは、パスワードハッシュから使用されているアルゴリズムやオプション情報を取得する関数です。既存のハッシュを分析したり、マイグレーションの計画を立てたりする際に非常に役立ちます。

基本的な使い方

構文

password_get_info(string $hash): array

パラメータ

  • $hash: 解析したいパスワードハッシュ文字列

戻り値

以下のキーを持つ連想配列:

  • algo: アルゴリズムのID(整数)
  • algoName: アルゴリズムの名前(文字列)
  • options: ハッシュ生成時に使用されたオプション(配列)

基本的な使用例

<?php
// パスワードをハッシュ化
$password = "my_secure_password";
$hash = password_hash($password, PASSWORD_BCRYPT);

// ハッシュの情報を取得
$info = password_get_info($hash);

print_r($info);
/*
出力例:
Array
(
    [algo] => 1
    [algoName] => bcrypt
    [options] => Array
        (
            [cost] => 10
        )
)
*/

echo "アルゴリズム: " . $info['algoName'] . "\n";
echo "コスト: " . $info['options']['cost'] . "\n";
?>

アルゴリズムIDと名前の対応

<?php
// アルゴリズム定数とIDの対応表
$algorithmMap = [
    PASSWORD_BCRYPT => [
        'id' => 1,
        'name' => 'bcrypt',
        'string_id' => '2y'
    ],
    PASSWORD_ARGON2I => [
        'id' => 2,
        'name' => 'argon2i',
        'string_id' => 'argon2i'
    ],
    PASSWORD_ARGON2ID => [
        'id' => 3,
        'name' => 'argon2id',
        'string_id' => 'argon2id'
    ]
];

// 各アルゴリズムの情報を表示
foreach ($algorithmMap as $constant => $details) {
    echo "定数ID: {$details['id']}, 名前: {$details['name']}\n";
}
?>

実践的な使用例

例1: ハッシュアルゴリズムの判定

<?php
class PasswordHashAnalyzer {
    /**
     * ハッシュのアルゴリズムを判定
     */
    public static function getAlgorithm($hash) {
        $info = password_get_info($hash);
        return $info['algoName'];
    }
    
    /**
     * アルゴリズムが指定されたものか確認
     */
    public static function isAlgorithm($hash, $expectedAlgo) {
        $info = password_get_info($hash);
        
        $algoNames = [
            PASSWORD_BCRYPT => 'bcrypt',
            PASSWORD_ARGON2I => 'argon2i',
            PASSWORD_ARGON2ID => 'argon2id'
        ];
        
        $expectedName = $algoNames[$expectedAlgo] ?? null;
        
        return $info['algoName'] === $expectedName;
    }
    
    /**
     * 詳細な分析レポート
     */
    public static function analyze($hash) {
        $info = password_get_info($hash);
        
        $report = [
            'algorithm' => $info['algoName'],
            'algorithm_id' => $info['algo'],
            'options' => $info['options'],
            'hash_length' => strlen($hash),
            'is_secure' => self::isSecure($info)
        ];
        
        return $report;
    }
    
    /**
     * セキュリティレベルを評価
     */
    private static function isSecure($info) {
        switch ($info['algoName']) {
            case 'argon2id':
            case 'argon2i':
                return true;
            case 'bcrypt':
                // costが12以上なら安全と判定
                return isset($info['options']['cost']) && 
                       $info['options']['cost'] >= 12;
            default:
                return false;
        }
    }
}

// 使用例
$hash = password_hash("password123", PASSWORD_BCRYPT, ['cost' => 12]);

echo "アルゴリズム: " . PasswordHashAnalyzer::getAlgorithm($hash) . "\n";

if (PasswordHashAnalyzer::isAlgorithm($hash, PASSWORD_BCRYPT)) {
    echo "このハッシュはBCryptを使用しています\n";
}

$report = PasswordHashAnalyzer::analyze($hash);
print_r($report);
?>

例2: データベース内のハッシュを監査

<?php
class DatabasePasswordAuditor {
    /**
     * 複数のハッシュを分析
     */
    public static function auditHashes($hashes) {
        $statistics = [
            'total' => count($hashes),
            'algorithms' => [],
            'weak_hashes' => [],
            'strong_hashes' => [],
            'unknown_hashes' => []
        ];
        
        foreach ($hashes as $userId => $hash) {
            $info = password_get_info($hash);
            
            // アルゴリズムごとにカウント
            $algo = $info['algoName'];
            if (!isset($statistics['algorithms'][$algo])) {
                $statistics['algorithms'][$algo] = [
                    'count' => 0,
                    'user_ids' => []
                ];
            }
            $statistics['algorithms'][$algo]['count']++;
            $statistics['algorithms'][$algo]['user_ids'][] = $userId;
            
            // セキュリティレベルを判定
            if (self::isWeakHash($info)) {
                $statistics['weak_hashes'][] = [
                    'user_id' => $userId,
                    'algorithm' => $algo,
                    'reason' => self::getWeakReason($info)
                ];
            } else {
                $statistics['strong_hashes'][] = $userId;
            }
        }
        
        return $statistics;
    }
    
    /**
     * 弱いハッシュかどうかを判定
     */
    private static function isWeakHash($info) {
        if ($info['algoName'] === 'unknown') {
            return true;
        }
        
        if ($info['algoName'] === 'bcrypt') {
            $cost = $info['options']['cost'] ?? 10;
            return $cost < 12;
        }
        
        if ($info['algoName'] === 'argon2i' || $info['algoName'] === 'argon2id') {
            $memoryCost = $info['options']['memory_cost'] ?? 0;
            return $memoryCost < 65536; // 64MB未満
        }
        
        return false;
    }
    
    /**
     * 弱い理由を取得
     */
    private static function getWeakReason($info) {
        if ($info['algoName'] === 'unknown') {
            return '不明なアルゴリズム';
        }
        
        if ($info['algoName'] === 'bcrypt') {
            $cost = $info['options']['cost'] ?? 10;
            return "BCrypt cost値が低い (現在: {$cost}, 推奨: 12以上)";
        }
        
        if ($info['algoName'] === 'argon2i' || $info['algoName'] === 'argon2id') {
            $memoryCost = $info['options']['memory_cost'] ?? 0;
            return "メモリコストが低い (現在: {$memoryCost}, 推奨: 65536以上)";
        }
        
        return '不明な理由';
    }
    
    /**
     * 監査レポートを表示
     */
    public static function displayAuditReport($hashes) {
        $stats = self::auditHashes($hashes);
        
        echo "=== パスワードハッシュ監査レポート ===\n\n";
        echo "総ハッシュ数: {$stats['total']}\n\n";
        
        echo "アルゴリズム分布:\n";
        foreach ($stats['algorithms'] as $algo => $data) {
            $percentage = round(($data['count'] / $stats['total']) * 100, 1);
            echo "  {$algo}: {$data['count']}件 ({$percentage}%)\n";
        }
        
        echo "\n強固なハッシュ: " . count($stats['strong_hashes']) . "件\n";
        echo "弱いハッシュ: " . count($stats['weak_hashes']) . "件\n";
        
        if (count($stats['weak_hashes']) > 0) {
            echo "\n⚠ 警告: 以下のユーザーは弱いハッシュを使用しています:\n";
            foreach ($stats['weak_hashes'] as $weak) {
                echo "  - ユーザーID {$weak['user_id']}: {$weak['reason']}\n";
            }
            echo "\n推奨アクション: これらのユーザーに次回ログイン時のパスワード再設定を促す\n";
        } else {
            echo "\n✓ すべてのハッシュが推奨基準を満たしています\n";
        }
    }
}

// 使用例: サンプルデータ
$userHashes = [
    1001 => password_hash("password1", PASSWORD_BCRYPT, ['cost' => 10]), // 弱い
    1002 => password_hash("password2", PASSWORD_BCRYPT, ['cost' => 12]), // 強い
    1003 => password_hash("password3", PASSWORD_BCRYPT, ['cost' => 14]), // 強い
];

// Argon2が使える場合
if (defined('PASSWORD_ARGON2ID')) {
    $userHashes[1004] = password_hash("password4", PASSWORD_ARGON2ID);
}

DatabasePasswordAuditor::displayAuditReport($userHashes);
?>

例3: マイグレーションの優先順位付け

<?php
class PasswordMigrationManager {
    /**
     * マイグレーションの優先順位を決定
     */
    public static function prioritizeMigration($hashes) {
        $priorities = [
            'critical' => [],  // 即座に更新が必要
            'high' => [],      // 優先的に更新
            'medium' => [],    // 通常の更新
            'low' => []        // 任意の更新
        ];
        
        foreach ($hashes as $userId => $hash) {
            $info = password_get_info($hash);
            $priority = self::calculatePriority($info);
            
            $priorities[$priority][] = [
                'user_id' => $userId,
                'algorithm' => $info['algoName'],
                'details' => $info
            ];
        }
        
        return $priorities;
    }
    
    /**
     * 優先度を計算
     */
    private static function calculatePriority($info) {
        // 不明なアルゴリズム = 最優先
        if ($info['algoName'] === 'unknown') {
            return 'critical';
        }
        
        // BCrypt
        if ($info['algoName'] === 'bcrypt') {
            $cost = $info['options']['cost'] ?? 10;
            if ($cost < 10) {
                return 'critical';
            } elseif ($cost < 12) {
                return 'high';
            } else {
                return 'medium'; // Argon2への移行を推奨
            }
        }
        
        // Argon2i (Argon2idへの移行を推奨)
        if ($info['algoName'] === 'argon2i') {
            return 'medium';
        }
        
        // Argon2id (最新)
        if ($info['algoName'] === 'argon2id') {
            $memoryCost = $info['options']['memory_cost'] ?? 65536;
            if ($memoryCost < 65536) {
                return 'high';
            } else {
                return 'low';
            }
        }
        
        return 'critical';
    }
    
    /**
     * マイグレーション計画を表示
     */
    public static function displayMigrationPlan($hashes) {
        $priorities = self::prioritizeMigration($hashes);
        
        echo "=== パスワードマイグレーション計画 ===\n\n";
        
        $priorityLabels = [
            'critical' => '🔴 最優先',
            'high' => '🟠 高優先度',
            'medium' => '🟡 中優先度',
            'low' => '🟢 低優先度'
        ];
        
        foreach ($priorityLabels as $level => $label) {
            $count = count($priorities[$level]);
            echo "{$label}: {$count}件\n";
            
            if ($count > 0 && in_array($level, ['critical', 'high'])) {
                foreach ($priorities[$level] as $item) {
                    echo "  - ユーザーID {$item['user_id']}: {$item['algorithm']}\n";
                }
            }
        }
        
        echo "\n=== 推奨アクション ===\n";
        
        if (count($priorities['critical']) > 0) {
            echo "1. 最優先ユーザー({$priorities['critical']}件): 即座にパスワードリセットを要求\n";
        }
        
        if (count($priorities['high']) > 0) {
            echo "2. 高優先度ユーザー({$priorities['high']}件): 次回ログイン時に自動再ハッシュ\n";
        }
        
        if (count($priorities['medium']) > 0) {
            echo "3. 中優先度ユーザー({$priorities['medium']}件): ログイン時に順次更新\n";
        }
        
        if (count($priorities['low']) > 0) {
            echo "4. 低優先度ユーザー({$priorities['low']}件): 現状維持または任意更新\n";
        }
    }
}

// 使用例
$userHashes = [
    2001 => password_hash("pass1", PASSWORD_BCRYPT, ['cost' => 8]),  // critical
    2002 => password_hash("pass2", PASSWORD_BCRYPT, ['cost' => 10]), // high
    2003 => password_hash("pass3", PASSWORD_BCRYPT, ['cost' => 12]), // medium
];

PasswordMigrationManager::displayMigrationPlan($userHashes);
?>

例4: ユーザーログイン時の自動再ハッシュ

<?php
class SecureLoginHandler {
    private $db; // データベース接続(仮)
    
    /**
     * ログイン処理と自動再ハッシュ
     */
    public function login($username, $password) {
        // データベースからハッシュを取得(仮)
        $storedHash = $this->getUserPasswordHash($username);
        
        if (!$storedHash) {
            return ['success' => false, 'message' => 'ユーザーが見つかりません'];
        }
        
        // パスワード検証
        if (!password_verify($password, $storedHash)) {
            return ['success' => false, 'message' => 'パスワードが間違っています'];
        }
        
        // ログイン成功 - ハッシュの更新が必要かチェック
        $needsRehash = $this->shouldRehash($storedHash);
        
        if ($needsRehash) {
            $this->rehashPassword($username, $password);
            $message = 'ログイン成功 (パスワードハッシュを更新しました)';
        } else {
            $message = 'ログイン成功';
        }
        
        return ['success' => true, 'message' => $message];
    }
    
    /**
     * 再ハッシュが必要かチェック
     */
    private function shouldRehash($hash) {
        $info = password_get_info($hash);
        
        // 最適なアルゴリズムを取得
        $bestAlgo = $this->getBestAlgorithm();
        
        // 標準の再ハッシュチェック
        if (password_needs_rehash($hash, $bestAlgo)) {
            return true;
        }
        
        // 追加チェック: BCryptのcost値
        if ($info['algoName'] === 'bcrypt') {
            $currentCost = $info['options']['cost'] ?? 10;
            return $currentCost < 12;
        }
        
        // 追加チェック: Argon2のメモリコスト
        if ($info['algoName'] === 'argon2i' || $info['algoName'] === 'argon2id') {
            $currentMemory = $info['options']['memory_cost'] ?? 0;
            return $currentMemory < 65536;
        }
        
        return false;
    }
    
    /**
     * パスワードを再ハッシュ
     */
    private function rehashPassword($username, $password) {
        $algo = $this->getBestAlgorithm();
        $options = $this->getRecommendedOptions($algo);
        
        $newHash = password_hash($password, $algo, $options);
        
        // データベースを更新(仮)
        $this->updateUserPasswordHash($username, $newHash);
        
        // ログに記録
        error_log("Password rehashed for user: {$username} using " . 
                  password_get_info($newHash)['algoName']);
    }
    
    /**
     * 最適なアルゴリズムを取得
     */
    private function getBestAlgorithm() {
        if (defined('PASSWORD_ARGON2ID') && 
            in_array('argon2id', password_algos())) {
            return PASSWORD_ARGON2ID;
        } elseif (defined('PASSWORD_ARGON2I') && 
                  in_array('argon2i', password_algos())) {
            return PASSWORD_ARGON2I;
        } else {
            return PASSWORD_BCRYPT;
        }
    }
    
    /**
     * 推奨オプションを取得
     */
    private function getRecommendedOptions($algo) {
        if ($algo === PASSWORD_ARGON2ID || $algo === PASSWORD_ARGON2I) {
            return [
                'memory_cost' => 65536,  // 64MB
                'time_cost' => 4,
                'threads' => 2
            ];
        } elseif ($algo === PASSWORD_BCRYPT) {
            return ['cost' => 12];
        }
        
        return [];
    }
    
    // ダミーメソッド(実際はデータベースアクセス)
    private function getUserPasswordHash($username) {
        // 実装例
        return password_hash("correct_password", PASSWORD_BCRYPT, ['cost' => 10]);
    }
    
    private function updateUserPasswordHash($username, $hash) {
        // 実装例: UPDATE users SET password_hash = ? WHERE username = ?
    }
}

// 使用例
$loginHandler = new SecureLoginHandler();
$result = $loginHandler->login("testuser", "correct_password");
echo $result['message'] . "\n";
?>

例5: ハッシュ情報のダッシュボード

<?php
class PasswordSecurityDashboard {
    /**
     * セキュリティダッシュボードを生成
     */
    public static function generateDashboard($hashes) {
        $data = self::collectStatistics($hashes);
        
        echo "╔════════════════════════════════════════════════╗\n";
        echo "║   パスワードセキュリティダッシュボード         ║\n";
        echo "╚════════════════════════════════════════════════╝\n\n";
        
        // 総数
        echo "📊 総ユーザー数: {$data['total']}\n\n";
        
        // アルゴリズム分布
        echo "🔐 アルゴリズム分布:\n";
        foreach ($data['algorithms'] as $algo => $count) {
            $percentage = round(($count / $data['total']) * 100, 1);
            $bar = self::createProgressBar($percentage);
            echo "  {$algo}: {$bar} {$percentage}% ({$count}件)\n";
        }
        
        echo "\n";
        
        // セキュリティスコア
        $score = self::calculateSecurityScore($data);
        echo "🛡️  総合セキュリティスコア: {$score}/100\n";
        echo self::getSecurityRating($score) . "\n\n";
        
        // 推奨事項
        echo "💡 推奨事項:\n";
        $recommendations = self::getRecommendations($data);
        foreach ($recommendations as $i => $rec) {
            echo "  " . ($i + 1) . ". {$rec}\n";
        }
    }
    
    /**
     * 統計情報を収集
     */
    private static function collectStatistics($hashes) {
        $data = [
            'total' => count($hashes),
            'algorithms' => [],
            'weak_count' => 0,
            'strong_count' => 0
        ];
        
        foreach ($hashes as $hash) {
            $info = password_get_info($hash);
            $algo = $info['algoName'];
            
            if (!isset($data['algorithms'][$algo])) {
                $data['algorithms'][$algo] = 0;
            }
            $data['algorithms'][$algo]++;
            
            // 強度判定
            if (self::isStrongHash($info)) {
                $data['strong_count']++;
            } else {
                $data['weak_count']++;
            }
        }
        
        return $data;
    }
    
    /**
     * 強固なハッシュか判定
     */
    private static function isStrongHash($info) {
        if ($info['algoName'] === 'argon2id') {
            return true;
        }
        
        if ($info['algoName'] === 'argon2i') {
            return true;
        }
        
        if ($info['algoName'] === 'bcrypt') {
            $cost = $info['options']['cost'] ?? 10;
            return $cost >= 12;
        }
        
        return false;
    }
    
    /**
     * セキュリティスコアを計算
     */
    private static function calculateSecurityScore($data) {
        $score = 0;
        
        // 強固なハッシュの割合(最大70点)
        if ($data['total'] > 0) {
            $strongRatio = $data['strong_count'] / $data['total'];
            $score += $strongRatio * 70;
        }
        
        // Argon2の使用率(最大30点)
        $argon2Count = ($data['algorithms']['argon2id'] ?? 0) + 
                       ($data['algorithms']['argon2i'] ?? 0);
        if ($data['total'] > 0) {
            $argon2Ratio = $argon2Count / $data['total'];
            $score += $argon2Ratio * 30;
        }
        
        return round($score);
    }
    
    /**
     * セキュリティ評価
     */
    private static function getSecurityRating($score) {
        if ($score >= 90) {
            return "  評価: ⭐⭐⭐⭐⭐ 優秀";
        } elseif ($score >= 75) {
            return "  評価: ⭐⭐⭐⭐ 良好";
        } elseif ($score >= 60) {
            return "  評価: ⭐⭐⭐ 普通";
        } elseif ($score >= 40) {
            return "  評価: ⭐⭐ 要改善";
        } else {
            return "  評価: ⭐ 危険";
        }
    }
    
    /**
     * プログレスバーを作成
     */
    private static function createProgressBar($percentage, $length = 20) {
        $filled = round(($percentage / 100) * $length);
        $empty = $length - $filled;
        return "[" . str_repeat("█", $filled) . str_repeat("░", $empty) . "]";
    }
    
    /**
     * 推奨事項を取得
     */
    private static function getRecommendations($data) {
        $recommendations = [];
        
        if ($data['weak_count'] > 0) {
            $recommendations[] = "{$data['weak_count']}件の弱いハッシュを更新してください";
        }
        
        $bcryptCount = $data['algorithms']['bcrypt'] ?? 0;
        if ($bcryptCount > $data['total'] * 0.5) {
            $recommendations[] = "BCryptからArgon2への移行を検討してください";
        }
        
        if (!isset($data['algorithms']['argon2id']) || $data['algorithms']['argon2id'] === 0) {
            $recommendations[] = "Argon2idの導入を推奨します";
        }
        
        if (empty($recommendations)) {
            $recommendations[] = "現在のセキュリティレベルを維持してください";
        }
        
        return $recommendations;
    }
}

// 使用例
$sampleHashes = [
    password_hash("pass1", PASSWORD_BCRYPT, ['cost' => 10]),
    password_hash("pass2", PASSWORD_BCRYPT, ['cost' => 12]),
    password_hash("pass3", PASSWORD_BCRYPT, ['cost' => 12]),
];

// Argon2が使える場合
if (defined('PASSWORD_ARGON2ID')) {
    $sampleHashes[] = password_hash("pass4", PASSWORD_ARGON2ID);
    $sampleHashes[] = password_hash("pass5", PASSWORD_ARGON2ID);
}

PasswordSecurityDashboard::generateDashboard($sampleHashes);
?>

よくある使用パターン

パターン1: ハッシュの検証とログ記録

<?php
function validateAndLogHash($hash, $userId) {
    $info = password_get_info($hash);
    
    $logEntry = [
        'timestamp' => date('Y-m-d H:i:s'),
        'user_id' => $userId,
        'algorithm' => $info['algoName'],
        'algorithm_id' => $info['algo'],
        'options' => json_encode($info['options'])
    ];
    
    // ログファイルに記録
    $logLine = implode(' | ', $logEntry) . "\n";
    file_put_contents('/var/log/password_hashes.log', $logLine, FILE_APPEND);
    
    return $info;
}
?>

パターン2: バッチ処理での分析

<?php
function batchAnalyzeHashes($hashes) {
    $results = [];
    
    foreach ($hashes as $id => $hash) {
        $info = password_get_info($hash);
        
        $results[$id] = [
            'algorithm' => $info['algoName'],
            'needs_update' => password_needs_rehash(
                $hash, 
                PASSWORD_ARGON2ID
            ),
            'options' => $info['options']
        ];
    }
    
    return $results;
}
?>

注意点とベストプラクティス

1. 不明なハッシュの処理

<?php
// 無効なハッシュを処理
$invalidHash = "invalid_hash_string";
$info = password_get_info($invalidHash);

print_r($info);
/*
Array
(
    [algo] => 0
    [algoName] => unknown
    [options] => Array()
)
*/

// 不明なハッシュのチェック
if ($info['algoName'] === 'unknown') {
    echo "警告: 無効なハッシュ形式です\n";
    // エラー処理やパスワードリセットを要求
}
?>

2. オプションの安全な取得

<?php
function getSafeOption($hash, $optionName, $default = null) {
    $info = password_get_info($hash);
    return $info['options'][$optionName] ?? $default;
}

$hash = password_hash("password", PASSWORD_BCRYPT);
$cost = getSafeOption($hash, 'cost', 10);
echo "Cost: {$cost}\n";
?>

まとめ

password_get_info関数は、パスワードハッシュの詳細情報を取得するための重要な関数です。

重要ポイント:

  • ハッシュの解析: アルゴリズムとオプションを簡単に取得
  • セキュリティ監査: 既存のハッシュ品質を評価
  • マイグレーション計画: アップグレードの優先順位付け
  • 自動再ハッシュ: ログイン時の段階的な更新

戻り値の構造:

[
    'algo' => 1,                    // アルゴリズムID
    'algoName' => 'bcrypt',         // アルゴリズム名
    'options' => ['cost' => 12]     // 使用されたオプション
]

実用的な活用方法:

  • ✅ データベース内のハッシュを定期的に監査
  • ✅ ログイン時に弱いハッシュを自動更新
  • ✅ セキュリティダッシュボードでの可視化
  • ✅ マイグレーション計画の作成と実行
  • ✅ ハッシュ形式の検証とログ記録

アルゴリズム判定の例:

$info = password_get_info($hash);

if ($info['algoName'] === 'unknown') {
    // 無効なハッシュ
} elseif ($info['algoName'] === 'bcrypt') {
    // BCrypt使用中
} elseif ($info['algoName'] === 'argon2id') {
    // Argon2id使用中(推奨)
}

パスワードセキュリティの維持・向上には、既存のハッシュ状況を正確に把握することが不可欠です。password_get_infoを活用して、常に最新のセキュリティ基準を満たすシステムを構築しましょう!


関連記事

  • password_hash() – パスワードをハッシュ化
  • password_verify() – パスワードを検証
  • password_needs_rehash() – 再ハッシュの必要性をチェック
  • password_algos() – 利用可能なアルゴリズムを取得
タイトルとURLをコピーしました