PHP-Hash-Funktionen im Vergleich: Länge, Tempo und Einsatzzweck
Eine Hash-Funktion bildet eine beliebig lange Eingabe auf einen Wert fester Länge ab. PHP 8.4.6 bringt dafür in hash_algos() 60 Verfahren mit (die Zahl hängt von Version und Build ab), dazu kommen BLAKE2b und SipHash aus der Erweiterung sodium. Meist braucht man sie für Cache-Schlüssel, Prüfsummen und kurze Codes für Short Links, selten für Passwörter; dann zählen Tempo und Kürze. Welches Verfahren du nimmst, hängt vom Zweck ab. Ich habe alle 62 Verfahren gemessen (Stand 29.10.2026, PHP 8.4.6) und die Ergebnisse so aufbereitet, dass du dir dein Verfahren für deinen Zweck heraussuchen kannst.
Welcher Hash passt zu welchem Zweck?
Welches Verfahren passt, hängt vom Zweck ab. Für Prüfsummen, Cache-Schlüssel und Deduplizierung genügt ein nicht kryptografischer Hash wie xxh3 oder crc32c. Für Integrität, Signaturen und Tokens nimmst du SHA-256, mit hash_hmac() bei Schlüsselbezug. Passwörter gehören ausschließlich in password_hash(). MD5 und SHA-1 taugen nur noch für Altformate.
| Zweck | Verfahren in PHP | Warum |
|---|---|---|
| Prüfsumme gegen Übertragungsfehler, ETag | crc32c, xxh3 |
sehr schnell, zufällige Fehler erkennt schon ein 32-Bit-Wert |
| Cache-Schlüssel, Deduplizierung, wenn niemand die Eingabe gezielt wählt | xxh3 (64 Bit), xxh128 (128 Bit) |
schnell bei großen Eingaben, 64 Bit kollidieren erst bei Milliarden Werten; bei Eingaben von außen sha256 oder hash_hmac() mit Schlüssel |
| Kurzer Code für Short Links | Zähler in base62, Zufall (random_bytes()) oder gekürzter sha256 |
der Zähler ist am kürzesten und kollisionsfrei; gekürzte Hashes brauchen mindestens 8 Zeichen und einen UNIQUE-Index |
| Hash-Tabelle mit fremden Eingaben | sodium_crypto_shorthash() (SipHash) mit geheimem Schlüssel aus random_bytes() |
schlüsselabhängig, ein Angreifer ohne Schlüssel kann Kollisionen nicht vorhersagen |
| Integrität gegen Zufallsfehler, Token-Ablage | sha256 |
kryptografisch, in jedem PHP-Build vorhanden |
| Echtheit einer Nachricht (Signatur) | hash_hmac('sha256', …) mit geheimem Schlüssel oder eine echte Signatur |
ein nacktes SHA-256 beweist keine Herkunft |
| Kryptografischer Hash mit Schlüsselmodus | sodium_crypto_generichash() (BLAKE2b) |
kryptografisch, braucht ext-sodium; kein Passwort-Hash. Schlüssel aus Schlüsselmaterial leitest du mit hash_hkdf() ab, Passphrasen gehören in password_hash() |
| Passwörter | password_hash() mit PASSWORD_ARGON2ID |
absichtlich langsam und speicherhungrig |
| Altformate, Kompatibilität | md5, sha1 |
nur wenn das Fremdsystem es verlangt |
So sieht das im Code aus:
// Cache-Schlüssel, ETag, Deduplizierung: nicht kryptografisch genügt, solange niemand die Eingabe gezielt wählt
$key = hash('xxh3', $routeName); // 16 Hex-Zeichen; $routeName stammt aus dem eigenen Code (bei Eingaben von außen: sha256)
$etag = '"' . hash('crc32c', $body) . '"'; // 8 Hex-Zeichen
// Integrität und Tokens: kryptografisch
$tokenHash = hash('sha256', $token); // $token = random_bytes(32), nicht erratbar
$mac = hash_hmac('sha256', $body, $secret);
if (!hash_equals($expectedMac, $mac)) {
// ablehnen
}
// Passwörter: nie hash(), immer password_hash()
$stored = password_hash($password, PASSWORD_ARGON2ID);
if (password_verify($input, $stored) and password_needs_rehash($stored, PASSWORD_ARGON2ID)) {
$stored = password_hash($input, PASSWORD_ARGON2ID); // Kosten über die Jahre anheben
}
Wer Cache-Schlüssel in APCu ablegt, findet die passende Umgebung in APCu Shared Memory für PHP konfigurieren.
Wie schnell sind die PHP-Hash-Verfahren?
Bei 1 MiB Eingabe schaffen crc32b, crc32c und xxh3 auf meinem Rechner 49 bis 53 GB/s, SHA-256 4,2 GB/s, SHA-1 und MD5 unter 1 GB/s, SHA-512 0,64 GB/s. SHA-256 liegt damit rund fünfmal vor MD5. Die langsamsten Altverfahren (md2, snefru, gost) erreichen nur 19 bis 85 MB/s.
Die Tabelle zeigt dreizehn Verfahren in drei Eingabegrößen. Die Spalte „16 B“ ist die Zeit für einen einzelnen Aufruf von hash() mit 16 Byte Eingabe, die beiden anderen sind Durchsatz. Die vollständige Liste aller 62 Verfahren gibt dir das Messskript weiter unten für deinen eigenen Server aus.
| Verfahren | Bits | Hex-Zeichen | 16 B | 4 KiB | 1 MiB |
|---|---|---|---|---|---|
crc32b (= crc32()) |
32 | 8 | 44 ns | 33,9 GB/s | 52,3 GB/s |
crc32c |
32 | 8 | 45 ns | 33,7 GB/s | 52,7 GB/s |
xxh3 |
64 | 16 | 40 ns | 31,7 GB/s | 49,0 GB/s |
xxh128 |
128 | 32 | 48 ns | 29,8 GB/s | 49,0 GB/s |
xxh64 |
64 | 16 | 40 ns | 19,5 GB/s | 23,7 GB/s |
murmur3f |
128 | 32 | 38 ns | 9,2 GB/s | 9,9 GB/s |
sha256 |
256 | 64 | 70 ns | 3,8 GB/s | 4,2 GB/s |
siphash-2-4 (sodium) |
64 | 16 | 33 ns | 3,7 GB/s | 3,8 GB/s |
blake2b (sodium) |
256 | 64 | 116 ns | 1,8 GB/s | 1,85 GB/s |
sha1 |
160 | 40 | 110 ns | 922 MB/s | 950 MB/s |
md5 |
128 | 32 | 115 ns | 771 MB/s | 800 MB/s |
sha3-256 |
256 | 64 | 229 ns | 730 MB/s | 779 MB/s |
sha512 |
512 | 128 | 256 ns | 618 MB/s | 639 MB/s |
Fünf Beobachtungen aus der Messung:
- SHA-256 ist auf diesem Rechner rund fünfmal schneller als MD5. Bei 4 KiB ist es das 5,0-Fache, bei 1 MiB das 5,2-Fache. Die verbreitete Annahme „MD5 ist schnell, SHA-256 ist sicher, aber langsam“ stimmt hier nicht. Wie weit die CPU das Tempo beeinflusst, hängt vom Prozessor und vom PHP-Build ab. Miss deshalb auf deinem Server.
xxh3ist so schnell wiecrc32und hat 64 statt 32 Bit. Für Cache-Schlüssel ist das der bessere Kompromiss, denn 32 Bit kollidieren zu früh (nächster Abschnitt).- Bei 16 Byte bestimmt der Aufrufrahmen mit. Schon eine leere Schleife kostet 7 ns, der Aufruf von
hash()mit der schnellsten Prüfsumme 44 ns.xxh3braucht 40 ns, SHA-256 70 ns, MD5 115 ns. Bei kurzen Schlüsseln liegen die Unterschiede damit im Bereich von Nanosekunden und fallen gegen einen Datenbank- oder Dateizugriff nicht ins Gewicht. - Die Kurzformen
md5(),sha1()undcrc32()sparen 9 bis 17 ns je Aufruf, Hex- statt Binärausgabe kostet bei SHA-256 rund 13 ns. Beides zählt erst bei Millionen Aufrufen. - Die Altverfahren sind sehr langsam.
whirlpoolschafft 201 MB/s,gost84 MB/s,snefru47 MB/s,md219 MB/s, das 260- bis 2.800-Fache langsamer alscrc32c. Diese Verfahren gibt es aus Kompatibilitätsgründen, für neue Anwendungen brauchst du sie nicht.
Wo ein Hash in einer Anwendung wirklich Zeit kostet, zeigt erst das Profil. Bei einem Cache-Schlüssel mit MD5 sind es 115 ns, die Abfrage oder Dateioperation dahinter braucht Millisekunden. Die häufigeren Bremsen beschreibt PHP-Performance: die acht teuersten Fehler im Code.
Welcher Hash eignet sich für Cache-Schlüssel und Short Links?
Für Cache-Schlüssel ist xxh3 bei URL-langen Eingaben am schnellsten (54 ns gegen 212 ns für MD5), solange niemand die Eingabe gezielt wählt. Für Short Links ist ein Zähler in base62 am kürzesten und kollisionsfrei: 6 Zeichen reichen für 56 Milliarden Links. Ein gekürzter Hash braucht mindestens 8 Zeichen und eine Kollisionsbehandlung.
Cache-Schlüssel: schnell und kurz
Ein Cache-Schlüssel entsteht bei jedem Zugriff neu, deshalb zählt der Hash pro Aufruf. Gemessen mit einer URL von 90 Byte als Eingabe und Hex-Ausgabe, wie im Betrieb:
| Verfahren | Länge als Hex | Zeit je Schlüssel |
|---|---|---|
xxh3 |
16 Zeichen | 54 ns |
crc32b |
8 Zeichen | 67 ns |
xxh128 |
32 Zeichen | 67 ns |
sha256 |
64 Zeichen | 111 ns |
sha1 |
40 Zeichen | 196 ns |
md5 |
32 Zeichen | 212 ns |
xxh3ist bei dieser Eingabe viermal schneller als MD5 und halb so lang.crc32bist kürzer, aber mit 32 Bit als alleiniger Schlüssel zu kollisionsanfällig (siehe unten).- Der Hash fällt gegen den Zugriff dahinter kaum ins Gewicht. Auf Cache, Datei oder Datenbank wartest du länger als auf die Berechnung; entscheidend sind Schlüssellänge und Kollisionsfreiheit.
- Rohbytes halbieren die Länge.
hash('xxh3', $key, true)liefert 8 statt 16 Zeichen. Ob dein Cache-Backend Binärschlüssel erlaubt, steht in dessen Dokumentation; sonst nimm Hex oder base64url. - Ein Präfix mit Version macht das Leeren einfach. Mit
'artikel:v3:' . hash(...)entwertest du alle Schlüssel einer Gruppe, indem du die Version erhöhst. - Eingaben von außen brauchen ein kryptografisches Verfahren.
sha256kostet 111 ns und lässt sich auf 32 Hex-Zeichen (128 Bit) kürzen.
Short Links: Zähler, Zufall oder gekürzter Hash?
Für kurze Codes gibt es drei Wege. Die Rechenzeit liegt bei 50 bis 170 ns und spielt gegen den Datenbankzugriff keine Rolle. Entscheidend sind Länge, Kollisionen und die Frage, ob Codes erratbar sein dürfen.
| Weg | Beispiel in PHP | Zeit | Kollisionen | Erratbar |
|---|---|---|---|---|
| Zähler in base62 | toBase62($id) |
168 ns | keine | ja, aufeinanderfolgende Codes |
| Zufall | bin2hex(random_bytes(4)) |
53 ns | möglich, UNIQUE-Index nötig | nein |
| Hash der URL, gekürzt | base64url(substr(hash('xxh3', $url, true), 0, 6)) |
105 ns | möglich, UNIQUE-Index nötig | nein; gleiche URL ergibt gleichen Code |
Wie lang ein Code sein muss, hängt von der Zahl der Links ab. Die Tabelle zeigt für zufällig verteilte Codes, ab wann Kollisionen wahrscheinlich werden:
| Länge des Codes | Mögliche Codes | Kollision zu 50 % bei | Bei 100.000 Links | Bei 1 Million Links |
|---|---|---|---|---|
| 8 Hex-Zeichen (32 Bit) | 4,3 Milliarden | 77.000 | 69 % | praktisch sicher |
| 6 Zeichen base62 | 56,8 Milliarden | 281.000 | 8,4 % | praktisch sicher |
| 7 Zeichen base62 | 3,5 Billionen | 2,2 Millionen | 0,14 % | 13 % |
| 8 Zeichen base62 | 218 Billionen | 17,4 Millionen | 0,002 % | 0,23 % |
| 8 Zeichen base64url (48 Bit) | 281 Billionen | 19,8 Millionen | 0,002 % | 0,18 % |
Aus der Tabelle folgen vier Regeln:
- Acht Hex-Zeichen taugen nicht als Short-Link-Code. Bei 100.000 Links kollidieren sie mit 69 % Wahrscheinlichkeit.
- Gekürzte Hashes brauchen mindestens 8 Zeichen aus base62 oder base64url und immer einen UNIQUE-Index auf dem Code. Bei einer Kollision hashst du mit einem Zähler als Salz erneut.
- Kommen die URLs von Nutzern, nimm
sha256stattxxh3. Mit einem nicht kryptografischen Hash kann ein Angreifer Kollisionen gezielt erzeugen. SHA-256 kostet rund 60 ns mehr. - Aufeinanderfolgende Codes lassen sich aufzählen. Sollen Links nicht öffentlich sein, nimm Zufall statt eines Zählers.
Zwei Bausteine dafür:
// Zähler → base62: die kürzesten Codes, keine Kollisionen
function toBase62(int $number): string
{
$alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$out = '';
do {
$out = $alphabet[$number % 62] . $out;
$number = intdiv($number, 62);
} while ($number > 0);
return $out;
}
echo toBase62(12_345_678); // PNFQ (4 Zeichen)
echo toBase62(56_800_235_583); // ZZZZZZ: 6 Zeichen reichen für 56,8 Milliarden Links
// Hash der URL → 8 Zeichen base64url (48 Bit), gleiche URL ergibt gleichen Code
$code = rtrim(strtr(base64_encode(substr(hash('sha256', $url, true), 0, 6)), '+/', '-_'), '=');
Wie lang ist ein Hash, und wann kollidiert er?
Vier Bit ergeben ein Hex-Zeichen: crc32 liefert 8 Zeichen, xxh3 16, MD5 32, SHA-1 40, SHA-256 64 und SHA-512 128. Je kürzer der Wert, desto früher kollidieren zufällig verschiedene Eingaben: Ein 32-Bit-Hash erreicht schon bei etwa 77.000 Werten eine Kollisionschance von 50 %, ein 64-Bit-Hash erst bei rund 5 Milliarden.
| Länge | Beispiele | Bytes (binär) | Hex-Zeichen | Kollision zu 50 % bei etwa |
|---|---|---|---|---|
| 32 Bit | crc32b, crc32c, xxh32 |
4 | 8 | 77.000 Werten |
| 64 Bit | xxh3, xxh64, SipHash |
8 | 16 | 5 Milliarden Werten |
| 128 Bit | md5, xxh128 |
16 | 32 | 2 · 10¹⁹ Werten |
| 256 Bit | sha256, sha3-256, blake2b |
32 | 64 | praktisch nie |
Die Werte folgen dem Geburtstagsparadox: Eine Kollision wird wahrscheinlich, sobald etwa 1,18 · 2^(n/2) verschiedene Werte gehasht wurden, bei n Bit Länge. Das gilt für zufällig verteilte Eingaben. Nicht kryptografische Hashes wie crc32 oder xxh3 schützen nicht vor einem Angreifer, der Eingaben gezielt wählt: Er kann Kollisionen erzeugen. Dagegen hilft nur ein kryptografisches Verfahren, und auch das nicht, wenn es MD5 oder SHA-1 heißt.
Zwei Praxisregeln folgen daraus:
- Speicher halbieren:
hash($algo, $data, true)liefert die Rohbytes. Ein SHA-256 braucht dann 32 statt 64 Byte, in PostgreSQL alsbytea, in MySQL alsBINARY(32). - 32-Bit-Werte nur mit Kollisionsbehandlung: Ein
crc32als alleiniger Schlüssel einer Tabelle mit hunderttausend Zeilen kollidiert mit hoher Wahrscheinlichkeit. Als Prüfsumme neben dem Inhalt ist er dagegen richtig.
Vorsicht bei den CRC-Varianten: hash('crc32b', $s) und crc32($s) liefern denselben Wert, hash('crc32', $s) nicht. Das ist die bzip2-Variante mit anderer Byte-Reihenfolge. crc32c verwendet ein anderes Polynom (Castagnoli) und liefert wieder andere Werte. Für „Hallo Welt“ ergibt sich 50281475 (crc32b), 5a9c5320 (crc32) und 5a8fb19f (crc32c).
Warum taugen MD5 und SHA-1 nicht mehr für Sicherheit?
Für beide Verfahren lassen sich Kollisionen gezielt erzeugen: bei MD5 seit 2004, bei SHA-1 seit der ersten öffentlichen Kollision 2017. Ein Angreifer kann damit zwei verschiedene Dokumente mit demselben Hash herstellen. Als Prüfsumme gegen zufällige Fehler und als Cache-Schlüssel sind beide weiter brauchbar, für Signaturen und Integrität nicht.
Es gibt nur noch einen Grund, MD5 oder SHA-1 zu verwenden: Ein Fremdsystem oder ein Dateiformat verlangt es. Der frühere Tempovorteil ist in der Messung nicht mehr sichtbar: SHA-256 war bei 4 KiB und 1 MiB schneller als MD5 und SHA-1. In meiner Messung (Intel Core Ultra 9, PHP 8.4.6) entfällt damit das Tempo-Argument für neue Anwendungen. Ob das auch auf deinem Server gilt, zeigt erst deine eigene Messung.
Warum gehören Passwörter nicht in hash()?
Ein schneller Hash ist für Passwörter ein Fehler, weil ein Angreifer mit einer gestohlenen Hash-Tabelle sehr viele Kandidaten pro Sekunde durchprobiert. password_hash() ist deshalb absichtlich langsam: Ein Argon2id-Hash mit den PHP-Standardwerten dauert 162 ms, ein SHA-256 über 16 Byte 70 ns, ein Faktor von rund 2,3 Millionen.
| Verfahren | Zeit je Hash | Hashes pro Sekunde |
|---|---|---|
sha256 über ein kurzes Passwort |
70 ns | rund 14 Millionen |
| bcrypt, cost 10 | 38 ms | 26 |
| bcrypt, cost 12 (PHP-Standard ab 8.4) | 152 ms | 6,6 |
| bcrypt, cost 14 | 616 ms | 1,6 |
| Argon2id, 64 MiB, t=4, p=1 (PHP-Standard) | 162 ms | 6,2 |
| Argon2id, 64 MiB, t=4, p=3 | 65 ms | 15 |
| Argon2id, 19 MiB, t=2, p=1 (OWASP-Untergrenze) | 21 ms | 47 |
| PBKDF2-HMAC-SHA256, 600.000 Iterationen | 74 ms | 13,5 |
Die Spalte „Hashes pro Sekunde“ zeigt, was ein einzelner PHP-Prozess auf dieser CPU schafft, nicht die Geschwindigkeit eines Angreifers: Mit Grafikkarten erreicht er bei schnellen Verfahren wie SHA-256 um Größenordnungen mehr. Bei Argon2id mit mehreren Threads (p=3) ist die Wanduhrzeit kürzer, die CPU-Zeit je Anmeldung nicht, und threads über 1 wirkt laut PHP-Handbuch nur in Builds mit libargon2, nicht mit der sodium-Implementierung.
Was daraus folgt:
- Nimm
PASSWORD_ARGON2ID, wenn dein PHP-Build es unterstützt. Speicherhunger macht Angriffe mit Grafikkarten teuer. bcrypt schneidet Passwörter bei 72 Byte ab, lange Passphrasen werden damit nicht vollständig geprüft. - Wähle die Kosten nach deiner Anmelde-Last. Die OWASP-Werte sind eine Untergrenze. Wie weit du darüber liegst, bestimmen die Zeit pro Anmeldung und die Zahl gleichzeitiger Anmeldungen auf dem Server.
- Rechne mit steigenden Kosten.
password_needs_rehash()meldet beim nächsten erfolgreichen Login, dass der gespeicherte Hash nicht mehr zu den aktuellen Parametern passt (siehe Codebeispiel oben). - Plane die Spaltenlänge großzügig. Die Konstante
PASSWORD_DEFAULTkann sich ändern, das Handbuch rät zu mindestens 255 Byte.
Und Tokens?
Ein Login-Link oder ein „angemeldet bleiben“-Token darf als SHA-256 in der Datenbank stehen, wenn der Wert aus random_bytes() stammt und mindestens 128 Bit lang ist. Der Unterschied zum Passwort ist die Entropie: Ein zufälliger 256-Bit-Wert lässt sich nicht erraten, auch nicht mit einer schnellen Hash-Funktion. Ein vom Menschen gewähltes Passwort schon. Geprüft wird über den Datenbank-Lookup des Hashes oder mit hash_equals(), und ein Token braucht ein Ablaufdatum und Einmalnutzung. In meinen eigenen Systemen liegen deshalb Passwörter als Argon2id-Hash und Token nur als SHA-256 ihres Zufallswerts in der Datenbank. Wer die Grundlagen systematisch aufbauen will, findet password_hash() und password_verify() in der Agenda von PHP Programmierung Grundlagen.
Ist ein gehashter Wert anonym?
Ein Hash anonymisiert Werte aus einem kleinen Raum nicht. Bei IPv4-Adressen lässt sich jeder mögliche Wert durchprobieren: Alle 4,3 Milliarden Adressen kosten mit xxh128 hochgerechnet etwa 4 Minuten auf einem Kern, mit SHA-256 etwa 5 Minuten, mit MD5 etwa 8,5 Minuten.
| Verfahren | Zeit je Kandidat | Gesamter IPv4-Raum (2³² Adressen) |
|---|---|---|
xxh128 |
54,5 ns | 3,9 Minuten |
sha256 |
71,3 ns | 5,1 Minuten |
md5 |
118,9 ns | 8,5 Minuten |
Das ist eine Hochrechnung aus der Messung (Eingabe 30 Byte, ein Kern, Median aus 3 Läufen), kein Angriff. Mit mehreren Kernen oder einer Grafikkarte schrumpfen die Zeiten entsprechend. Ein Salt, das im Quelltext steht, ändert daran nichts, und ein kryptografisches Verfahren hilft kaum: SHA-256 ist hier sogar schneller als MD5. Schutz bieten vor allem ein geheimer Schlüssel, der nicht im Code steht (hash_hmac()), und das Kürzen oder Löschen der Adresse; auch ein geschlüsselter Hash bleibt ein Pseudonym. IPv6-Adressen lassen sich nicht vollständig durchprobieren, nach einem bekannten /64-Präfix aber stark eingrenzen.
Pseudonymisierte Daten bleiben für den personenbezogen, der die Zuordnung herstellen kann (Erwägungsgrund 26 DSGVO). Bei IP-Adressen ist das der Betreiber selbst, der alle Adressen durchprobieren kann: Ein Hash macht sie nicht anonym. Anders liegt es bei einem Hash über einen Zufallswert, den niemand einer Person zuordnen kann. Wie du IP-Adressen rechtssicher behandelst, klärst du mit deinem Datenschutzbeauftragten oder einer Rechtsberatung.
So misst du die Hash-Verfahren auf deinem Server
Miss mit einer Schleife, die hash() direkt aufruft, ohne Closure-Rahmen, und werte die beste von mehreren Runden. Schalte Xdebug aus, prüfe die Last auf dem Rechner und wiederhole die Läufe mit Pausen. Das Ergebnis von deinem Server ist belastbarer als jede Tabelle aus diesem Artikel, auch als meine.
Das Skript misst Durchsatz je Verfahren und gibt eine Tabelle aus:
<?php
declare(strict_types=1);
function nsPerCall(string $algo, string $data): float
{
hash($algo, $data, true); // Warmlauf
$best = INF;
for ($round = 0; $round < 5; $round++) { // beste von 5 Runden
$n = 0;
$t = hrtime(true);
do {
for ($i = 0; $i < 100; $i++) {
hash($algo, $data, true); // direkter Aufruf, kein Closure-Rahmen
}
$n += 100;
$elapsed = hrtime(true) - $t;
} while ($elapsed < 25_000_000); // jede Runde mindestens 25 ms
$best = min($best, $elapsed / $n);
}
return $best;
}
$data = random_bytes(4096);
$algos = hash_algos();
shuffle($algos); // feste Reihenfolge verzerrt bei Dauerlast
foreach ($algos as $algo) {
$ns = nsPerCall($algo, $data);
printf("%-12s %4d Bit %8.0f MB/s\n", $algo, strlen(hash($algo, '', true)) * 8, strlen($data) / $ns * 1000);
}
Aufruf: php -d xdebug.mode=off hash-benchmark.php. Wer eine fertige Messklasse mit Prozentvergleich („100 % = am schnellsten“) bevorzugt, findet sie in meinem Open-Source-Repository perf auf GitHub.
Meine eigene Messung hat sich dreimal selbst widerlegt, und das ist der Teil, den ich für wichtig halte:
- Erste Fassung: Aufrufrahmen mitgemessen. Ich rief das Verfahren über eine Closure pro Durchlauf auf. Das kostet rund 40 ns und machte bei 16 Byte alle schnellen Verfahren gleich schnell. Die direkte Schleife zeigte den Unterschied.
- Zweite Fassung: Laptop gedrosselt. Nach etwa 30 Sekunden Dauerlast fielen die Werte auf die Hälfte (SHA-256 bei 1 MiB: 1,7 statt 4,1 GB/s), die Streuung stieg auf bis zu 37 %. Argon2id maß im Sammellauf 349 ms, in einem eigenen Prozess mit Pause 162 ms. Deshalb besteht die Messung jetzt aus Läufen von 30 Sekunden mit Pausen, und jede Passwort-Variante läuft in einem eigenen Prozess.
- Dritte Fassung: feste Reihenfolge. Alle 62 Verfahren liefen in jedem Lauf in derselben Reihenfolge, also landeten dieselben am Ende, wenn der Laptop schon drosselte:
crc32ckam auf 22 statt 53 GB/s, BLAKE2b auf 0,78 statt 1,85 GB/s. Seit das Skript die Reihenfolge je Lauf mischt, streuen die Läufe nur noch um 1,7 %, und die Werte decken sich mit meinem ersten Lauf auf dem kühlen Rechner.
Die Umgebung wirkt auch im Betrieb: Wie du OPcache, FPM und die Pool-Größe dafür einstellst, beschreibt PHP-Konfiguration: OPcache und FPM richtig einstellen. Das systematische Messen und Gegenmessen von Flaschenhälsen ist Teil von PHP OOP Fortgeschrittene Technologien.
Häufige Fragen (FAQ)
Welcher Hash ist in PHP am schnellsten?
In meiner Messung sind es bei 1 MiB Eingabe crc32c, crc32b und xxh3 mit 49 bis 53 GB/s, gefolgt von xxh128 mit 49 GB/s. Alle vier sind nicht kryptografisch. Der schnellste kryptografische Hash ist SHA-256 mit etwa 4 GB/s. Bei 16 Byte Eingabe bestimmt der Funktionsaufruf mit: 33 bis 48 ns für die schnellen Verfahren, 70 ns für SHA-256, 115 ns für MD5.
Welchen Hash nehme ich für einen Cache-Schlüssel?
xxh3 oder xxh128, solange niemand die Eingabe gezielt wählt: Bei einer URL mit 90 Byte brauchte xxh3 54 ns und liefert 16 Hex-Zeichen, MD5 brauchte 212 ns bei 32 Zeichen. Kommt die Eingabe von außen, nimm sha256 (111 ns), bei Bedarf auf 32 Hex-Zeichen gekürzt.
Wie erzeuge ich einen kurzen Code für einen Short Link in PHP?
Am kürzesten wird ein Zähler in base62: 6 Zeichen reichen für 56,8 Milliarden Links, ohne Kollisionen. Sollen Codes nicht erratbar sein, nimm Zufall mit random_bytes() oder einen gekürzten Hash mit mindestens 8 Zeichen base62 oder base64url. Beides braucht einen UNIQUE-Index und eine Wiederholung bei Kollision.
Ist MD5 in PHP noch zu gebrauchen?
Als Prüfsumme gegen zufällige Fehler und als Cache-Schlüssel ja, für alles mit Sicherheitsbezug nein. Kollisionen lassen sich bei MD5 seit 2004 gezielt erzeugen. Einen Tempovorteil gegenüber SHA-256 gab es in meiner Messung (Intel Core Ultra 9, PHP 8.4.6) nicht: SHA-256 war bei 1 MiB rund fünfmal schneller. Auf anderer Hardware kann das anders sein.
Welchen Hash nehme ich für Passwörter in PHP?
Keinen von hash_algos(). Nimm password_hash($passwort, PASSWORD_ARGON2ID) und prüfe mit password_verify(). Die Funktion setzt Salt und Kosten selbst. Als Untergrenze nennt OWASP für Argon2id 19 MiB Speicher, 2 Iterationen und 1 Thread; die PHP-Standardwerte (64 MiB, 4 Iterationen) liegen darüber.
Wie lang ist ein SHA-256-Hash?
256 Bit, also 32 Byte oder 64 Hex-Zeichen. Mit hash('sha256', $daten, true) bekommst du die 32 Rohbytes, die in der Datenbank nur halb so viel Platz brauchen wie die Hex-Darstellung. In PostgreSQL passt dafür bytea, in MySQL BINARY(32).
Was ist der Unterschied zwischen crc32, crc32b und crc32c in PHP?
hash('crc32b', $s) liefert denselben Wert wie die Funktion crc32($s). hash('crc32', $s) ist die bzip2-Variante mit anderer Byte-Reihenfolge, hash('crc32c', $s) verwendet das Castagnoli-Polynom. Alle drei sind 32 Bit lang und nur zur Fehlererkennung gedacht, nicht zur Sicherheit. Für „Hallo Welt“ ergeben sie drei verschiedene Werte.
Wann reicht ein 32-Bit-Hash nicht mehr?
Sobald viele verschiedene Werte eindeutig bleiben müssen. Ein 32-Bit-Hash kollidiert bei etwa 77.000 zufällig verteilten Werten mit 50 % Wahrscheinlichkeit, ein 64-Bit-Hash wie xxh3 erst bei rund 5 Milliarden. Als alleiniger Schlüssel in einer Tabelle mit Hunderttausenden Zeilen ist crc32 deshalb ungeeignet.
Ist ein gehashter Wert aus einem kleinen Wertebereich, etwa eine IP-Adresse, anonym?
Nein. Bei kleinen Wertebereichen wie IPv4-Adressen lassen sich alle 4,3 Milliarden Möglichkeiten in wenigen Minuten auf einem Kern durchrechnen, auch mit SHA-256. Solche Hashes sind Pseudonyme und bleiben für den Betreiber, der die Zuordnung herstellen kann, personenbezogen (Erwägungsgrund 26 DSGVO).
Wie viele Hash-Verfahren hat PHP?
In PHP 8.4.6 auf Windows listet hash_algos() 60 Verfahren auf. Die Zahl hängt von Version und Build ab. Dazu kommen mit der Erweiterung sodium BLAKE2b (sodium_crypto_generichash()) und SipHash (sodium_crypto_shorthash()). Für neue Anwendungen reichen drei Bausteine: xxh3 für Schlüssel aus eigenen Daten, sha256 mit hash_hmac() für Integrität und Tokens und password_hash() für Passwörter. blake2b ist eine Option, wenn ext-sodium vorhanden ist.
Verwandte Seminare und Artikel
- PHP-Performance: die acht teuersten Fehler im Code — die acht teuersten Fehler im PHP-Code, mit Messung und Auflösung
- PHP schneller konfigurieren: OPcache, Preloading, FPM — OPcache, Preloading und FPM, die Betriebsseite der PHP-Performance
- APCu Shared Memory für PHP konfigurieren: FPM, Apache mod_php und CLI im Vergleich — Cache-Schlüssel und Shared Memory unter FPM, mod_php und CLI
- PHP Programmierung Grundlagen — PHP von Grund auf, einschließlich Formularen, Sessions und
password_hash() - PHP OOP Fortgeschrittene Technologien — objektorientierte PHP-Entwicklung mit Performance und Benchmarking
- KI & VibeCoding für PHP-Entwickler — Performance- und Sicherheits-Audits im PHP-Projekt, jeder Befund mit Messung
Im Seminar vertiefen
Zu diesem Thema gibt es Seminare und Beratung. Die Agenda von PHP OOP Fortgeschrittene Technologien enthält den Punkt „Performance & Benchmarking: Flaschenhälse im Code vermeiden“, das Seminar KI & VibeCoding für PHP-Entwickler behandelt Audits auf Korrektheit, Sicherheit und Performance. Das Angebot richtet sich an Unternehmen, Behörden und Selbstständige.
Die Anfrage ist unverbindlich und kostenlos; Umfang und Konditionen klären wir danach individuell.
Inhouse-Seminar: PHP OOP Fortgeschrittene Technologien
Die Agenda enthält „Performance & Benchmarking: Flaschenhälse im Code vermeiden“. Das Seminar findet inhouse oder remote statt, bei Bedarf am Code deines Teams.
Coaching am Arbeitsplatz
Du willst wissen, welches Verfahren in deiner Anwendung passt oder ob die Kosten deiner Passwort-Hashes stimmen? Wir messen gemeinsam auf deiner Hardware, im Raum Köln und Leverkusen vor Ort, bundesweit inhouse oder remote.