randomCode(); $key = (string) Str::uuid(); Cache::put( self::PREFIX.$key, $this->digest($code), now()->addSeconds(self::TTL_SECONDS), ); $svg = $this->renderSvg($code); return [ 'captcha_key' => $key, 'image_svg' => $svg, 'image_base64' => base64_encode($svg), ]; } public function verify(?string $captchaKey, ?string $captchaInput): bool { if ($captchaKey === null || $captchaKey === '' || $captchaInput === null || trim($captchaInput) === '') { return false; } $digest = Cache::pull(self::PREFIX.$captchaKey); if ($digest === null) { return false; } $guess = strtolower(trim($captchaInput)); return hash_equals($digest, $this->digest($guess)); } private function digest(string $normalizedCode): string { return hash_hmac( 'sha256', $normalizedCode, (string) config('app.key'), ); } private function randomCode(int $length = 4): string { $out = ''; $max = strlen(self::CHARSET) - 1; for ($i = 0; $i < $length; $i++) { $out .= self::CHARSET[random_int(0, $max)]; } return $out; } private function renderSvg(string $code): string { $w = 160; $h = 48; $bg = '#fafafa'; $escape = static fn (string $c): string => htmlspecialchars($c, ENT_XML1 | ENT_QUOTES); $lines = ''; for ($i = 0; $i < 4; $i++) { $x1 = random_int(0, $w); $y1 = random_int(0, $h); $x2 = random_int(0, $w); $y2 = random_int(0, $h); $stroke = sprintf('#%06x', random_int(0x9_00_000, 0xBF_BF_BF)); $lines .= sprintf( '', $x1, $y1, $x2, $y2, $stroke, ); } $chars = str_split($code); $texts = ''; $x = 18; foreach ($chars as $ch) { $rot = random_int(-20, 20); $dy = random_int(-3, 3); $y = 32 + $dy; $fill = sprintf('#%06x', random_int(0x20_20_20, 0x3F_3F_3F)); $texts .= sprintf( '%s', $x, $y, $fill, $rot, $x, $y, $escape($ch), ); $x += 34; } return sprintf( '%s%s', $w, $h, $w, $h, $bg, $lines, $texts, ); } }