Skip to content
Benyamin Khalife edited this page Dec 27, 2025 · 1 revision

Webrium Hash

A comprehensive and secure hashing utility class for the Webrium framework. This class provides secure password hashing, verification, HMAC generation, token generation, and various cryptographic hashing functions for different use cases.

Features

  • Secure Password Hashing: bcrypt, Argon2i, Argon2id algorithms
  • Password Verification: Safe password checking with timing-attack protection
  • Automatic Rehashing: Update old hashes to newer, more secure algorithms
  • General-Purpose Hashing: MD5, SHA-1, SHA-256, SHA-512, and more
  • HMAC Support: Generate and verify keyed-hash message authentication codes
  • Token Generation: Create secure random tokens and UUIDs
  • File Hashing: Calculate checksums for files
  • Timing-Safe Comparison: Prevent timing attacks on hash comparison
  • No Installation Required: Part of the Webrium framework

Table of Contents

Password Hashing

Basic Password Hashing

make(string $password, $algorithm = PASSWORD_DEFAULT, array $options = []): string

Hash a password using a secure algorithm.

use Webrium\Hash;

// Using default algorithm (recommended)
$hash = Hash::make('user_password');

// Specify bcrypt explicitly
$hash = Hash::make('user_password', PASSWORD_BCRYPT);

// Using Argon2i
$hash = Hash::make('user_password', PASSWORD_ARGON2I);

// Using Argon2id (most secure, if available)
$hash = Hash::make('user_password', PASSWORD_ARGON2ID);

Algorithm-Specific Methods

bcrypt(string $password, int $cost = 10): string

Create a bcrypt hash with custom cost parameter.

// Default cost (10)
$hash = Hash::bcrypt('user_password');

// Higher security (slower, more secure)
$hash = Hash::bcrypt('user_password', 12);

// Lower cost for testing (faster, less secure)
$hash = Hash::bcrypt('user_password', 8);

Cost Parameter:

  • Range: 4-31
  • Default: 10
  • Higher cost = more secure but slower
  • Each increment doubles the computation time

argon2i(string $password, int $memoryCost = 65536, int $timeCost = 4, int $threads = 1): string

Create an Argon2i hash with custom parameters.

// Default parameters
$hash = Hash::argon2i('user_password');

// Custom parameters for higher security
$hash = Hash::argon2i('user_password', 
    memoryCost: 131072,  // 128 MB
    timeCost: 6,
    threads: 2
);

// Lower resources for development
$hash = Hash::argon2i('user_password',
    memoryCost: 32768,   // 32 MB
    timeCost: 2,
    threads: 1
);

argon2id(string $password, int $memoryCost = 65536, int $timeCost = 4, int $threads = 1): string

Create an Argon2id hash (recommended for new applications).

// Argon2id is the most secure option
$hash = Hash::argon2id('user_password');

// High-security configuration
$hash = Hash::argon2id('user_password',
    memoryCost: 262144,  // 256 MB
    timeCost: 8,
    threads: 4
);

Argon2 Parameters:

  • memoryCost: Memory in KiB (higher = more secure)
  • timeCost: Number of iterations (higher = more secure)
  • threads: Parallel threads (affects performance)

Password Verification

Basic Verification

check(string $password, string $hash): bool

Verify a password against a hash.

$hash = Hash::make('secret123');

// Correct password
if (Hash::check('secret123', $hash)) {
    echo "Password is correct!";
}

// Wrong password
if (!Hash::check('wrong_password', $hash)) {
    echo "Invalid password!";
}

Verification with Rehashing

checkAndRehash(string $password, string $hash, $algorithm = PASSWORD_DEFAULT, array $options = []): array

Verify password and rehash if security standards have changed.

$storedHash = '$2y$10$...'; // Old bcrypt hash

$result = Hash::checkAndRehash('user_password', $storedHash);

if ($result['verified']) {
    echo "Password is correct!";
    
    // Check if rehashing was needed
    if ($result['hash'] !== null) {
        // Update the hash in your database
        $newHash = $result['hash'];
        // updateUserHash($userId, $newHash);
        echo "Hash updated to newer algorithm!";
    }
}

