# Slaptažodžių saugojimas PHP: teisingas password_hash naudojimas

> Saugokite slaptažodžius PHP su password_hash: bcrypt ar Argon2id, kaštų koeficientas, 72 baitų riba ir perėjimas nuo senų MD5 maišų (hash) be visų naudotojų atstatymo.

Šaltinis: https://phpsos.de/lt/blog/passwoerter-in-php-speichern/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** slaptažodžius PHP saugote su `password_hash()` ir tikrinate su `password_verify()`. Numatytasis `PASSWORD_DEFAULT` šiuo metu naudoja bcrypt, nuo PHP 8.4 su kaštų koeficientu 12; kur PHP sukompiliuotas su Argon2, geras pasirinkimas yra `PASSWORD_ARGON2ID`. Senas MD5 ar SHA-1 maišas (hash) pakeičiate iš karto įdėdami jas į password_hash ir kito prisijungimo metu pakeisdami tiesiogine maiša.

## Ką duomenų bazės nutekėjimas turi bendra su jūsų maišomis (hash)

Po įsilaužimo į žiniatinklio serverį duomenų bazė yra vienas pirmųjų taikinių. Kas, kaip įmanoma su PHP2Shell, gali vykdyti kodą serveryje, perskaito ir programos prisijungimo duomenis, o kartu ir lentelę su naudotojų paskyromis. Ar joje esantys slaptažodžiai taps skaitomi po valandų, ar po metų, nusprendžiama ne atakos dieną, o programuojant.

Stebėtinai daug programų slaptažodžius vis dar saugo metodais, kurie šiam tikslui niekada nebuvo skirti: MD5, SHA-1, paprastu SHA-256 ar net grąžinamai užšifruotus. Šie metodai yra greiti, ir būtent tai yra problema. Užpuolikai su jais per sekundę gali išbandyti labai daug kandidatų.

PHP jau seniai turi sprendimą, kuris skaičiuoja lėtai, kiekvienam slaptažodžiui automatiškai sukuria druską (salt) ir pačioje maišoje (hash) išsaugo metodą bei parametrus. Jums tereikia nuosekliai juo naudotis.

## password_hash PHP: bcrypt ar Argon2id?

`password_hash()` pažįsta kelis metodus. Pasirinkimas pagal PHP vadovą:

| Konstanta | Metodas | Pastaba |
|---|---|---|
| `PASSWORD_DEFAULT` | šiuo metu bcrypt | numatytasis, būsimose versijose gali pasikeisti |
| `PASSWORD_BCRYPT` | bcrypt | kaštų koeficientas nuo PHP 8.4 pagal numatytuosius nustatymus 12, anksčiau 10; skaičiuojami tik pirmi 72 baitai |
| `PASSWORD_ARGON2I` | Argon2i | nuo PHP 7.2, tik jei PHP sukompiliuotas su Argon2 palaikymu |
| `PASSWORD_ARGON2ID` | Argon2id | nuo PHP 7.3, ta pati sąlyga |

OWASP kaip pirmą pasirinkimą rekomenduoja Argon2id, mažiausiai 19 MiB atminties, dviem praėjimais ir viena gija, o bcrypt daugiausia esamoms sistemoms. Daugumai PHP programų praktikoje skirtumas mažesnis nei skirtumas tarp bet kurio iš šių metodų ir MD5. Svarbiau, kad apskritai naudotumėte vieną iš jų ir reguliariai pritaikytumėte parametrus.

Ar Argon2id prieinamas, parodo trumpas bandymas:

```php
<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());
```

## Saugojimas ir tikrinimas: pilnas pavyzdys

Toliau pateiktame pavyzdyje parodyta registracija ir prisijungimas su automatiniu pasenusių maišų (hash) atnaujinimu. Ilgio patikra saugo nuo bcrypt 72 baitų ribos:

```php
<?php
declare(strict_types=1);

const PASSWORT_ALGO = PASSWORD_DEFAULT;

function passwortSpeichern(string $passwort): string
{
    if (strlen($passwort) > 72) {
        throw new InvalidArgumentException('Passwort zu lang für das gewählte Verfahren.');
    }
    return password_hash($passwort, PASSWORT_ALGO);
}

function passwortPruefen(string $eingabe, string $gespeicherterHash, callable $hashAktualisieren): bool
{
    if (!password_verify($eingabe, $gespeicherterHash)) {
        return false;
    }
    if (password_needs_rehash($gespeicherterHash, PASSWORT_ALGO)) {
        $hashAktualisieren(password_hash($eingabe, PASSWORT_ALGO));
    }
    return true;
}
```

