NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Crypto for Laravel

HMAC, hašovanie a HKDF

Hash\Hmac podpisuje a overuje kľúčované MAC — základ kontroly podpisov webhookov. Tajné kľúče sa odovzdávajú pri každom volaní a sú označené #[\SensitiveParameter]; trieda nedrží žiadny vlastný kľúč:

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

// A checksum of a file of any size, read in chunks:
$checksum = Crypto::digest()->file($path);

// A per-purpose subkey from one root secret (HKDF):
$fingerprintKey = Crypto::hkdf()->derive($rootKey, 32, 'app/fingerprint');

Alebo volajte priamo triedy, ktoré fasáda sprístupňuje:

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);
MetódaVracia
sign($message, $key)HMAC ako surové bajty.
signHex($message, $key)HMAC ako hex malými písmenami.
verify($message, $signature, $key)bool — overenie podpisu v surových bajtoch v konštantnom čase.

Deterministické digesty

Hash\Digest pre rovnaký vstup vždy vráti rovnaký výstup — práve to umožňuje indexované vyhľadávanie podľa zhody nad hašovanými dátami, napríklad slepý index nad e-mailom alebo tokenom:

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() robí voľbu medzi obyčajným a kľúčovaným digestom explicitnou: obyčajný digest zvolí iba null. Akýkoľvek iný pepper — aj medzera či prázdny reťazec — sa použije doslovne ako HMAC kľúč, takže prázdna hodnota nikdy potichu nezníži kľúčovaný digest na obyčajný. Dáta odovzdané do raw(), hex() a withPepper() — tokeny, heslá, osobné údaje — sú tiež označené #[\SensitiveParameter].

MetódaVracia
raw($data)Digest ako surové bajty.
hex($data)Digest ako hex malými písmenami.
withPepper($data, $pepper)Hex digest; ak pepper nie je null, ide o HMAC s pepperom ako kľúčom.
file($path, $pepper = null)Hex digest súboru, čítaného po častiach.
stream($stream, $pepper = null)Hex digest streamu od jeho aktuálnej pozície, čítaného po častiach.

Kontrolné súčty súborov a streamov

file() a stream() spočítajú digest vstupu akejkoľvek veľkosti pri stálej spotrebe pamäte: hašovací kontext plnia po častiach pevnej veľkosti a celý vstup nikdy nenačítajú. Použite ich na kontrolné súčty nahratých súborov, kľúče na deduplikáciu súborov a ETagy:

use Illuminate\Support\Facades\Storage;
use RoundlyConsulting\Crypto\Facades\Crypto;

$checksum = Crypto::digest()->file($request->file('csv')->getRealPath());   // sha-256 hex

// A Laravel disk (local, S3, …): hand over its read stream.
$stream = Storage::disk('imports')->readStream($path);
$checksum = Crypto::digest()->stream($stream);
fclose($stream);                                                           // still yours to close
  • Len hex. Obe metódy vracajú hex digest malými písmenami — tvar, ktorý ukladá stĺpec char(64) s kontrolným súčtom, ETag či kľúč na deduplikáciu —, preto variant v surových bajtoch neexistuje. Ak surové bajty potrebujete, výsledok dekódujte z hex (Crypto::hexDecode()).
  • Rovnaká hodnota ako pri digestoch reťazcov: pre tie isté bajty sa file() a stream() rovnajú hex() bez pepperu a withPepper() s pepperom, pri každom HashAlgorithm.
  • Pepper sa riadi pravidlom withPepper(): null (predvolené) dá obyčajný digest; akýkoľvek iný reťazec, aj prázdny, sa použije doslovne ako kľúč HMAC. Pepper je označený #[\SensitiveParameter]; cesta ani stream nie, keďže ani jedno neprenesie bajty do stack trace.
  • file($path) otvorí čokoľvek, čo prečíta fopen() — lokálnu cestu aj URL stream wrappera — a vlastný handle zatvorí, aj keď vyhodí výnimku. Pri Laravel disku namiesto toho odovzdajte jeho readStream() do stream().
  • stream($stream) číta od aktuálnej pozície streamu po jeho koniec a nikdy ho nepretáča: volajúci, ktorý už prečítal hlavičku, dostane digest zvyšku, a kto chce digest celého streamu, najprv ho pretočí. Stream nikdy nezatvorí a nechá ho na konci; stream, ktorý už je na konci, sa hašuje ako prázdny vstup.