Use Case Example:

function loginUser($username, $password) {
    $user = getUserFromDatabase($username);
    
    $result = Hash::checkAndRehash($password, $user['password_hash']);
    
    if (!$result['verified']) {
        return ['success' => false, 'message' => 'Invalid credentials'];
    }
    
    // Update hash if needed
    if ($result['hash'] !== null) {
        updateUserPassword($user['id'], $result['hash']);
    }
    
    return ['success' => true, 'user' => $user];
}

Check if Rehashing is Needed

needsRehash(string $hash, $algorithm = PASSWORD_DEFAULT, array $options = []): bool

Check if a hash needs to be updated.

$hash = '$2y$10$...'; // Old hash

if (Hash::needsRehash($hash)) {
    echo "This hash should be updated on next login";
}

// Check against specific algorithm
if (Hash::needsRehash($hash, PASSWORD_ARGON2ID)) {
    echo "Hash should be upgraded to Argon2id";
}

Hash Information

info(string $hash): array

Get information about a password hash.

$hash = Hash::make('password');
$info = Hash::info($hash);

print_r($info);
// Output:
// Array (
//     [algo] => 1 (or 2 for Argon2i, 3 for Argon2id)
//     [algoName] => bcrypt (or argon2i, argon2id)
//     [options] => Array (...)
// )

getAlgorithm(string $hash): ?string

Get the algorithm name from a hash.

$hash = Hash::bcrypt('password');
echo Hash::getAlgorithm($hash); // "bcrypt"

$hash = Hash::argon2id('password');
echo Hash::getAlgorithm($hash); // "argon2id"

General Hashing

Multi-Purpose Hash Methods

digest(string $data, string $algorithm = 'sha256', bool $binary = false): string

Create a hash using any supported algorithm.

// SHA-256 (default)
$hash = Hash::digest('data to hash');

// MD5
$hash = Hash::digest('data to hash', 'md5');

// SHA-512
$hash = Hash::digest('data to hash', 'sha512');

// Binary output
$binaryHash = Hash::digest('data to hash', 'sha256', binary: true);

Specific Hash Algorithms

md5(string $data, bool $binary = false): string

Create an MD5 hash (not recommended for security-critical applications).

$hash = Hash::md5('data');
// Output: 8d777f385d3dfec8815d20f7496026dc

sha1(string $data, bool $binary = false): string

Create a SHA-1 hash.

$hash = Hash::sha1('data');
// Output: a17c9aaa61e80a1bf71d0d850af4e5baa9800bbd

sha256(string $data, bool $binary = false): string

Create a SHA-256 hash (recommended for general hashing).

$hash = Hash::sha256('data');
// Output: 3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7

sha512(string $data, bool $binary = false): string

Create a SHA-512 hash.

$hash = Hash::sha512('data');
// Output: 77c7ce9a5d86bb386d443bb96390faa120633158...

Salted Hashing

salted(string $data, string $salt, string $algorithm = 'sha256'): string

Create a hash with a salt (for non-password data).

$data = '[email protected]';
$salt = 'random_salt_12345';

$hash = Hash::salted($data, $salt);
// Use for creating deterministic identifiers

Peppered Hashing

peppered(string $data, string $pepper, string $algorithm = 'sha256'): string

Create a hash with an application-wide secret.

$appSecret = 'my_application_secret_key';
$data = 'sensitive_data';

$hash = Hash::peppered($data, $appSecret);
// Adds extra security layer with application secret

HMAC Operations

Generate HMAC

hmac(string $data, string $key, string $algorithm = 'sha256', bool $binary = false): string

Generate a keyed-hash message authentication code.

$data = 'important message';
$secretKey = 'my_secret_key';

$hmac = Hash::hmac($data, $secretKey);

// Using different algorithm
$hmac = Hash::hmac($data, $secretKey, 'sha512');

Use Cases:

  • API request signing
  • Message authentication
  • Data integrity verification

Verify HMAC

verifyHmac(string $data, string $hmac, string $key, string $algorithm = 'sha256'): bool

Verify an HMAC.

$data = 'important message';
$secretKey = 'my_secret_key';
$receivedHmac = 'hmac_from_client';