`strlen()` skaičiuoja baitus, o ne simbolius. Čia taip ir reikia, nes bcrypt riba taikoma baitams. Duomenų bazės stulpelis pagal vadovą turėtų talpinti daugiau nei 60 simbolių, 255 yra geras pasirinkimas. Niekada nenustatykite savos druskos (salt): ši parinktis pasenusi ir nuo PHP 8.0 ignoruojama.

Pereinant į PHP 8.4, numatytasis bcrypt kaštų koeficientas didėja nuo 10 iki 12. Esamos maišos (hash) dėl `password_needs_rehash()` kito prisijungimo metu pakeliamos automatiškai. Labai apkrautose sistemose prieš perėjimą verta trumpai išbandyti prisijungimo apkrovą.

> **Patarimas:**
> Kaštų koeficientą išmatuokite savo gamybiniame serveryje. Vienas patikrinimas turėtų pastebimai kainuoti skaičiavimo laiko, bet likti gerokai žemiau sekundės, kad prisijungimai esant apkrovai netaptų kliūtimi.

## Senų MD5 ir SHA-1 maišų (hash) pakeitimas

Tikrasis iššūkis dažniausiai yra esami duomenys. Pačių slaptažodžių nežinote, todėl jų tiesiog perskaičiuoti negalite. Laukimas, kol kiekvienas naudotojas vėl prisijungs, palieka neaktyvias paskyras neapsaugotas. Todėl OWASP aprašo dviejų pakopų eigą:

1. **Iš karto įdėti:** kiekvieną esamą MD5 ar SHA-1 reikšmę skriptu apgaubti `password_hash()` ir išsaugoti su žyma, pavyzdžiui, stulpelyje `hash_typ`.
2. **Prisijungiant patikrinti:** pažymėtoms paskyroms įvestį pirmiausia perskaičiuoti senuoju metodu, o tada su `password_verify()` patikrinti pagal apgaubtą reikšmę.
3. **Tiesiogiai pakeisti:** po sėkmingo prisijungimo iš atviro teksto sukurti įprastą maišą (hash) ir pašalinti žymą.
4. **Sutvarkyti likutį:** paskyras, kurios po nustatyto termino vis dar turi apgaubtas maišas (hash), nustatyti slaptažodžio atstatymui.

```php
<?php
// 1 pakopa (vieną kartą skriptu): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// 2 ir 3 pakopos prisijungiant:
if ($konto['hash_typ'] === 'md5-umhuellt') {
    $ok = password_verify(md5($eingabe), $konto['passwort_hash']);
    if ($ok) {
        $speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
    }
}
```

