Текуща ситуация: PHP2Shell. Две критични уязвимости в PHP (CVSS 9,2) позволяват злонамерен код без вход. Корекции от 21.10.2026 г.Какво да направите сега →

Сигурно съхранение на пароли в PHP: правилно използване на password_hash

Как разработчиците съхраняват пароли в PHP с password_hash и password_verify, избират подходящия метод и заменят стари MD5 или SHA-1 хешове, без да принуждават всички потребители да сменят паролата си.

Актуалност 6 мин. четенеСигурна разработка

Накратко: Паролите в PHP съхранявате с password_hash() и ги проверявате с password_verify(). Стандартът PASSWORD_DEFAULT в момента използва bcrypt, от PHP 8.4 с фактор на разход 12; където PHP е компилиран с Argon2, PASSWORD_ARGON2ID е добра алтернатива. Старите MD5 или SHA-1 хешове преминавате, като веднага ги вградите в password_hash и при следващия вход ги замените с директен хеш.

Какво общо има изтичането на базата данни с Вашите хешове

След пробив в уеб сървър базата данни е една от първите цели. Който, както е възможно при PHP2Shell, може да изпълнява код на сървъра, чете и данните за достъп на приложението, а с това и таблицата с потребителските акаунти. Дали паролите в нея ще станат четими след часове или след години, се решава не в деня на атаката, а при програмирането.

Учудващо много приложения още съхраняват пароли с методи, които никога не са били предвидени за тази цел: MD5, SHA-1, обикновен SHA-256 или дори обратимо шифрирани. Тези методи са бързи и точно това е проблемът. Атакуващите могат с тях да изпробват огромен брой кандидати в секунда.

PHP отдавна носи решение, което смята бавно, автоматично генерира salt за всяка парола и съхранява метода и параметрите в самия хеш. Трябва само да го използвате последователно.

password_hash в PHP: bcrypt или Argon2id?

password_hash() познава няколко метода. Изборът според наръчника на PHP:

Константа Метод Забележка
PASSWORD_DEFAULT в момента bcrypt Стандарт, може да се промени в бъдещи версии
PASSWORD_BCRYPT bcrypt Факторът на разход от PHP 8.4 е по подразбиране 12, преди това 10; броят само първите 72 байта
PASSWORD_ARGON2I Argon2i От PHP 7.2, само ако PHP е изграден с поддръжка на Argon2
PASSWORD_ARGON2ID Argon2id От PHP 7.3, същото условие

OWASP препоръчва Argon2id като първи избор, с най-малко 19 MiB памет, два прохода и една нишка, а bcrypt най-вече за съществуващи системи. За повечето PHP приложения разликата на практика е по-малка от разликата между който и да е от тези методи и MD5. По-важно е изобщо да използвате един от тях и да коригирате редовно параметрите.

Дали Argon2id е наличен, показва кратък тест:

<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());

Съхраняване и проверка: цялостен образец

Следващият пример показва регистрация и вход с автоматично обновяване на остарели хешове. Проверката на дължината предпазва от ограничението от 72 байта на bcrypt:

<?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() брои байтове, а не знаци. Това тук е желано, защото ограничението на bcrypt се отнася до байтове. Колоната в базата данни според наръчника трябва да побира повече от 60 знака, 255 е добър избор. Никога не задавайте собствен salt: опцията е остаряла и от PHP 8.0 се игнорира.

С преминаването към PHP 8.4 стандартният фактор на разход за bcrypt нараства от 10 на 12. Съществуващите хешове благодарение на password_needs_rehash() се повишават автоматично при следващия вход. На силно натоварени системи преди преминаването си струва кратък тест на натоварване на входа.

Съвет

Измерете фактора на разход на Вашия продуктивен сървър. Една проверка трябва да отнема осезаемо изчислително време, но да остава ясно под една секунда, за да не станат входовете пречка при натоварване.

Заместване на стари MD5 и SHA-1 хешове

Същинското предизвикателство обикновено е съществуващият набор. Самите пароли не ги знаете, така че не можете просто да ги хеширате наново. Изчакването всеки потребител да влезе отново оставя неактивните акаунти незащитени. Затова OWASP описва двустепенен подход:

  1. Вградете веднага: обгърнете всяка налична MD5 или SHA-1 стойност със скрипт с password_hash() и я запишете с маркировка, например в колона hash_typ.
  2. Проверявайте при вход: за маркираните акаунти първо преизчислете въведеното със стария метод и после го проверете с password_verify() спрямо обгърнатата стойност.
  3. Заменете директно: след успешен вход създайте обикновен хеш от явния текст и премахнете маркировката.
  4. Почистете остатъка: акаунтите, които след определен срок още имат обгърнати хешове, поставете на нулиране на паролата.
