Download the PHP package darkstrata/credential-check without Composer
On this page you can find all versions of the php package darkstrata/credential-check. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download darkstrata/credential-check
More information about darkstrata/credential-check
Files in darkstrata/credential-check
Package credential-check
Short Description DarkStrata credential breach checking SDK using k-anonymity. Only a 5-6 character hash prefix is ever sent to the API.
License Apache-2.0
Homepage https://darkstrata.io
Informations about the package credential-check
DarkStrata Credential Check SDK for PHP
Check if credentials have been exposed in data breaches using k-anonymity to protect user privacy.
This SDK is developed in the darkstrata-sdks monorepo under
sdks/php. The darkstrata-credential-check-php repository is a read-only mirror published for Packagist. Please open issues and pull requests in the monorepo.
Installation
Prerequisites
- PHP 7.4 or newer (tested on 7.4 through 8.5)
- The
curlandjsonextensions, which ship with virtually every PHP build - A DarkStrata API key
No other dependencies.
Quick Start
$client->check($email, $password) does both steps in one call.
Features
- Privacy-first k-anonymity - Only the first 5 characters of the hash are sent to the server
- Zero dependencies - Only
ext-curlandext-json - Batch checking - One API call per distinct prefix, results in input order
- Automatic retries - Exponential backoff for transient failures
- Response caching - In-memory, per-client, with TTL and server key-window awareness
- Typed exceptions - Every error reports whether it is retryable
- Wide PHP support - 7.4 through 8.5
- Laravel integration - Blocks compromised logins and passwords out of the box, see Laravel
How It Works
- The SDK computes
SHA-256(email:password)locally, with the email trimmed and lowercased - Only the first 5 characters (the prefix) are sent to the server
- The server returns HMACs of all hashes matching that prefix (typically 50-1000)
- The SDK HMACs the full hash with the server's key and compares it against the response using a constant-time comparison
- Your actual credential never leaves your server
API Reference
Client
Constructor
prefixLength: hash characters sent to the API. 6 is ~2x faster with a ~16x smaller response, but hides each check among ~500 hashes instead of ~8,000.
Throws ValidationException if apiKey is missing or prefixLength is not 5 or 6.
check(string $email, string $password, array $options = []): CheckResult
Hashes the credential and checks it. Throws ValidationException if either value is empty.
checkHash(string $hash, array $options = []): CheckResult
Checks a precomputed SHA-256 hash (64 hex characters, any case) from Crypto::hashCredential(). The result's email is [hash-only].
checkBatch(array $credentials, array $options = []): CheckResult[]
Checks many ['email' => ..., 'password' => ...] pairs. Hashes are grouped by prefix so each prefix is fetched once. Results are in input order.
checkHashBatch(array $hashes, array $options = []): CheckResult[]
Same as checkBatch for precomputed hashes. $results[$i] corresponds to $hashes[$i].
clearCache(): void and getCacheSize(): int
Check Options
Every check method accepts an optional $options array:
| Key | Type | Description |
|---|---|---|
clientHmac |
string |
Your own HMAC key (64+ hex chars). The server uses it instead of its rotating key, so results are stable across calls |
since |
DateTimeInterface\|int |
Only include breaches from this date (or epoch day) onwards |
CheckResult
| Property | Type | Description |
|---|---|---|
found |
bool |
Credential appears in a known breach |
email |
string |
Email as supplied, or [hash-only] for hash checks |
prefix |
string |
The prefix that was sent to the API |
totalResults |
int |
Candidate hashes returned for the prefix |
hmacSource |
string |
server or client |
timeWindow |
string |
Server HMAC key rotation window |
filterSince |
?int |
Epoch-day filter applied, or null |
cachedResult |
bool |
Result served from the in-memory cache |
checkedAt |
DateTimeImmutable |
When the check ran |
Caching
Caching is per Client instance, keyed by prefix and options. Entries expire after cacheTtl seconds or when the server's HMAC key window rolls over (hourly), whichever comes first.
Under PHP-FPM a client only lives for one request, so the cache mainly helps batch calls that share a prefix. For cross-request caching, keep the client alive in a long-running worker (Swoole, RoadRunner, Laravel Octane), or disable it with enableCaching => false.
Error Handling
All exceptions extend DarkStrata\CredentialCheck\Exception\DarkStrataException, which extends RuntimeException and exposes getErrorCode() and isRetryable().
Error Types
| Exception | Code | Retryable | Extra |
|---|---|---|---|
AuthenticationException |
AUTHENTICATION_ERROR |
No | |
ValidationException |
VALIDATION_ERROR |
No | getField() |
ApiException |
API_ERROR |
408, 429, 5xx only | getStatusCode(), getResponseBody() |
RateLimitException |
RATE_LIMIT_ERROR |
Yes | getRetryAfter() |
TimeoutException |
TIMEOUT_ERROR |
Yes | |
NetworkException |
NETWORK_ERROR |
Yes |
Retryable errors are retried automatically up to retries times with exponential backoff (1s, 2s, 4s, capped at 10s) before being thrown.
Cryptographic Utilities
DarkStrata\CredentialCheck\Crypto exposes the primitives for advanced use. All hex output is uppercase.
Advanced Usage
Pre-computed hashes
Hash once at signup, store the hash, and re-check it later without ever handling the password again:
Other frameworks
The client has no framework dependencies. Register it as a singleton in your container and inject it wherever logins are handled:
Laravel
The package ships a Laravel integration that stops compromised credentials being used on your site, the same as the Umbraco package. When someone logs in or sets a password, the email + password pair is hashed locally and checked against the breach corpus. Passwords never leave your server.
Supports Laravel 11 through 13 (PHP 8.2+). The service provider is auto-discovered; nothing to register.
What it does
| Hook | Behaviour |
|---|---|
Login (Auth::attempt()) |
After the password is verified and before the session starts, a compromised pair throws CompromisedCredentialException (default) or is allowed with a warning. Wrong passwords never cost an API call. |
| Password set / change / reset | Add the NotCompromisedCredential rule to the form. A compromised pair fails validation with the configured message. |
| Any hit | Dispatches CompromisedCredentialDetected so you can run your own workflow. |
| Health check | php artisan darkstrata:check confirms the key is configured and accepted. |
Configure
Set the key in .env:
Create it in app.darkstrata.io under Integrations → API keys with the credential_check:read scope. Then check it works:
To change anything else, publish the config:
| Setting | Env | Default | Meaning |
|---|---|---|---|
api_key |
DARKSTRATA_API_KEY |
- | Required. With no key every check is skipped and a warning is logged at boot. |
validate_passwords |
DARKSTRATA_VALIDATE_PASSWORDS |
true |
Let the NotCompromisedCredential rule reject compromised pairs. |
check_logins |
DARKSTRATA_CHECK_LOGINS |
true |
Check every successful login. |
login_action |
DARKSTRATA_LOGIN_ACTION |
deny |
deny fails the login. warn allows it, logs a warning and dispatches the event. |
fail_open |
DARKSTRATA_FAIL_OPEN |
true |
If the API is unreachable, allow the operation. false rejects instead. |
email_field |
email |
The credentials key and request field holding the email. | |
messages.login, messages.password |
Wording shown to the person. Translate by publishing the config and using __(). |
Password forms
This differs from Laravel's built-in Password::uncompromised(), which asks Have I Been Pwned whether the password alone has ever leaked. DarkStrata checks whether this email and this password have appeared together, which is what credential-stuffing attacks actually use.
Login
Nothing to wire up. CompromisedCredentialException is a ValidationException on the email field, so Breeze, Fortify, Jetstream and hand-written controllers that call Auth::attempt() show the configured message on the login form, and the failed attempt still counts towards your rate limiter.
If you'd rather handle it yourself:
React to a hit
Pause or uninstall
Remove the key to pause: every check is skipped and behaviour is as if the package were not installed. Set login_action=warn to keep checking without blocking anyone, useful for a trial period. To uninstall, composer remove darkstrata/credential-check and delete any NotCompromisedCredential rules and listeners you added. The package writes nothing to your database.
Examples
See the examples/ directory:
basic_usage.php- Single credential checkbatch_check.php- Batch checking with prefix groupingerror_handling.php- Every exception type and retryability
Testing
Licence
Apache-2.0
All versions of credential-check with dependencies
ext-curl Version *
ext-json Version *