Aktuel situation: PHP2Shell. To kritiske PHP-sårbarheder (CVSS 9,2) tillader skadelig kode uden login. Rettelser siden 21.10.2026.Hvad der skal gøres nu →

Sikre tilfældige tal og tokens i PHP med random_bytes

Hvordan udviklere i PHP genererer tokens til adgangskodelinks, API-nøgler og engangskoder: kryptografisk sikre kilder, tilstrækkelig længde, lagring som hash og begrænset gyldighed.

Status 5 min. læsetidSikker udvikling

Kort sagt: Sikre tokens genererer I i PHP med random_bytes(), f.eks. 32 bytes, som I omdanner til tekst med bin2hex() eller Base64-URL. Til tilfældige tal i et interval findes random_int(), til koder fra et fast tegnforråd siden PHP 8.3 Random\Randomizer::getBytesFromString(). Tokens gemmer I som hash, begrænser deres gyldighed, bruger dem kun én gang og sammenligner dem med hash_equals().

"Glemt adgangskode?" Et klik, og systemet sender et link med en lang kode. Den, der kender linket, kan sætte adgangskoden. Netop derfor skal denne kode være uforudsigelig. Er den ikke det, har en angriber hverken brug for postkassen eller den gamle adgangskode, kun for nok forsøg.

Ved kontrol af PHP-applikationer findes overraskende ofte tokens, der er bygget af klokkeslættet, bruger-ID'et eller en simpel tilfældighedsgenerator. De ser lange og tilfældige ud, men er det ikke. Det samme gælder API-nøgler, invitationslinks, delingslinks til dokumenter, bekræftelseskoder og CSRF-tokens.

Risikoen er håndgribelig: Et forudsigeligt nulstillingslink tillader overtagelse af enhver konto, hvis e-mailadresse er kendt, uden at ejeren opdager noget, før han selv ikke længere kan logge ind.

PHP tilbyder siden version 7.0 en enkel og sikker løsning. Den skal blot bruges konsekvent.

random_bytes i PHP: Hvor sikre tilfældige værdier kommer fra

random_bytes() leverer ifølge PHP-manualen kryptografisk sikre tilfældige bytes, som også egner sig til langlivede hemmeligheder som nøgler. Funktionen bruger operativsystemets tilfældighedskilde, under Linux f.eks. getrandom(), under Windows CNG-grænsefladen. Finder den ingen egnet kilde, kaster den siden PHP 8.2 en Random\RandomException.

Oversigten viser, hvilke funktioner der duer til hemmeligheder:

Funktion Egnet til tokens? Bemærkning
random_bytes() ja sikre bytes, siden PHP 7.0
random_int() ja sikkert tal i et interval, siden PHP 7.0
Random\Randomizer med Secure-engine ja Secure er ifølge manualen standard-engine
rand(), mt_rand() nej forudsigelige, kun til ikke-sikkerhedsrelevante formål
uniqid() nej tidsbaseret, tænkt til entydighed
md5(time()), sha1($id) nej input kan gættes, hash ændrer intet ved det
str_shuffle(), array_rand() nej bruger ingen sikker generator

Generér tokens: Længde og kodning

32 tilfældige bytes svarer til 256 bit og er til næsten ethvert formål mere end nok. Til overførsel i links eller headere skal tokenet omdannes til tekst. To varianter er almindelige:

<?php
declare(strict_types=1);

// Hex: 64 tegn, kun 0-9 og a-f
$tokenHex = bin2hex(random_bytes(32));

// Base64-URL: 43 tegn, sikker i URL'er uden yderligere kodning
$tokenUrl = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

// Tilfældigt tal i et interval, f.eks. til et udvalg
$index = random_int(0, 9);

// Fra PHP 8.3: sekscifret talkode, f.eks. til en bekræftelse via SMS
$randomizer = new \Random\Randomizer();
$code = $randomizer->getBytesFromString('0123456789', 6);

Korte talkoder er kun forsvarlige, hvis antallet af forsøg er strengt begrænset, og koden udløber efter få minutter. Til links, der sendes pr. e-mail, tager I altid det lange token.

Til API-nøgler, som partnere eller egne systemer bruger varigt, har to tilføjelser vist sig at fungere. Et kort, ikke-hemmeligt præfiks foran den tilfældige del, f.eks. for miljø eller formål, gør det lettere at genkende i logge og ved fund i kode-repositories. Og nøglen vises kun én gang ved oprettelsen; derefter kender systemet kun dens hash. Går en nøgle tabt, erstattes den, ikke gendannes.

Forsigtig med selvbyggede tokens, der indeholder oplysninger, f.eks. bruger-ID og udløbstid i Base64. Kodning er ikke sikring: Enhver kan læse og ændre sådanne tokens. Skal et token bære data, skal det signeres med en hemmelig nøgle, f.eks. via hash_hmac('sha256', ...), og signaturen kontrolleres med hash_equals(). Til mere krævende tilfælde er afprøvede biblioteker at foretrække frem for egne konstruktioner.

Gem, kontrollér og lad tokens udløbe

