Last active
March 19, 2026 01:53
-
-
Save ArrayIterator/74caf97b09de996c0876412237859537 to your computer and use it in GitHub Desktop.
Stateless Session Implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| class SessionPayload { | |
| public function __construct( | |
| public string $token, | |
| public int $userId, | |
| public int $timestamp, | |
| public int $expiredAt, | |
| public string $random16 // binary string | |
| ) {} | |
| public function isExpired(): bool { | |
| $now = time(); | |
| return $this->timestamp <= 0 || | |
| $this->expiredAt <= 0 || | |
| $this->timestamp >= $now || | |
| $this->expiredAt <= $now; | |
| } | |
| public function isNeedRenew(int $renewDurationSecs): bool { | |
| $now = time(); | |
| return $this->expiredAt > $now && ($this->expiredAt - $now) <= $renewDurationSecs; | |
| } | |
| public function __toString(): string { | |
| return $this->token; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| class SessionTokenizer { | |
| private string $combinedKey; | |
| public const LENGTH = 208; // 104 bytes * 2 (hex) | |
| public function __construct(string $secret, string $salt) { | |
| $this->combinedKey = $secret . $salt; | |
| } | |
| public function generate(int $userId, int $durationSecs): SessionPayload { | |
| $now = time(); | |
| $expiredAt = $now + $durationSecs; | |
| // 1. Generate Random 16 bytes (seperti UUID v4 bytes) | |
| $random16 = random_bytes(16); | |
| // 2. Prepare Big-Endian Bytes | |
| $user8 = pack('J', $userId); // 'J' = unsigned long 64-bit big-endian | |
| $time8 = pack('J', $now); | |
| $exp8 = pack('J', $expiredAt); | |
| // 3. Inner Signature (Identity Binding) | |
| $idBinding = hash_hmac('sha256', $user8, $this->combinedKey, true); | |
| // 4. Construct Buffer (72 bytes payload) | |
| // [0..16] random + [16..48] idBinding + [48..56] time + [56..64] exp + [64..72] user | |
| $payloadPart = $random16 . $idBinding . $time8 . $exp8 . $user8; | |
| // 5. Final Outer Sign (32 bytes signature) | |
| $finalSign = hash_hmac('sha256', $payloadPart, $this->combinedKey, true); | |
| // 6. Final Token (104 bytes -> 208 hex chars) | |
| $tokenHex = bin2hex($payloadPart . $finalSign); | |
| return new SessionPayload($tokenHex, $userId, $now, $expiredAt, $random16); | |
| } | |
| public function parse(string $tokenHex): ?SessionPayload { | |
| if (strlen($tokenHex) !== self::LENGTH) { | |
| return null; // throw Exception | |
| } | |
| $bytes = hex2bin($tokenHex); | |
| if (!$bytes) return null; | |
| // Split Buffer | |
| $payloadPart = substr($bytes, 0, 72); | |
| $outerSignature = substr($bytes, 72, 32); | |
| // 1. Verify Outer Signature | |
| $expectedOuterSign = hash_hmac('sha256', $payloadPart, $this->combinedKey, true); | |
| if (!hash_equals($expectedOuterSign, $outerSignature)) { | |
| return null; // Integrity check failed | |
| } | |
| // 2. Extract Components | |
| $random16 = substr($payloadPart, 0, 16); | |
| $innerSignature = substr($payloadPart, 16, 32); | |
| // Unpack Big-Endian | |
| $timestamp = unpack('J', substr($payloadPart, 48, 8))[1]; | |
| $expiredAt = unpack('J', substr($payloadPart, 56, 8))[1]; | |
| $user8 = substr($payloadPart, 64, 8); | |
| $userId = unpack('J', $user8)[1]; | |
| // 3. Verify Inner Signature | |
| $expectedInnerSign = hash_hmac('sha256', $user8, $this->combinedKey, true); | |
| if (!hash_equals($expectedInnerSign, $innerSignature)) { | |
| return null; // Identity binding failed | |
| } | |
| return new SessionPayload($tokenHex, $userId, $timestamp, $expiredAt, $random16); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/env php | |
| <?php | |
| $tokenizer = new SessionTokenizer("super-secret-key", "some-salt"); | |
| $totalTests = 300; | |
| $successCount = 0; | |
| echo "π Starting Stress Test: Generating and Parsing $totalTests Tokens..." . PHP_EOL; | |
| echo str_repeat("-", 50) . PHP_EOL; | |
| $startTime = microtime(true); | |
| for ($i = 1; $i <= $totalTests; $i++) { | |
| $uid = mt_rand(1, 9999999); | |
| $duration = 3600; | |
| $session = $tokenizer->generate($uid, $duration); | |
| $parsed = $tokenizer->parse($session->token); | |
| if ($parsed && $parsed->userId === $uid) { | |
| $successCount++; | |
| if ($i % 50 === 0) { | |
| echo "β Processed $i tokens... (Latest UID: $uid)" . PHP_EOL; | |
| } | |
| } else { | |
| echo "β FAILED at loop $i for UID: $uid" . PHP_EOL; | |
| } | |
| } | |
| $endTime = microtime(true); | |
| $executionTime = round($endTime - $startTime, 4); | |
| echo str_repeat("-", 50) . PHP_EOL; | |
| echo "π SUMMARY REPORT" . PHP_EOL; | |
| echo "Total Iterations : $totalTests" . PHP_EOL; | |
| echo "Successful Parse : $successCount / $totalTests" . PHP_EOL; | |
| echo "Execution Time : $executionTime seconds" . PHP_EOL; | |
| echo "Avg Time/Token : " . ($executionTime / $totalTests) . "s" . PHP_EOL; | |
| if ($successCount === $totalTests) { | |
| echo "π RESULT: 100% Consistent! Logic Rust-to-PHP Porting Safe!." . PHP_EOL; | |
| } else { | |
| echo "β οΈ RESULT: Mismatch logic, there are invalid byte pack/unpack!" . PHP_EOL; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example Usage