if (Hash::verifyHmac($data, $receivedHmac, $secretKey)) {
    echo "HMAC is valid - message is authentic";
} else {
    echo "HMAC verification failed - message may be tampered";
}

API Signature Example:

function signApiRequest($payload, $apiKey) {
    $data = json_encode($payload);
    $signature = Hash::hmac($data, $apiKey);
    
    return [
        'payload' => $payload,
        'signature' => $signature
    ];
}

function verifyApiRequest($request, $apiKey) {
    $data = json_encode($request['payload']);
    return Hash::verifyHmac($data, $request['signature'], $apiKey);
}

Token Generation

Random Token

token(int $length = 32): string

Generate a secure random token (URL-safe).

// Default 32-character token
$token = Hash::token();
// Output: a4f8c9d2b1e6f3a8c9d2b1e6f3a8c9d2

// Custom length
$token = Hash::token(64);
$shortToken = Hash::token(16);

Use Cases:

  • Session tokens
  • API keys
  • Password reset tokens
  • CSRF tokens

Random Hash

random(int $length = 32, string $algorithm = 'sha256'): string

Generate a random hash.

$randomHash = Hash::random();

// Custom length and algorithm
$randomHash = Hash::random(40, 'sha512');

Unique Hash

unique(string $prefix = '', string $algorithm = 'sha256'): string

Generate a unique hash based on time and random data.

$uniqueId = Hash::unique();

// With prefix
$uniqueId = Hash::unique('user_');

// Use case: Generate unique identifiers
$orderId = Hash::unique('order_');
$sessionId = Hash::unique('session_');

UUID v4

uuid(): string

Generate a UUID version 4.

$uuid = Hash::uuid();
// Output: 550e8400-e29b-41d4-a716-446655440000

// Use case: Primary keys, unique identifiers
$userId = Hash::uuid();
$fileId = Hash::uuid();

File Hashing

File Hash

file(string $filepath, string $algorithm = 'sha256', bool $binary = false): string|false

Create a hash from a file.

// Calculate file checksum
$hash = Hash::file('/path/to/file.pdf');

// Using different algorithm
$md5Hash = Hash::file('/path/to/file.pdf', 'md5');

// Check if file exists
if ($hash === false) {
    echo "File not found";
}

Use Cases:

  • File integrity verification
  • Detecting file changes
  • Duplicate file detection

Checksum Methods

checksum(string $data, string $algorithm = 'sha256'): string

Generate a checksum for data integrity.

$data = file_get_contents('config.json');
$checksum = Hash::checksum($data);

// Store checksum for later verification
// saveChecksum('config.json', $checksum);

verifyChecksum(string $data, string $checksum, string $algorithm = 'sha256'): bool

Verify data against a checksum.

$data = file_get_contents('config.json');
$storedChecksum = getStoredChecksum('config.json');

if (Hash::verifyChecksum($data, $storedChecksum)) {
    echo "File integrity verified";
} else {
    echo "File has been modified or corrupted";
}

Utility Methods

Timing-Safe Comparison

equals(string $known, string $user): bool

Compare two strings in a timing-attack-safe manner.

$storedToken = 'abc123def456';
$userToken = getUserInput();

// NEVER use === for security-critical comparisons
// BAD:  if ($storedToken === $userToken)

// GOOD: Use timing-safe comparison
if (Hash::equals($storedToken, $userToken)) {
    echo "Token is valid";
}

Why Timing-Safe Comparison Matters: Standard string comparison (===) can leak information through timing, allowing attackers to guess tokens character by character.

Available Algorithms

algorithms(): array

Get list of all available hash algorithms.

$algorithms = Hash::algorithms();
// Returns: ['md5', 'sha1', 'sha256', 'sha512', 'whirlpool', ...]

isAlgorithmSupported(string $algorithm): bool

Check if an algorithm is supported.

if (Hash::isAlgorithmSupported('sha256')) {
    echo "SHA-256 is supported";
}

if (!Hash::isAlgorithmSupported('custom_algo')) {
    echo "Custom algorithm not available";
}

Complete Examples

User Registration

use Webrium\Hash;