Et sikkert genereret token er kun den halve vej. Mindst lige så vigtigt er, hvordan applikationen håndterer det. En gennemprøvet fremgangsmåde til nulstillings- og invitationslinks:

  1. Gem kun hashen: I databasen står hash('sha256', $token), selve tokenet går kun via linket til brugeren.
  2. Begræns gyldigheden: Gem udløbstiden med og kontrollér den ved indløsning.
  3. Brug kun én gang: Slet posten eller markér den som brugt efter vellykket brug.
  4. Ugyldiggør ved ændringer: Slet alle åbne nulstillingstokens for kontoen efter en ændring af adgangskoden.
  5. Sammenlign i konstant tid: Hvor et token sammenlignes direkte med en gemt værdi, brug hash_equals() med den gemte værdi som første parameter.
<?php
// Generér
$token = bin2hex(random_bytes(32));
$eintrag = [
    'konto_id'   => $kontoId,
    'token_hash' => hash('sha256', $token),
    'gueltig_bis'=> (new DateTimeImmutable('+30 minutes'))->format('Y-m-d H:i:s'),
];
// send kun $token i linket, gem $eintrag

// Indløs
$gesucht = hash('sha256', (string) ($_GET['token'] ?? ''));
// Søg posten via token_hash med prepared statement,
// kontrollér udløb, slet derefter posten straks

Begræns desuden antallet af nulstillingsforespørgsler pr. konto og tidsrum, så funktionen ikke kan misbruges til masseudsendelse af e-mails. Svaret på en nulstillingsforespørgsel bør desuden ikke afsløre, om den angivne adresse overhovedet har en konto.

En hurtig hash som SHA-256 er nok her, fordi selve tokenet allerede er langt og tilfældigt. For adgangskoder, som mennesker vælger, gælder det ikke; dér kræves langsomme metoder, som Gem adgangskoder sikkert i PHP forklarer.

Tip

Tokens hører ikke hjemme i logfiler. Kontrollér, om jeres webserver logger fuldstændige URL'er med query-parametre, og om fejlrapporter sender forespørgselsdata med.

Efter en hændelse: Forny tokens og nøgler

Den, der kunne udføre kode på en server, havde som regel også adgang til gemte tokens, API-nøgler og sessiondata. Er der efter PHP2Shell eller en anden hændelse mistanke om et indbrud, bør I derfor ikke kun forny adgangskoder, men også langlivede tokens og nøgler: API-adgange, signaturnøgler til cookies eller links, adgange til betalings- og forsendelsestjenester.

Sessioner er et særtilfælde: PHP genererer selv session-ID'er. Hvordan I sikrer deres indstillinger og afslutter sessioner efter en hændelse, beskriver vejledningen Sikr sessioner i PHP. Om formular-tokens mod forfalskede forespørgsler se CSRF-beskyttelse i PHP-applikationer. Yderligere indbyggede værktøjer præsenterer PHP's indbyggede sikkerhedsfunktioner.

Hvordan hjælper PHP SOS konkret?

  • Vi finder i jeres applikation alle steder, hvor tokens, koder og nøgler genereres, og erstatter usikre kilder.
  • Vi kontrollerer lagring, udløb og engangsbrug af nulstillings-, invitations- og delingslinks.
  • Vi sørger for, at tokens ikke havner i logge, fejlrapporter eller analyseværktøjer.
  • Efter en hændelse planlægger vi sammen med jer den ordnede udskiftning af tokens og nøgler.

Kontrol af egen PHP-software tilbyder vi som sikkerhedsaudit og penetrationstest.

Ofte stillede spørgsmål

Hvorfor er mt_rand uegnet til tokens?

mt_rand og rand er tænkt til statistik og spil, ikke til hemmeligheder. Deres output kan under visse omstændigheder forudsiges. Til alt, hvad en angriber ikke må kunne gætte, bruger I random_bytes eller random_int.

Hvor langt bør et token være?

Til links til nulstilling af adgangskoder, invitationer eller API-nøgler er 32 tilfældige bytes et solidt valg, som hex-tekst 64 tegn. 16 bytes, altså 128 bit, er en udbredt nedre grænse.

Er uniqid et sikkert token?

Nej. uniqid bygger på det aktuelle tidspunkt og er tænkt til entydighed, ikke til hemmeligholdelse. Også kombinationer som md5 af tid eller bruger-ID er forudsigelige.

Skal jeg kryptere nulstillingstokens i databasen?

Gem en hash af dem i stedet for selve tokenet, f.eks. med SHA-256. Den, der læser databasen, kan så ikke bygge gyldige links. Fordi selve tokenet allerede er langt og tilfældigt, kræves der ingen langsom metode som ved adgangskoder.

Hvad sker der, hvis systemet ikke finder nogen tilfældighedskilde?

random_bytes og random_int kaster siden PHP 8.2 en Random\RandomException. Opfang dette tilfælde, og afbryd forløbet i stedet for at vige ud til en svagere kilde.

Kilder

Akut kontakt

Hver time tæller. Skriv til os nu.

Beskriv kort system, PHP-version og observation. Vi vender tilbage, afklarer situationen og arbejder på afstand, indtil hændelsen er afsluttet og dokumenteret.

Akut hjælp