HMAC & hashing
Hash\Hmac signs and verifies keyed MACs — the building block for webhook signature checks. Secrets are passed per call and marked #[\SensitiveParameter]; the class holds no key of its own:
use RoundlyConsulting\Crypto\Facades\Crypto;
use RoundlyConsulting\Crypto\Hash\HashAlgorithm;
// GitHub-style "sha256=…" header check:
$expected = 'sha256='.Crypto::hmac(HashAlgorithm::Sha256)->signHex($request->getContent(), $webhookSecret);
$ok = Crypto::constantTimeEquals($expected, $request->header('X-Hub-Signature-256', ''));
// Deterministic digest → an indexable lookup column:
$lookupKey = Crypto::digest()->hex($emailLowercased);Or call the classes the facade fronts directly:
use RoundlyConsulting\Crypto\Hash\Hmac;
use RoundlyConsulting\Crypto\Hash\HashAlgorithm;
$hmac = new Hmac(HashAlgorithm::Sha256);
// GitHub-style "sha256=…" header check:
$expected = 'sha256='.$hmac->signHex($request->getContent(), $webhookSecret);
$ok = hash_equals($expected, $request->header('X-Hub-Signature-256', ''));
// Or verify raw signatures directly (constant-time):
$ok = $hmac->verify($payload, $signature, $webhookSecret);| Method | Returns |
|---|---|
sign($message, $key) | The raw-bytes HMAC. |
signHex($message, $key) | The lower-case hex HMAC. |
verify($message, $signature, $key) | bool — a constant-time check of a raw-bytes signature. |
Deterministic digests
Hash\Digest produces the same output for the same input — which is what makes an indexed equality lookup on hashed data possible, such as a blind index over an email or a token:
use RoundlyConsulting\Crypto\Hash\Digest;
$digest = new Digest; // SHA-256
$lookupKey = $digest->hex($emailLowercased); // deterministic → indexable column
$peppered = $digest->withPepper($token, config('app.pepper')); // HMAC when a pepper is set
$raw = $digest->raw($data); // raw byteswithPepper() makes the plain-versus-keyed choice explicit: only null selects the plain digest. Any non-null pepper — even whitespace or an empty string — is used verbatim as the HMAC key, so a blank value can never silently downgrade a keyed digest.
Constant-time comparison
ConstantTime::equals() wraps hash_equals so every secret, tag and MAC comparison routes through one place and never leaks, through timing, how much of a value matched:
use RoundlyConsulting\Crypto\Hash\ConstantTime;
// $known is the trusted value, $user the attacker-influenced one:
$ok = ConstantTime::equals($knownExpected, $userSupplied);Hash algorithms
| Case | Value | Status |
|---|---|---|
HashAlgorithm::Sha1 | sha1 | Legacy — collision-broken, interop only; isLegacy() returns true. |
HashAlgorithm::Sha256 | sha256 | Current (the default). |
HashAlgorithm::Sha384 | sha384 | Current. |
HashAlgorithm::Sha512 | sha512 | Current. |
OTP is separate: RFC 6238 mandates SHA-1 and authenticator apps default to it, so Otp\OtpAlgorithm keeps SHA-1 first-class.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.