-
Notifications
You must be signed in to change notification settings - Fork 2
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.
- 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
- Password Hashing
- Password Verification
- General Hashing
- HMAC Operations
- Token Generation
- File Hashing
- Utility Methods
- Security Best Practices
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);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
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
);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)
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!";
}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 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";
}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 (...)
// )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"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);Create an MD5 hash (not recommended for security-critical applications).
$hash = Hash::md5('data');
// Output: 8d777f385d3dfec8815d20f7496026dcCreate a SHA-1 hash.
$hash = Hash::sha1('data');
// Output: a17c9aaa61e80a1bf71d0d850af4e5baa9800bbdCreate a SHA-256 hash (recommended for general hashing).
$hash = Hash::sha256('data');
// Output: 3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7Create a SHA-512 hash.
$hash = Hash::sha512('data');
// Output: 77c7ce9a5d86bb386d443bb96390faa120633158...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 identifiersCreate 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 secretGenerate 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 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);
}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
Generate a random hash.
$randomHash = Hash::random();
// Custom length and algorithm
$randomHash = Hash::random(40, 'sha512');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_');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();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
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);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";
}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.
Get list of all available hash algorithms.
$algorithms = Hash::algorithms();
// Returns: ['md5', 'sha1', 'sha256', 'sha512', 'whirlpool', ...]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";
}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);
}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
];
}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
);
}
}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;
}
}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];
}
}-
Use Argon2id when available
// Best option $hash = Hash::argon2id($password); // Fallback to bcrypt if Argon2id not available $hash = Hash::bcrypt($password, 12);
-
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
-
Implement automatic rehashing
// Update old hashes on login $result = Hash::checkAndRehash($password, $oldHash); if ($result['hash'] !== null) { updateDatabase($result['hash']); }
-
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);
-
Never use predictable values
// ❌ BAD - Predictable $token = md5($userId . time()); // ✓ GOOD - Cryptographically secure $token = Hash::token();
-
Always use timing-safe comparison for security tokens
// ❌ BAD - Vulnerable to timing attacks if ($userToken === $storedToken) { } // ✓ GOOD - Timing-safe if (Hash::equals($userToken, $storedToken)) { }
-
Keep keys secret and rotate regularly
// Store in environment variables $secretKey = getenv('HMAC_SECRET_KEY'); $signature = Hash::hmac($data, $secretKey);
-
Include timestamp to prevent replay attacks
$data = json_encode($payload) . time(); $signature = Hash::hmac($data, $secretKey);
// 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
// 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
);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
- Part of the Webrium framework
- PHP 8.0 or higher
- OpenSSL extension (usually included by default)
- Argon2 support depends on PHP compilation
- Never roll your own crypto - This class uses PHP's built-in cryptographic functions
-
Keep algorithms updated - Use
checkAndRehash()to upgrade old hashes - Secure key storage - Store HMAC keys and secrets in environment variables
-
Timing attacks - Always use
Hash::equals()for security-critical comparisons - Salt/Pepper - Password functions handle salting automatically; don't add manual salts to passwords
This is a framework component. For bug reports or feature requests, please contact the Webrium framework maintainers.