<?php
// Стъпка 1 (еднократно със скрипт): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// Стъпки 2 и 3 при вход:
if ($konto['hash_typ'] === 'md5-umhuellt') {
    $ok = password_verify(md5($eingabe), $konto['passwort_hash']);
    if ($ok) {
        $speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
    }
}

Обгърнатите хешове са по-добри от голите MD5 стойности, но по-слаби от директните хешове. Те са преходно решение, а не цел. След инцидент, при който може да е засегната базата данни, това преминаване не замества искането към потребителите да сменят паролите си. Кои следи сочат към такъв инцидент, описва Вече компрометирани ли сме?.

Pepper, шифриране и други заблуди

Някои идеи звучат като повече сигурност, но носят малко или вредят:

  • Собствени конструкции за хеширане, като многократен SHA-256 със самостоятелно генериран salt, са по-бързи от bcrypt и затова по-слаби.
  • Шифрираните пароли са обратими. Ако ключът е на същия сървър, шифроването след пробив почти не защитава.
  • Pepper, тоест тайна допълнителна стойност за всички пароли, може да е допълнителен слой, ако се съхранява отделно от базата данни. Той обаче не замества добрия метод за хеширане и затруднява по-късни смени.
  • Предварително хеширане преди bcrypt, за да се заобиколи ограничението от 72 байта, носи според OWASP собствени рискове. Ясно ограничение на дължината или Argon2id са по-прости.

И при правилата за новите пароли си струва да се погледне актуалното състояние на техниката: дължината защитава повече от сложни изисквания за специални знаци, а много често срещани пароли или такива, появявали се вече в изтичания на данни, трябва да се отхвърлят. Принудителните смени през фиксирани интервали без повод по опит водят по-скоро до предвидими шаблони, отколкото до по-силни пароли.

Освен това за системите за вход паролите никога не стоят сами: двуфакторните методи за администраторите и спирачка срещу масови опити за вход допълват съхранението. И двете разглежда наръчникът Защита на админ входовете срещу Brute Force. За връзки за нулиране и еднократни кодове важат други правила, вижте Сигурни случайни числа и токени в PHP. Преглед на други функции дава Вградените функции за сигурност на PHP.

Как помага PHP SOS конкретно?

  • Проверяваме как Вашето приложение и добавките Ви съхраняват пароли и откриваме остарели методи и в странични функции.
  • Планираме преминаването на съществуващия набор с обгръщане, миграция при вход и срок за останалите акаунти.
  • Съобразяваме фактора на разход или параметрите на Argon2 с Вашите сървъри.
  • След инцидент подпомагаме решението за нулиране на пароли и техническото му изпълнение.

Проверка на собствен PHP софтуер предлагаме като одит на сигурността и тест за проникване.

Често задавани въпроси

Какъв алгоритъм използва password_hash с PASSWORD_DEFAULT?

В момента bcrypt. Наръчникът на PHP посочва, че стандартът може да се промени, когато се добавят по-силни методи. Затова колоната в базата данни трябва да побира повече от 60 знака; наръчникът препоръчва 255 като добър избор.

Трябва ли да шифрирам паролите вместо да ги хешвам?

Не. Шифрираните пароли могат да се прочетат отново с ключа, а този ключ обикновено се намира на същия сървър. Хешът на парола е еднопосочна улица: приложението може само да провери дали въведеното съвпада.

Колко висок трябва да е факторът на разход при bcrypt?

От PHP 8.4 стандартът е 12, преди това беше 10. OWASP посочва 10 като долна граница и препоръчва да се стигне толкова високо, колкото издържат Вашите сървъри. Измерете на собствения си хардуер колко време отнема една проверка.

Какво означава ограничението от 72 байта при bcrypt?

bcrypt взема предвид само първите 72 байта на паролата, всичко след това се отрязва. При букви с диакритични знаци или кирилица един знак често заема два байта. Затова ограничете дължината изрично или използвайте Argon2id, ако Вашият PHP е компилиран с него.

Трябва ли потребителите да сменят паролите си след пробив?

Ако не може да се изключи, че базата данни е изтекла, трябва да го разпоредите. Добрите хешове значително забавят разбиването, но слабите пароли остават уязвими. Дали допълнително има задължение за уведомяване, уточнете със своето длъжностно лице по защита на данните.

Източници

Незабавен контакт

Всеки час има значение. Пишете ни сега.

Опишете накратко системата, версията на PHP и какво сте забелязали. Ние се свързваме с Вас, уточняваме ситуацията и работим отдалечено, докато инцидентът бъде приключен и документиран.

Незабавна помощ