function registerUser($username, $email, $password) {
    // Hash password with Argon2id (most secure)
    $hashedPassword = Hash::argon2id($password);
    
    // Generate email verification token
    $verificationToken = Hash::token(64);
    
    // Create user ID
    $userId = Hash::uuid();
    
    // Store in database
    saveUser([
        'id' => $userId,
        'username' => $username,
        'email' => $email,
        'password' => $hashedPassword,
        'verification_token' => $verificationToken
    ]);
    
    // Send verification email
    sendVerificationEmail($email, $verificationToken);
}

User Login with Automatic Rehashing

use Webrium\Hash;

function loginUser($username, $password) {
    $user = getUserByUsername($username);
    
    if (!$user) {
        return ['success' => false, 'message' => 'Invalid credentials'];
    }
    
    // Verify password and rehash if needed
    $result = Hash::checkAndRehash(
        $password,
        $user['password'],
        PASSWORD_ARGON2ID
    );
    
    if (!$result['verified']) {
        return ['success' => false, 'message' => 'Invalid credentials'];
    }
    
    // Update hash if algorithm changed
    if ($result['hash'] !== null) {
        updateUserPassword($user['id'], $result['hash']);
    }
    
    // Generate session token
    $sessionToken = Hash::token(64);
    createSession($user['id'], $sessionToken);
    
    return [
        'success' => true,
        'user' => $user,
        'token' => $sessionToken
    ];
}

API Request Signing

use Webrium\Hash;

class ApiClient {
    private $apiKey;
    
    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
    }
    
    public function sendRequest($endpoint, $data) {
        $payload = json_encode($data);
        $timestamp = time();
        
        // Create signature
        $signatureData = $payload . $timestamp;
        $signature = Hash::hmac($signatureData, $this->apiKey);
        
        // Send request with signature
        return $this->httpPost($endpoint, [
            'payload' => $data,
            'timestamp' => $timestamp,
            'signature' => $signature
        ]);
    }
    
    public function verifyRequest($request) {
        $payload = json_encode($request['payload']);
        $signatureData = $payload . $request['timestamp'];
        
        return Hash::verifyHmac(
            $signatureData,
            $request['signature'],
            $this->apiKey
        );
    }
}

File Integrity System

use Webrium\Hash;

class FileIntegrityChecker {
    private $checksumFile = 'checksums.json';
    
    public function generateChecksums($directory) {
        $checksums = [];
        $files = scandir($directory);
        
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') continue;
            
            $filepath = $directory . '/' . $file;
            if (is_file($filepath)) {
                $checksums[$file] = Hash::file($filepath);
            }
        }
        
        file_put_contents(
            $this->checksumFile,
            json_encode($checksums, JSON_PRETTY_PRINT)
        );
        
        return $checksums;
    }
    
    public function verifyIntegrity($directory) {
        $storedChecksums = json_decode(
            file_get_contents($this->checksumFile),
            true
        );
        
        $results = [];
        
        foreach ($storedChecksums as $file => $checksum) {
            $filepath = $directory . '/' . $file;
            
            if (!file_exists($filepath)) {
                $results[$file] = 'MISSING';
                continue;
            }
            
            $currentHash = Hash::file($filepath);
            
            if (Hash::equals($checksum, $currentHash)) {
                $results[$file] = 'OK';
            } else {
                $results[$file] = 'MODIFIED';
            }
        }
        
        return $results;
    }
}

Password Reset Flow

use Webrium\Hash;

class PasswordReset {
    public function requestReset($email) {
        $user = getUserByEmail($email);
        
        if (!$user) {
            return ['success' => false];
        }
        
        // Generate secure reset token
        $resetToken = Hash::token(64);
        $resetTokenHash = Hash::sha256($resetToken);
        
        // Store hashed token with expiry
        saveResetToken($user['id'], $resetTokenHash, time() + 3600);
        
        // Send email with plain token
        sendResetEmail($email, $resetToken);
        
        return ['success' => true];
    }
    
