diff options
| author | Joe Carstairs <me@joeac.net> | 2026-09-24 08:40:10 +0100 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2026-09-24 08:40:10 +0100 |
| commit | 5e18002355ef29aeb04c6d1c237c64bc237483fc (patch) | |
| tree | f8e0121e28b023738e4184ee1498d066e32326f8 | |
| parent | 5b0c1651d47be5bf261f4795838c0e764e63e43b (diff) | |
add bcrypt.php
| -rw-r--r-- | http/bcrypt.php | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/http/bcrypt.php b/http/bcrypt.php new file mode 100644 index 0000000..00eda33 --- /dev/null +++ b/http/bcrypt.php @@ -0,0 +1,62 @@ +<?php + +const BCRYPT_COST = 12; + +$user = $_SERVER['PHP_AUTH_USER'] ?? null; +$pass = $_SERVER['PHP_AUTH_PW'] ?? null; + +// Fallback for CGI / FastCGI SAPIs that don't populate PHP_AUTH_*. +// Apache: add "CGIPassAuth On" (2.4.13+) or a RewriteRule to expose +// HTTP_AUTHORIZATION; nginx+php-fpm passes it through by default. +if ($user === null || $pass === null) { + $header = $_SERVER['HTTP_AUTHORIZATION'] + ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] + ?? null; + + if (is_string($header) && preg_match('/^Basic\s+(.+)$/i', $header, $m) === 1) { + $decoded = base64_decode($m[1], true); + + // Split on the FIRST colon only: passwords may contain colons. + if ($decoded !== false && str_contains($decoded, ':')) { + [$user, $pass] = explode(':', $decoded, 2); + } + } +} + +if (!is_string($user) || $user === '' || !is_string($pass) || $pass === '') { + header('WWW-Authenticate: Basic realm="Restricted", charset="UTF-8"'); + http_response_code(401); + header('Content-Type: text/plain; charset=utf-8'); + header('Cache-Control: no-store'); + echo 'authentication required'; + exit; +} + +if (strlen($pass) > 72) { + http_response_code(422); + header('Content-Type: text/plain; charset=utf-8'); + header('Cache-Control: no-store'); + echo 'password exceeds the 72-byte bcrypt limit'; + exit; +} + +$hash = password_hash($pass, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]); + +if ($hash === false) { + http_response_code(500); + header('Content-Type: text/plain; charset=utf-8'); + header('Cache-Control: no-store'); + echo 'hashing failed'; + exit; +} + +header('Content-Type: application/json; charset=utf-8'); +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Pragma: no-cache'); + +echo json_encode([ + 'user' => $user, + 'pass_hash' => $hash, + 'algo' => password_get_info($hash)['algoName'], + 'cost' => BCRYPT_COST, +], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), "\n"; |
