session = Session::i(); } private function generateColor(bool $maxOutRed = false): array { return [ "red" => $maxOutRed ? 255 : random_int(0, 150), "green" => random_int(0, 100), "blue" => random_int(0, 150), "alpha" => 0, ]; } private function generateCode(): string { return mb_strtoupper(str_replace("/", "+", base64_encode(openssl_random_pseudo_bytes(6)))); } private function applyNoiseOnImage(Image $image, \Closure $fun): void { $_noise = (function() use ($image) { foreach(range(1, 33) as $x) foreach(range(1, 13) as $y) $image->setPixel($x * 5, $y * 5, $this->generateColor()); }); $_noise(); $fun(); $_noise(); } private function applyLinesOnImage(Image $image, \Closure $fun): void { $_lines = (function() use ($image) { foreach(range(1, random_int(2, 6)) as $i) $image->line(random_int(0, 15), random_int(0, 65), random_int(150, 165), random_int(0, 65), $this->generateColor()); }); $_lines(); $fun(); $_lines(); } private function generateCaptchaImage(string $code): Image { $image = Image::fromBlank(165, 65, $this->generateColor()); $this->applyNoiseOnImage($image, function() use ($image, $code) { $this->applyLinesOnImage($image, function() use ($image, $code) { $length = iconv_strlen($code); $offset = 165 / $length; for($i = 0; $i < $length; $i++) { $letter = $code[$i]; $font = __DIR__ . "/data/san-francissco.ttf"; $x = (int) ceil(0 + ($offset * $i)); $y = random_int(45, 55); $image->ttfText(random_int(18, 28), random_int(-10, 10), $x, $y, $this->generateColor(true), $font, $letter); } }); }); return $image; } function getImage(): Image { $code = $this->generateCode(); $hash = hash("crc32b", mb_strtolower($code)); $image = $this->generateCaptchaImage($code); $nonce = bin2hex(openssl_random_pseudo_bytes(SODIUM_CRYPTO_STREAM_NONCEBYTES / 2)); $key = substr(CHANDLER_ROOT_CONF["security"]["secret"], 0, SODIUM_CRYPTO_STREAM_KEYBYTES); $encHash = sodium_crypto_stream_xor($hash, $nonce, $key); $this->session->set("captcha", implode(":", [ time(), $nonce, base64_encode($encHash), ])); return $image; } function verifyCaptcha(?string $input = NULL): bool { if(!COMMITCAPTCHA_ROOT_CONF["commitcaptcha"]["enable"]) return true; if(!$input) $input = $_POST["captcha"]; $data = $this->session->get("captcha"); if(!$data) return false; $this->session->set("captcha", ""); [$time, $nonce, $encHash] = explode(":", $data); if((time() - $time) > 3600) return false; $key = substr(CHANDLER_ROOT_CONF["security"]["secret"], 0, SODIUM_CRYPTO_STREAM_KEYBYTES); $hash = sodium_crypto_stream_xor(base64_decode($encHash), $nonce, $key); return hash_equals(hash("crc32b", mb_strtolower($input)), $hash); } use TSimpleSingleton; }