Apgaubtos maišos (hash) geresnės nei plikos MD5 reikšmės, bet silpnesnės nei tiesioginės maišos. Tai pereinamasis sprendimas, o ne tikslas. Po incidento, kai galėjo būti paveikta duomenų bazė, šis perėjimas nepakeičia raginimo naudotojams pakeisti slaptažodžius. Kokie požymiai rodo tokį incidentą, aprašo [Ar jau esu pažeistas?](https://phpsos.de/lt/php2shell/bin-ich-gehackt-anzeichen/).

## Pepper, šifravimas ir kiti klystkeliai

Kai kurios idėjos skamba kaip daugiau saugumo, bet duoda mažai arba kenkia:

- **Savos maišos (hash) konstrukcijos**, pavyzdžiui, kartotinis SHA-256 su pačių sukurta druska (salt), yra greitesnės nei bcrypt, todėl silpnesnės.
- **Užšifruoti slaptažodžiai** yra grąžinami. Jei raktas yra tame pačiame serveryje, šifravimas po įsilaužimo beveik neapsaugo.
- **Pepper**, t. y. slapta papildoma reikšmė visiems slaptažodžiams, gali būti papildomas sluoksnis, jei laikoma atskirai nuo duomenų bazės. Tačiau ji nepakeičia geros maišos (hash) funkcijos ir apsunkina vėlesnius pakeitimus.
- **Išankstinis maišymas prieš bcrypt**, siekiant apeiti 72 baitų ribą, pasak OWASP, turi savų rizikų. Aiški ilgio riba arba Argon2id paprastesni.

Ir naujų slaptažodžių taisyklėms verta pažvelgti į technikos lygį: ilgis saugo labiau nei sudėtingi reikalavimai specialiems simboliams, o labai dažni ar jau atsiradę duomenų nutekėjimuose slaptažodžiai turėtų būti atmetami. Priverstiniai keitimai nustatytais intervalais be priežasties, patirtis rodo, veikiau veda prie nuspėjamų šablonų, o ne prie stipresnių slaptažodžių.

Prisijungimo sistemose slaptažodžiai niekada nestovi vieni: dviejų veiksnių metodai administratoriams ir stabdis masiniams prisijungimo bandymams papildo saugojimą. Abu aptaria vadovas [Administratoriaus prisijungimų apsauga nuo brute force](https://phpsos.de/lt/ratgeber/brute-force-login-schutz/). Atstatymo nuorodoms ir vienkartiniams kodams galioja kitos taisyklės, žr. [Saugūs atsitiktiniai skaičiai ir žetonai PHP](https://phpsos.de/lt/blog/sichere-tokens-php/). Apžvalgą apie kitas funkcijas pateikia [Įtaisytosios PHP saugumo funkcijos](https://phpsos.de/lt/php/eingebaute-sicherheitsfunktionen/).

## Kaip PHP SOS padeda konkrečiai?

- Patikriname, kaip jūsų programa ir papildiniai saugo slaptažodžius, ir randame pasenusius metodus net šalutinėse funkcijose.
- Suplanuojame esamų duomenų perėjimą su apgaubimu, prisijungimo migracija ir terminu likusioms paskyroms.
- Kaštų koeficientą ar Argon2 parametrus suderiname su jūsų serveriais.
- Po incidento padedame priimti sprendimą dėl slaptažodžių atstatymo ir jo techninio įgyvendinimo.

Savos PHP programinės įrangos patikrą siūlome kaip [saugumo auditą ir įsiskverbimo testą](https://phpsos.de/lt/leistungen/sicherheitsaudit-penetrationstest/).


## Dažni klausimai

**Kokį algoritmą naudoja password_hash su PASSWORD_DEFAULT?**  
Šiuo metu bcrypt. PHP vadovas pažymi, kad numatytasis gali pasikeisti, kai atsiras stipresnių metodų. Todėl duomenų bazės stulpelis turėtų talpinti daugiau nei 60 simbolių; vadovas kaip gerą pasirinkimą nurodo 255.

**Ar slaptažodžius turėčiau šifruoti, o ne maišyti (hash)?**  
Ne. Užšifruotus slaptažodžius su raktu galima vėl perskaityti, o šis raktas dažniausiai yra tame pačiame serveryje. Slaptažodžio maiša (hash) yra vienos krypties gatvė: programa gali tik patikrinti, ar įvestis tinka.

**Koks turėtų būti bcrypt kaštų koeficientas?**  
Nuo PHP 8.4 numatytasis yra 12, anksčiau 10. OWASP kaip apatinę ribą nurodo 10 ir rekomenduoja eiti tiek aukštai, kiek leidžia jūsų serveriai. Savo aparatinėje įrangoje išmatuokite, kiek trunka vienas patikrinimas.

**Ką reiškia 72 baitų riba bcrypt?**  
bcrypt atsižvelgia tik į pirmus 72 slaptažodžio baitus, viskas po to nukerpama. Su lietuviškomis raidėmis vienas simbolis dažnai skaičiuojamas kaip du baitai. Todėl aiškiai apribokite ilgį arba naudokite Argon2id, jei jūsų PHP su juo sukompiliuotas.

**Ar po įsilaužimo naudotojai turi keisti slaptažodžius?**  
Jei negalima atmesti, kad duomenų bazė nutekėjo, tai turėtumėte inicijuoti. Geros maišos (hash) smarkiai sulėtina laužimą, bet silpni slaptažodžiai lieka pažeidžiami. Ar papildomai yra pranešimo pareiga, išsiaiškinkite su savo duomenų apsaugos pareigūnu.

## Šaltiniai

- [PHP vadovas: password_hash](https://www.php.net/manual/de/function.password-hash.php)
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
