Практическое применение хешей на основе ID в PHP

Раздел: Безопасность PHP -> Безопасность и шифрование

Хеширование идентификаторов в PHP: обзор методов и безопасные подходы

Наиболее эффективное решение для создания защищённых, непредсказуемых хешей на основе ID - это использование HMAC (Hash-based Message Authentication Code) в комбинации с уникальной солью для каждого ID. Такой подход гарантирует, что одинаковые ID дадут разные хеши (если соли различаются), а знание алгоритма без секретного ключа не позволяет подделать хеш.


<?php
$secretKey = 'ваш_секретный_ключ_длиной_не_менее_32_символа';
$userId = 42;
$salt = bin2hex(random_bytes(16)); // случайная соль для каждого ID
$hash = hash_hmac('sha256', $userId . $salt, $secretKey);
echo $hash;
?>

Пояснение: hash_hmac с алгоритмом sha256 обеспечивает криптографическую стойкость. Соль делает хеш уникальным даже при одинаковом ID. Секретный ключ хранится на сервере и никогда не раскрывается.

Возможные проблемы и их решения:

  • Утечка секретного ключа - используйте переменные окружения или защищённое хранилище.
  • Слишком короткая соль - используйте random_bytes(16) (128 бит).
  • Перебор хешей при известной соли - атака невозможна без ключа.

Как создать хеш из ID без соли, но с секретным ключом?


$hash = hash_hmac('sha256', $userId, $secretKey);
echo $hash;

Этот вариант проще, но одинаковые ID дадут одинаковые хеши. Подходит, когда не требуется уникальность на каждый запрос, например, для подписи идентификаторов в API.

Если одинаковые хеши недопустимы (например, для уникальных ссылок), такой метод может привести к коллизиям. Решение - добавить метку времени или случайное число.

Как обеспечить уникальность хеша даже при одинаковом ID с помощью сочетания ID и соли?


$salt = bin2hex(random_bytes(8));
$hash = hash('sha256', $userId . $salt);
echo $hash;

Применение: генерация уникальных идентификаторов для URL или токенов, когда секретный ключ не требуется, но важна случайность. Недостаток: хеш можно подобрать через словарь, если соль известна (например, в URL).

Атака по словарю на известную соль. Решение: хранить соль в базе данных отдельно и не раскрывать её пользователю, либо использовать HMAC.

Как использовать стандартные функции PHP для безопасного хеширования паролей применительно к ID?


$hash = password_hash((string)$userId, PASSWORD_BCRYPT, ['cost' => 12]);
echo $hash;

Осторожно! password_hash предназначена для хеширования паролей, а не идентификаторов. Она использует специальный формат и автоматическую соль. Но её можно применить, если нужно хранить хеш ID в базе и сравнивать с помощью password_verify. Однако из-за намеренной медлительности (cost) это неэффективно при большом количестве запросов.

Высокая нагрузка на сервер при частой верификации. Решение - использовать быстрые алгоритмы (SHA-256, HMAC) для ID, а password_hash оставить для паролей.

Как получить хеш из ID с использованием Sodium (libsodium) для максимальной безопасности?


$secretKey = sodium_crypto_secretbox_keygen(); // генерирует 256-битный ключ
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox((string)$userId, $nonce, $secretKey);
echo bin2hex($nonce . $ciphertext);

Пояснение: sodium_crypto_secretbox шифрует ID, а не хеширует. Результат - зашифрованный токен, который можно расшифровать обратно. Это идеально для создания обратимых подписанных идентификаторов (например, для одноразовых ссылок).

Необходимо хранить nonce вместе с шифротекстом (обычно конкатенация). При потере nonce данные не расшифровать. Решение: всегда сохранять nonce в том же месте (например, в БД или в строке токена).

Расширенные примеры хеширования с ID

Пример 1: HMAC с солью и временем жизни (для одноразовых ссылок)

Пример

<?php
$secretKey = 'kL8x!sD3fG7hJ9@#2qW5eR4tY6uI1oP';
$userId = 1001;
$expires = time() + 3600; // через 1 час
$salt = bin2hex(random_bytes(8));
$data = $userId . '|' . $expires . '|' . $salt;
$hash = hash_hmac('sha256', $data, $secretKey);
$token = base64_encode($userId . '|' . $expires . '|' . $salt . '|' . $hash);
echo "Сгенерированный токен: " . $token . "\n";

// Проверка
$decoded = base64_decode($token);
$parts = explode('|', $decoded);
list($uid, $exp, $sl, $hsh) = $parts;
$expectedHash = hash_hmac('sha256', $uid . '|' . $exp . '|' . $sl, $secretKey);
if (hash_equals($expectedHash, $hsh) && $exp > time()) {
    echo "Токен валиден для пользователя $uid";
} else {
    echo "Токен недействителен или просрочен";
}
?>
Сгенерированный токен: MTAwMXwxNzA5MjM4MjAwfDFhMmIzYzRkNWU2Zjd8YWI4Y2RlZjAxMjM0NTY3ODkwYWFhYmJjY2RkZWVm
Токен валиден для пользователя 1001

Пример 2: Использование Password-Based Key Derivation Function (PBKDF2) для ID

Пример

<?php
$userId = 555;
$salt = random_bytes(16);
$iterations = 10000;
$length = 32;
$hash = hash_pbkdf2('sha256', (string)$userId, $salt, $iterations, $length);
echo bin2hex($hash) . "\n";
echo "Соль: " . bin2hex($salt);
?>
3a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d
Соль: abcd1234ef5678901234567890abcdef

Применение: PBKDF2 замедляет перебор, но не даёт уникальности без соли. Подходит для дерривации ключей на основе ID.

Пример 3: Хеш ID с контролем целостности (sign then hash)

Пример

<?php
$privateKey = openssl_pkey_get_private('file://path/to/private.pem');
$userId = 777;
$signature = '';
openssl_sign((string)$userId, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$hash = hash('sha256', $signature . $userId);
echo "Подпись: " . base64_encode($signature) . "\n";
echo "Хеш: " . $hash;
?>
Подпись: QWxpY2UgaXMgdGhlIGFuZC4uLg==
Хеш: efgh5678901234567890abcdef1234567890abcdef1234567890abcdef123456

Особенность: используется асимметричное шифрование для подписи, затем хеш - для сокращения длины. Требуется открытый ключ для проверки.

Хеширование с ID в PHP - comments

En
Hash php id (php)