125 lines
3.4 KiB
PHP
125 lines
3.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Core;
|
|
|
|
class Security
|
|
{
|
|
public static function hashPassword(string $password): string
|
|
{
|
|
$algo = defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT;
|
|
return password_hash($password, $algo);
|
|
}
|
|
|
|
public static function verifyPassword(string $password, string $hash): bool
|
|
{
|
|
return password_verify($password, $hash);
|
|
}
|
|
|
|
public static function generateRandomHex(int $bytes = 32): string
|
|
{
|
|
return bin2hex(random_bytes($bytes));
|
|
}
|
|
|
|
public static function generateHmacSignature(string $data, string $secretKey): string
|
|
{
|
|
return hash_hmac('sha256', $data, $secretKey);
|
|
}
|
|
|
|
public static function verifyHmacSignature(string $data, string $signature, string $secretKey): bool
|
|
{
|
|
$calculated = self::generateHmacSignature($data, $secretKey);
|
|
return hash_equals($calculated, $signature);
|
|
}
|
|
|
|
public static function encryptAesGcm(string $plaintext, string $key): string
|
|
{
|
|
$cipher = 'aes-256-gcm';
|
|
$keyHash = hash('sha256', $key, true);
|
|
$iv = random_bytes(12); // 96-bit IV recommended for GCM
|
|
$tag = '';
|
|
|
|
$ciphertext = openssl_encrypt(
|
|
$plaintext,
|
|
$cipher,
|
|
$keyHash,
|
|
OPENSSL_RAW_DATA,
|
|
$iv,
|
|
$tag,
|
|
'',
|
|
16
|
|
);
|
|
|
|
return base64_encode($iv . $tag . $ciphertext);
|
|
}
|
|
|
|
public static function decryptAesGcm(string $encryptedPackage, string $key): ?string
|
|
{
|
|
$data = base64_decode($encryptedPackage, true);
|
|
if (!$data || strlen($data) < 28) {
|
|
return null;
|
|
}
|
|
|
|
$cipher = 'aes-256-gcm';
|
|
$keyHash = hash('sha256', $key, true);
|
|
|
|
$iv = substr($data, 0, 12);
|
|
$tag = substr($data, 12, 16);
|
|
$ciphertext = substr($data, 28);
|
|
|
|
$decrypted = openssl_decrypt(
|
|
$ciphertext,
|
|
$cipher,
|
|
$keyHash,
|
|
OPENSSL_RAW_DATA,
|
|
$iv,
|
|
$tag
|
|
);
|
|
|
|
return $decrypted !== false ? $decrypted : null;
|
|
}
|
|
|
|
public static function generateDynamicQrToken(int $userId, string $membershipNumber, int $expiresInSeconds, string $secretKey): string
|
|
{
|
|
$payload = [
|
|
'uid' => $userId,
|
|
'mem' => $membershipNumber,
|
|
'exp' => time() + $expiresInSeconds,
|
|
'rnd' => self::generateRandomHex(8),
|
|
];
|
|
|
|
$json = json_encode($payload, JSON_UNESCAPED_SLASHES);
|
|
$base64Payload = rtrim(strtr(base64_encode($json), '+/', '-_'), '=');
|
|
$signature = self::generateHmacSignature($base64Payload, $secretKey);
|
|
|
|
return $base64Payload . '.' . $signature;
|
|
}
|
|
|
|
public static function verifyDynamicQrToken(string $token, string $secretKey): ?array
|
|
{
|
|
$parts = explode('.', $token);
|
|
if (count($parts) !== 2) {
|
|
return null;
|
|
}
|
|
|
|
[$base64Payload, $signature] = $parts;
|
|
|
|
if (!self::verifyHmacSignature($base64Payload, $signature, $secretKey)) {
|
|
return null;
|
|
}
|
|
|
|
$json = base64_decode(strtr($base64Payload, '-_', '+/'));
|
|
if (!$json) {
|
|
return null;
|
|
}
|
|
|
|
$payload = json_decode($json, true);
|
|
if (!is_array($payload) || !isset($payload['exp']) || $payload['exp'] < time()) {
|
|
return null; // Expired or invalid format
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
}
|