NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Crypto for Laravel

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);
MethodReturns
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 bytes

withPepper() 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

CaseValueStatus
HashAlgorithm::Sha1sha1Legacy — collision-broken, interop only; isLegacy() returns true.
HashAlgorithm::Sha256sha256Current (the default).
HashAlgorithm::Sha384sha384Current.
HashAlgorithm::Sha512sha512Current.

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 crypto

By 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.