Každý prípad, keď čítanie nedobehne do konca, vyhodí Hash\UnreadableInputException (CryptoException) namiesto digestu polovice bajtov: prázdna cesta alebo cesta s bajtom NUL, chýbajúci súbor, adresár, súbor, ktorý sa nedá otvoriť, hodnota, ktorá nie je otvorený stream (reťazec, zatvorený resource, stream context), handle adresára, stream len na zápis (prečítal by sa ako prázdny a dal by haš ničoho) a čítanie, ktoré sa zastaví pred koncom:

use RoundlyConsulting\Crypto\Facades\Crypto;
use RoundlyConsulting\Crypto\Hash\HashAlgorithm;
use RoundlyConsulting\Crypto\Hash\UnreadableInputException;

// Another algorithm and a pepper: the value withPepper() gives for the same bytes.
$keyed = Crypto::digest(HashAlgorithm::Sha512)->stream($stream, $pepper);

try {
    $checksum = Crypto::digest()->file($path);
} catch (UnreadableInputException $e) {
    // missing, a directory, unreadable or cut short — never a partial digest
}

HKDF — podkľúče pre každý účel

Hash\Hkdf implementuje odvodzovanie kľúčov extract-then-expand podľa RFC 5869: z jedného koreňového tajomstva nezávislé podkľúče pre každý účel, takže kľúč, ktorý pečatí hodnoty, nikdy zároveň nepočíta MAC odtlačku:

use RoundlyConsulting\Crypto\Facades\Crypto;
use RoundlyConsulting\Crypto\Hash\HashAlgorithm;

$fingerprintKey = Crypto::hkdf()->derive($rootKey, 32, 'app/fingerprint');       // no salt
$sealKey        = Crypto::hkdf(HashAlgorithm::Sha512)->derive($rootKey, 64, 'app/seal', 'app.hkdf/1');

Alebo volajte priamo triedu, ktorú fasáda sprístupňuje:

use RoundlyConsulting\Crypto\Hash\Hkdf;

$subkey = (new Hkdf)->derive($rootKey, 32, 'app/fingerprint');   // SHA-256 by default

derive(string $ikm, int $length, string $info = '', string $salt = '') vracia surové bajty:

  • $ikm, vstupný kľúčový materiál, je #[\SensitiveParameter] a nesmie byť prázdny.
  • $length je povinná: 1 až 255 × dĺžka hašu (8160 bajtov pre SHA-256). Funkcia hash_hkdf() v PHP číta 0 ako „jedna dĺžka hašu“; tu sa 0 odmietne, takže veľkosť odvodeného kľúča nikdy nie je implicitná.
  • $info pomenúva účel. Jeho polia oddeľte bajtom, ktorý žiadne pole nemôže obsahovať, napríklad NUL.
  • $salt je hodnota, ktorá nie je tajná (RFC 5869 §3.1); prázdna znamená „neuvedená“ — HashLen nulových bajtov.
  • Výstup je bajt po bajte zhodný s hash_hkdf(), čo pripínajú vektory z prílohy A RFC 5869 (SHA-256 aj SHA-1). SHA-1 zostáva dostupné len pre kompatibilitu (HashAlgorithm::isLegacy()).
  • Prázdny $ikm alebo $length mimo rozsahu vyhodí Hash\InvalidHkdfParameterException — programátorská chyba, nikdy nie vlastnosť dát.

Porovnanie v konštantnom čase

ConstantTime::equals() obaľuje hash_equals, aby každé porovnanie tajného kľúča, tagu či MAC prechádzalo jedným miestom a časovaním neprezradilo, aká časť hodnoty sa zhodovala. Oba vstupy sú #[\SensitiveParameter], rovnako ako pri hash_equals() v PHP: očakávaná hodnota je samotné tajomstvo, MAC, token či OTP, takže ani jeden sa nikdy nedostane do stack trace:

use RoundlyConsulting\Crypto\Hash\ConstantTime;

// $known is the trusted value, $user the attacker-influenced one:
$ok = ConstantTime::equals($knownExpected, $userSupplied);

Hašovacie algoritmy

PrípadHodnotaStav
HashAlgorithm::Sha1sha1Zastaraný — prelomený kolíziami, len pre kompatibilitu; isLegacy() vracia true.
HashAlgorithm::Sha256sha256Aktuálny (predvolený).
HashAlgorithm::Sha384sha384Aktuálny.
HashAlgorithm::Sha512sha512Aktuálny.

OTP je oddelené: RFC 6238 predpisuje SHA-1 a autentifikačné aplikácie ho používajú predvolene, preto Otp\OtpAlgorithm ponecháva SHA-1 ako plnohodnotnú voľbu.

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.