    public function resetPassword($token, $newPassword) {
        // Hash the provided token
        $tokenHash = Hash::sha256($token);
        
        // Find user by token hash
        $resetData = getResetTokenData($tokenHash);
        
        if (!$resetData || $resetData['expires'] < time()) {
            return ['success' => false, 'message' => 'Invalid or expired token'];
        }
        
        // Hash new password
        $newHash = Hash::argon2id($newPassword);
        
        // Update password
        updateUserPassword($resetData['user_id'], $newHash);
        
        // Delete reset token
        deleteResetToken($tokenHash);
        
        return ['success' => true];
    }
}

Security Best Practices

Password Hashing

  1. Use Argon2id when available

    // Best option
    $hash = Hash::argon2id($password);
    
    // Fallback to bcrypt if Argon2id not available
    $hash = Hash::bcrypt($password, 12);
  2. Never use MD5 or SHA for passwords

    // NEVER DO THIS for passwords
    $hash = Hash::md5($password); // ❌ BAD
    $hash = Hash::sha256($password); // ❌ BAD
    
    // ALWAYS use password-specific methods
    $hash = Hash::make($password); // ✓ GOOD
  3. Implement automatic rehashing

    // Update old hashes on login
    $result = Hash::checkAndRehash($password, $oldHash);
    if ($result['hash'] !== null) {
        updateDatabase($result['hash']);
    }

Token Generation

  1. Use secure random tokens

    // Good for security tokens
    $token = Hash::token(64);
    
    // Store hash of token, not plain token
    $tokenHash = Hash::sha256($token);
    saveToDatabase($tokenHash);
  2. Never use predictable values

    // ❌ BAD - Predictable
    $token = md5($userId . time());
    
    // ✓ GOOD - Cryptographically secure
    $token = Hash::token();

Comparison

  1. Always use timing-safe comparison for security tokens
    // ❌ BAD - Vulnerable to timing attacks
    if ($userToken === $storedToken) { }
    
    // ✓ GOOD - Timing-safe
    if (Hash::equals($userToken, $storedToken)) { }

HMAC Usage

  1. Keep keys secret and rotate regularly

    // Store in environment variables
    $secretKey = getenv('HMAC_SECRET_KEY');
    $signature = Hash::hmac($data, $secretKey);
  2. Include timestamp to prevent replay attacks

    $data = json_encode($payload) . time();
    $signature = Hash::hmac($data, $secretKey);

Performance Considerations

Bcrypt Cost

// Development (faster)
$hash = Hash::bcrypt($password, 8);

// Production (secure)
$hash = Hash::bcrypt($password, 12);

// High-security systems
$hash = Hash::bcrypt($password, 14);

Cost Benchmark (approximate):

  • Cost 10: ~0.1s per hash
  • Cost 12: ~0.4s per hash
  • Cost 14: ~1.6s per hash

Argon2 Tuning

// Low resources (testing)
$hash = Hash::argon2id($password,
    memoryCost: 32768,  // 32 MB
    timeCost: 2
);

// Balanced (production)
$hash = Hash::argon2id($password,
    memoryCost: 65536,  // 64 MB
    timeCost: 4
);

// High security
$hash = Hash::argon2id($password,
    memoryCost: 262144, // 256 MB
    timeCost: 8
);

PHP Version Compatibility

The Hash class is fully compatible with PHP 8.0 and higher, including:

  • PHP 8.0+
  • PHP 8.1+
  • PHP 8.2+
  • PHP 8.3+

Features:

  • Uses native PHP password hashing functions
  • Supports all modern hashing algorithms
  • Compatible with PHP 8 named parameters

Requirements

  • Part of the Webrium framework
  • PHP 8.0 or higher
  • OpenSSL extension (usually included by default)
  • Argon2 support depends on PHP compilation

Security Notes

  1. Never roll your own crypto - This class uses PHP's built-in cryptographic functions
  2. Keep algorithms updated - Use checkAndRehash() to upgrade old hashes
  3. Secure key storage - Store HMAC keys and secrets in environment variables
  4. Timing attacks - Always use Hash::equals() for security-critical comparisons
  5. Salt/Pepper - Password functions handle salting automatically; don't add manual salts to passwords

Contributing

This is a framework component. For bug reports or feature requests, please contact the Webrium framework maintainers.

Clone this wiki locally