aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http/bcrypt.php62
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";