|
<?php |
|
function encrypt_cookie( |
|
int $user_id, |
|
string $securityKey, |
|
string $saltKey |
|
) { |
|
global $start; |
|
// encrypt |
|
$gmt_time = strtotime(gmdate('Y-m-d H:i:s')); |
|
$random = sha1(random_bytes(64)); // 64 chars is enough |
|
$toHash = $random . $user_id . $saltKey . $gmt_time; |
|
$the_password = hash_hmac( |
|
'sha256', // stronger than sha1 |
|
$toHash, |
|
$securityKey |
|
); |
|
$start = microtime(true); |
|
$password_sign = password_hash( |
|
$the_password, |
|
PASSWORD_BCRYPT, |
|
// we just need 256 iterations |
|
// as cost 8 |
|
// use 10 as cost maybe better, but 8 is enough just for sign |
|
// @use \PASSWORD_BCRYPT_DEFAULT_COST = 10 |
|
[ |
|
'cost' => 8 |
|
] |
|
); |
|
// cookie value maximum is 4096 |
|
// [0-9]+|[a-f0-9]{40}|$2y$10$[./0-9a-zA-Z]{53}|1234567890 |
|
return sprintf( |
|
'%d|%s|%s|%d', |
|
$user_id, |
|
$random, |
|
$password_sign, |
|
$gmt_time |
|
); |
|
} |
|
function get_user_id_by_cookie_value( |
|
string $cookieValue, |
|
string $securityKey, |
|
string $saltKey |
|
) { |
|
$user_id = null; |
|
// decrypt |
|
$dataStateLess = explode('|', $cookieValue); |
|
$valid = count($dataStateLess) === 4; |
|
if ($valid) { |
|
// create current_gmt_time |
|
$current_gmt_time = strtotime(gmdate('Y-m-d H:i:s')); |
|
// get user_id |
|
$user_id = array_shift($dataStateLess); |
|
// get random hex sha1 |
|
$random = array_shift($dataStateLess); |
|
// get sign |
|
$password_sign_hashes = array_shift($dataStateLess); |
|
// get gmt time |
|
$time = array_shift($dataStateLess); |
|
// validate data |
|
$valid = is_numeric($user_id) // user id is numeric |
|
&& strpos($user_id, '.') === false // must be integer |
|
&& is_numeric($time) // time is numeric |
|
&& strpos($time, '.') === false // must be integer |
|
// given time must be less than current time |
|
// or equal if we have check for the current request |
|
// recommendation using less or equal (<=) |
|
&& ((int)$time) <= $current_gmt_time |
|
// is string random |
|
&& is_string($random) |
|
// is string of sign |
|
&& is_string($password_sign_hashes) |
|
// random is sha1 will return 40 bytes |
|
&& preg_match('~^[a-f0-9]{40}$~', $random) |
|
// password hash cost 8 is: $2y$08$ |
|
// 08 as cost of 8 |
|
// and contains 60 characters on crypt |
|
// $2y$10$ = 7 -> 60 - 7 = 53 |
|
// bcrypt chars : [./0-9a-zA-Z] |
|
// @see https://stackoverflow.com/a/5882472/10710040 |
|
&& preg_match('~^[$]2y[$]08[$][./0-9a-zA-Z]{53}$~', $password_sign_hashes); |
|
// LETS WE CREATE SIMPLE SIGN IF VALID |
|
if ($valid) { |
|
// convert to integer user_id |
|
$user_id = (int) $user_id; |
|
// combine salt & security key as hash_hmac |
|
$toHash = $random . $user_id . $saltKey . $time; |
|
$password = hash_hmac( |
|
'sha256', // stronger than sha1 |
|
$toHash, |
|
$securityKey |
|
); |
|
$valid = password_verify($password, $password_sign_hashes); |
|
} |
|
} |
|
|
|
return $valid ? $user_id : null; |
|
} |
|
|
|
// TEST |
|
$securityKey = 'thisMyRandomKey'; |
|
$saltKey = 'thisMyRandomSalt'; |
|
$user_id = 1; |
|
$start_time = microtime(true); |
|
// encrypt |
|
$cookieValue = encrypt_cookie($user_id, $securityKey, $saltKey); |
|
echo "COOKIE VALUE: " . $cookieValue."\n"; |
|
echo "PERFORMANCE ENCRYPT: ". round((microtime(true) - $start_time) * 1000, 3) ."ms\n"; |
|
$decrypt_start_time = microtime(true); |
|
$decrypted_user_id = get_user_id_by_cookie_value($cookieValue, $securityKey, $saltKey); |
|
echo "PERFORMANCE DECRYPT: ". round((microtime(true) - $decrypt_start_time) * 1000, 3) ."ms\n"; |
|
echo "IS VALID: ". ($user_id === $decrypted_user_id ? "YES" : "NO")."\n"; |
|
echo "PERFORMANCE TOTAL: ". round((microtime(true) - $start_time) * 1000, 3) ."ms\n"; |
|
/* |
|
COOKIE VALUE: 1|351f3a96402ba66f26ccc01815c1cd936153e1d6|$2y$08$997xHQGXmAUb3m/ZY/A.3eYox9/ZaQ/yJKZucZornzT5xEnvRwOr.|1671251979 |
|
PERFORMANCE ENCRYPT: 26.037ms |
|
PERFORMANCE DECRYPT: 22.656ms |
|
IS VALID: YES |
|
PERFORMANCE TOTAL: 48.749ms |
|
|
|
*/ |
This just simple logic for educational use only.
I am not responsible to anything that happened to you when you use this example codes.