رمز عبور یکی از مهمترین اطلاعات حساس در هر سیستم کاربری است. تقریباً تمام وبسایتهایی که قابلیت ثبتنام، ورود، پنل کاربری یا مدیریت حساب دارند، به نوعی با رمز عبور سروکار دارند.
اگر رمز عبور کاربران به شکل نادرست ذخیره شود، در صورت افشای دیتابیس، مهاجم ممکن است بتواند به حساب کاربران دسترسی پیدا کند.
یکی از اشتباهات رایج در پروژههای PHP این است که رمز عبور را به صورت متن ساده یا با الگوریتمهای Hash نامناسب ذخیره کنیم.
در PHP برای مدیریت امن رمز عبور، توابع داخلی مناسبی وجود دارد:
password_hash()
و:
password_verify()
در این مقاله یاد میگیریم چگونه رمز عبور را در PHP به شکل امن Hash کنیم، هنگام ورود آن را بررسی کنیم و در برابر حملاتی مانند Brute Force، Password Spraying و افشای دیتابیس دفاع بهتری داشته باشیم.
# چرا نباید رمز عبور را به صورت متن ساده ذخیره کنیم؟
فرض کنید جدول کاربران ما چنین ساختاری داشته باشد:
users
--------------------------------
id
username
password
و مقدار ستون password این باشد:
123456
این روش بسیار خطرناک است.
اگر دیتابیس افشا شود، مهاجم بدون هیچ مرحله اضافی میتواند رمز عبور کاربران را ببیند.
نمونه نامناسب:
$password = $_POST['password'];
$sql = "INSERT INTO users (password)
VALUES ('$password')";
در این روش رمز عبور مستقیماً ذخیره میشود.
حتی اگر دیتابیس فقط برای مدیران قابل دسترسی باشد، باز هم نباید رمز عبور را به صورت متن ساده ذخیره کنیم؛ زیرا ممکن است:
- Backup دیتابیس افشا شود.
- Logها شامل اطلاعات حساس باشند.
- دسترسی یکی از مدیران به خطر بیفتد.
- فایل Export دیتابیس در اختیار فرد دیگری قرار بگیرد.
- مهاجم از طریق آسیبپذیری دیگری به دیتابیس برسد.
اصل مهم این است:
> برنامه نباید به رمز عبور اصلی کاربر نیاز داشته باشد؛ فقط باید بتواند صحت آن را بررسی کند.
# تفاوت Encryption و Hash چیست؟
برای درک امنیت رمز عبور باید تفاوت Encryption و Hash را بدانیم.
Encryption
Encryption یا رمزنگاری معمولاً قابل بازگشت است.
یعنی اگر کلید مناسب را داشته باشیم، میتوانیم داده رمزنگاریشده را به مقدار اصلی برگردانیم.
مثلاً:
Plain Text
↓
Encryption
↓
Encrypted Data
↓
Decryption
↓
Plain Text
رمزنگاری برای دادههایی مناسب است که برنامه بعداً باید آنها را بازیابی کند.
Hash
Hash معمولاً یکطرفه است.
یعنی رمز عبور به یک مقدار Hash تبدیل میشود، اما برنامه نباید بتواند از روی Hash رمز اصلی را بازیابی کند.
Password
↓
Hash Function
↓
Password Hash
برای رمز عبور معمولاً از الگوریتمهای مخصوص Password Hashing استفاده میشود.
این الگوریتمها عمداً طوری طراحی شدهاند که محاسبه آنها نسبت به Hashهای عمومی مانند MD5 یا SHA-256 پرهزینهتر باشد.
# چرا MD5 و SHA-1 برای رمز عبور مناسب نیستند؟
الگوریتمهایی مانند:
MD5
SHA-1
SHA-256
برای کاربردهای مختلف مفید هستند، اما استفاده مستقیم از آنها برای ذخیره رمز عبور مناسب نیست.
نمونه نامناسب:
$passwordHash = md5($password);
یا:
$passwordHash = sha1($password);
مشکل اصلی این است که این الگوریتمها برای محاسبه سریع طراحی شدهاند.
مهاجم میتواند تعداد بسیار زیادی رمز عبور احتمالی را در مدت کوتاه آزمایش کند.
همچنین استفاده از Hash ساده معمولاً به اندازه کافی در برابر حملات Dictionary و Brute Force مقاوم نیست.
# Salt چیست؟
Salt یک مقدار تصادفی است که هنگام Hash کردن رمز عبور به کار میرود.
هدف Salt این است که حتی اگر دو کاربر رمز عبور یکسانی داشته باشند، Hash آنها یکسان نباشد.
مثلاً:
User A Password: 123456
User B Password: 123456
اگر از Hash ساده استفاده کنیم، ممکن است خروجی هر دو یکسان باشد.
اما با Salt:
Password + Random Salt
↓
Password Hash
Hash نهایی متفاوت میشود.
در PHP، توابع جدید Password Hashing به شکل داخلی Salt مناسب را مدیریت میکنند و معمولاً نیازی نیست خودمان Salt را دستی تولید یا در ستون جداگانه ذخیره کنیم.
# تابع password_hash() چیست؟
تابع:
password_hash()
رمز عبور را با یک الگوریتم مناسب Hash میکند.
مثال:
<?php
$password = 'MyStrongPassword123';
$hash = password_hash(
$password,
PASSWORD_DEFAULT
);
echo $hash;
خروجی چیزی شبیه این خواهد بود:
$2y$...
مقدار دقیق خروجی در هر بار اجرا میتواند متفاوت باشد، حتی اگر رمز عبور یکسان باشد؛ زیرا Salt تصادفی در فرآیند Hash استفاده میشود.
# چرا Hash یک رمز عبور در هر بار متفاوت است؟
مثلاً:
$hash1 = password_hash('123456', PASSWORD_DEFAULT);
$hash2 = password_hash('123456', PASSWORD_DEFAULT);
var_dump($hash1 === $hash2);
خروجی معمولاً:
false
است.
این موضوع طبیعی و مطلوب است.
دو Hash متفاوت میتوانند هر دو مربوط به یک رمز عبور باشند.
تابع password_verify() اطلاعات لازم برای بررسی را از خود Hash ذخیرهشده استخراج میکند.
# الگوریتم PASSWORD_DEFAULT
برای بیشتر پروژههای PHP، استفاده از:
PASSWORD_DEFAULT
انتخاب مناسبی است.
مزیت آن این است که برنامه به جای وابستگی مستقیم به یک الگوریتم مشخص، از الگوریتم پیشفرض مناسب نسخه PHP استفاده میکند.
مثال:
$hash = password_hash(
$password,
PASSWORD_DEFAULT
);
این روش باعث میشود در صورت تغییر الگوریتم پیشفرض در نسخههای آینده PHP، امکان ارتقای Hashها سادهتر باشد.
# الگوریتم Bcrypt در PHP
در بسیاری از نسخههای PHP، PASSWORD_DEFAULT از Bcrypt استفاده میکند.
میتوان Bcrypt را نیز به صورت مستقیم مشخص کرد:
$hash = password_hash(
$password,
PASSWORD_BCRYPT
);
مثال کامل:
<?php
$password = 'MyStrongPassword123';
$hash = password_hash(
$password,
PASSWORD_BCRYPT
);
echo $hash;
Bcrypt برای Hash کردن رمز عبور طراحی شده و از الگوریتمهای سریع عمومی مانند MD5 مناسبتر است.
# الگوریتم Argon2 در PHP
در نسخهها و محیطهایی که Argon2 فعال باشد، میتوان از الگوریتمهای Argon2 نیز استفاده کرد.
مثلاً:
$hash = password_hash(
$password,
PASSWORD_ARGON2ID
);
Argon2id برای مقاومت بهتر در برابر برخی حملات مبتنی بر سختافزار و حافظه طراحی شده است.
با این حال، قبل از استفاده باید بررسی کنیم که نسخه PHP و محیط سرور از آن پشتیبانی میکنند.
برای بررسی الگوریتمهای موجود میتوانیم از:
print_r(password_algos());
استفاده کنیم.
# password_verify() چیست؟
تابع:
password_verify()
رمز عبوری که کاربر هنگام Login وارد کرده است با Hash ذخیرهشده در دیتابیس مقایسه میکند.
مثال:
<?php
$password = 'MyStrongPassword123';
$hash = password_hash(
$password,
PASSWORD_DEFAULT
);
if (password_verify($password, $hash)) {
echo 'رمز عبور صحیح است.';
} else {
echo 'رمز عبور اشتباه است.';
}
این تابع نباید با مقایسه ساده رشتهای جایگزین شود.
روش نامناسب:
if ($password === $hash) {
// Login
}
زیرا مقدار ورودی کاربر رمز عبور خام است، اما مقدار دیتابیس Hash شده است.
روش صحیح:
if (password_verify($password, $hash)) {
// Login
}
# نمونه ثبتنام کاربر در PHP
فرض کنیم کاربر یک فرم ثبتنام دارد:
<form method="POST" action="register.php">
<input type="text" name="username" required>
<input type="password" name="password" required>
<button type="submit">
ثبتنام
</button>
</form>
در سمت PHP باید رمز عبور را قبل از ذخیرهسازی Hash کنیم:
<?php
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username === '' || $password === '') {
exit('نام کاربری و رمز عبور الزامی است.');
}
$passwordHash = password_hash(
$password,
PASSWORD_DEFAULT
);
سپس مقدار $passwordHash را در دیتابیس ذخیره میکنیم، نه مقدار اصلی $password.
# نمونه ذخیره رمز عبور با PDO
در این مثال از Prepared Statement استفاده میکنیم:
<?php
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username === '' || $password === '') {
exit('اطلاعات ثبتنام کامل نیست.');
}
$passwordHash = password_hash(
$password,
PASSWORD_DEFAULT
);
$pdo = new PDO(
'mysql:host=localhost;dbname=test;charset=utf8mb4',
'root',
''
);
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION
);
$sql = "
INSERT INTO users (username, password)
VALUES (:username, :password)
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'username' => $username,
'password' => $passwordHash,
]);
echo 'ثبتنام با موفقیت انجام شد.';
در این مثال:
- رمز عبور اصلی ذخیره نمیشود.
- Hash رمز عبور در دیتابیس قرار میگیرد.
- از Prepared Statement استفاده شده است.
- دادههای ورودی مستقیماً داخل SQL قرار نگرفتهاند.
# ساختار مناسب ستون رمز عبور در دیتابیس
برای Hashهای امروزی بهتر است ستون رمز عبور طول کافی داشته باشد.
برای مثال در MySQL:
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);
استفاده از:
VARCHAR(255)
برای ذخیره Hash رمز عبور انتخاب مناسبی است، زیرا طول Hash ممکن است با تغییر الگوریتم افزایش پیدا کند.
نباید ستون را بیش از حد کوتاه تعریف کنیم؛ مثلاً:
password VARCHAR(50)
ممکن است در آینده برای بعضی الگوریتمها کافی نباشد.
# نمونه Login با password_verify()
فرض کنیم کاربر نام کاربری و رمز عبور خود را وارد کرده است.
ابتدا کاربر را از دیتابیس پیدا میکنیم:
<?php
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$stmt = $pdo->prepare(
'SELECT id, username, password FROM users WHERE username = :username LIMIT 1'
);
$stmt->execute([
'username' => $username,
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
سپس Hash را بررسی میکنیم:
if (!$user || !password_verify($password, $user['password'])) {
exit('نام کاربری یا رمز عبور اشتباه است.');
}
اگر رمز صحیح باشد، باید Session امن ایجاد کنیم:
session_start();
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['authenticated'] = true;
header('Location: /dashboard.php');
exit;
تغییر Session ID بعد از Login برای کاهش ریسک Session Fixation اهمیت دارد.
# چرا پیام خطای Login باید عمومی باشد؟
روش نامناسب:
if (!$user) {
exit('این نام کاربری وجود ندارد.');
}
if (!password_verify($password, $user['password'])) {
exit('رمز عبور اشتباه است.');
}
این پیامها به مهاجم کمک میکنند بفهمد یک نام کاربری در سیستم وجود دارد یا خیر.
بهتر است از پیام عمومی استفاده کنیم:
exit('نام کاربری یا رمز عبور اشتباه است.');
این کار به کاهش User Enumeration کمک میکند.
# بررسی نیاز به Rehash با password_needs_rehash()
ممکن است برنامه شما در گذشته از الگوریتم یا تنظیمات قدیمیتری استفاده کرده باشد.
PHP تابعی دارد به نام:
password_needs_rehash()
که بررسی میکند آیا Hash ذخیرهشده باید با تنظیمات جدید دوباره تولید شود یا خیر.
مثال:
if (
password_verify($password, $user['password']) &&
password_needs_rehash(
$user['password'],
PASSWORD_DEFAULT
)
) {
$newHash = password_hash(
$password,
PASSWORD_DEFAULT
);
// ذخیره $newHash در دیتابیس
}
این فرآیند معمولاً بعد از ورود موفق کاربر انجام میشود.
مزیت آن این است که لازم نیست همه رمزهای عبور کاربران را یکباره تغییر دهیم.
# چرا نباید همه رمزهای عبور را یکباره Rehash کنیم؟
برنامه به رمز عبور اصلی کاربران دسترسی ندارد.
بنابراین نمیتوانیم همه Hashها را بدون داشتن رمز اصلی دوباره تولید کنیم.
راهکار معمول:
Login موفق
↓
Verify Password
↓
Check Rehash
↓
Generate New Hash
↓
Update Database
به این ترتیب هر کاربر هنگام ورود موفق، در صورت نیاز Hash جدید دریافت میکند.
# محدودیت طول رمز عبور
نباید فقط رمز عبور کوتاه را رد کنیم؛ بهتر است برای رمزهای عبور طولانی نیز محدودیت منطقی داشته باشیم.
برای مثال:
if (strlen($password) < 12) {
exit('رمز عبور باید حداقل 12 کاراکتر باشد.');
}
اما نباید بدون دلیل محدودیت بسیار کوچک برای طول رمز عبور تعیین کنیم.
مثلاً محدودیت:
حداکثر 16 کاراکتر
میتواند استفاده از Password Manager و عبارتهای عبور طولانی را دشوار کند.
همچنین اگر از Bcrypt استفاده میکنید، باید محدودیت طول آن را در طراحی سیستم در نظر بگیرید. برای رمزهای عبور بسیار طولانی، سیاست مناسب طول و الگوریتم باید از ابتدا مشخص شود.
# آیا باید از Regular Expression برای رمز عبور استفاده کنیم؟
میتوان برخی قوانین حداقلی تعریف کرد، اما استفاده افراطی از Regular Expression همیشه مفید نیست.
مثلاً:
if (!preg_match('/[A-Z]/', $password)) {
exit('رمز عبور باید حداقل یک حرف بزرگ داشته باشد.');
}
یا:
if (!preg_match('/[0-9]/', $password)) {
exit('رمز عبور باید حداقل یک عدد داشته باشد.');
}
با این حال، طول مناسب، غیرقابل حدس بودن و استفاده از Password Manager معمولاً اهمیت زیادی دارند.
همچنین بهتر است کاربران را مجبور نکنیم رمزهایی بسازند که به دلیل قوانین پیچیده، قابل پیشبینیتر شوند.
# جلوگیری از رمزهای عبور بسیار رایج
رمزهایی مانند:
123456
password
123456789
qwerty
111111
بسیار قابل حدس هستند.
میتوان هنگام ثبتنام یا تغییر رمز عبور، فهرستی از رمزهای رایج را بررسی کرد.
مثلاً:
$weakPasswords = [
'123456',
'password',
'123456789',
'qwerty',
];
if (in_array(strtolower($password), $weakPasswords, true)) {
exit('این رمز عبور بسیار ضعیف است.');
}
در پروژههای بزرگ بهتر است از فهرست جامعتر و بهروزشونده استفاده شود.
# Brute Force چیست؟
Brute Force حملهای است که در آن مهاجم تعداد زیادی رمز عبور احتمالی را امتحان میکند.
مثلاً:
123456
123457
123458
...
اگر سیستم Login هیچ محدودیتی نداشته باشد، مهاجم میتواند تعداد زیادی درخواست ارسال کند.
برای کاهش این ریسک باید از روشهایی مانند:
- Rate Limiting
- تأخیر کنترلشده
- قفل موقت
- CAPTCHA در شرایط خاص
- احراز هویت دومرحلهای
- ثبت و بررسی تلاشهای مشکوک
استفاده کنیم.
# Rate Limiting برای Login
در یک نمونه ساده میتوان تعداد تلاشهای ناموفق را ثبت کرد.
مثلاً:
user_id
ip_address
attempt_count
last_attempt_at
سپس اگر تعداد تلاشها از حد مشخصی بیشتر شد، درخواستهای بعدی را برای مدت کوتاهی محدود کنیم.
نمونه مفهومی:
if ($failedAttempts >= 5) {
exit('تعداد تلاشها بیش از حد مجاز است. کمی بعد دوباره تلاش کنید.');
}
در پروژههای واقعی بهتر است Rate Limiting بر اساس ترکیبی از موارد زیر طراحی شود:
- IP
- شناسه کاربر
- نام کاربری
- Device
- زمان
- الگوی درخواست
قفل کردن دائمی حساب بر اساس IP یا نام کاربری میتواند باعث سوءاستفاده و ایجاد مشکل برای کاربران واقعی شود.
# Password Spraying چیست؟
در Password Spraying مهاجم یک رمز عبور رایج را روی تعداد زیادی حساب امتحان میکند.
مثلاً:
password123
روی چندین نام کاربری مختلف.
برای کاهش این خطر، فقط محدود کردن تعداد تلاش برای هر حساب کافی نیست. باید الگوهای گستردهتر Login نیز بررسی شوند.
# Credential Stuffing چیست؟
Credential Stuffing زمانی رخ میدهد که مهاجم از نام کاربری و رمز عبوری استفاده کند که قبلاً در یک سایت دیگر افشا شدهاند.
اگر کاربر در چند سایت از یک رمز عبور استفاده کرده باشد، افشای اطلاعات یک سایت میتواند حسابهای دیگر او را نیز در معرض خطر قرار دهد.
راهکارهای مهم:
- استفاده از رمز عبور منحصربهفرد
- Password Manager
- احراز هویت دومرحلهای
- بررسی رمزهای افشاشده
- محدودسازی تلاشهای ورود
- اطلاعرسانی Login مشکوک
# احراز هویت دومرحلهای
حتی اگر رمز عبور کاربر افشا شود، احراز هویت دومرحلهای میتواند یک لایه امنیتی اضافه ایجاد کند.
روشهای رایج:
Password
+
Authenticator App
یا:
Password
+
Security Key
یا در بعضی سیستمها:
Password
+
One-Time Code
احراز هویت دومرحلهای جایگزین Password Hashing نیست، بلکه مکمل آن است.
# رمز عبور را در Log ذخیره نکنید
نباید رمز عبور را در موارد زیر ذخیره کنیم:
error_log($password);
یا:
file_put_contents(
'debug.log',
$password
);
همچنین نباید رمز عبور را در:
- Query String
- URL
- HTML
- JavaScript
- پیام خطا
- گزارش Debug
- ایمیل معمولی
قرار دهیم.
# رمز عبور را در ایمیل ارسال نکنید
روش نامناسب:
رمز عبور شما:
123456
بهتر است اگر کاربر رمز عبور خود را فراموش کرده است، از فرآیند Reset Password استفاده شود.
در این فرآیند:
- یک Token تصادفی تولید میشود.
- Token مدت اعتبار محدودی دارد.
- Token یکبارمصرف است.
- لینک Reset برای کاربر ارسال میشود.
- بعد از تغییر رمز، Token منقضی میشود.
نباید رمز عبور قبلی کاربر را از دیتابیس بازیابی کنیم، زیرا رمز عبور باید به شکل Hash ذخیره شده باشد.
# ذخیره Token بازیابی رمز عبور
برای Reset Password بهتر است Token خام را مستقیماً در دیتابیس ذخیره نکنیم.
میتوان Token را تولید کرد:
$token = bin2hex(random_bytes(32));
سپس Hash آن را ذخیره کرد:
$tokenHash = hash('sha256', $token);
کاربر Token خام را در لینک دریافت میکند، اما دیتابیس فقط Hash آن را نگه میدارد.
در زمان بررسی:
$providedHash = hash('sha256', $providedToken);
if (!hash_equals($storedHash, $providedHash)) {
exit('Token نامعتبر است.');
}
همچنین Token باید:
- تاریخ انقضا داشته باشد.
- یکبارمصرف باشد.
- بعد از استفاده حذف یا بیاعتبار شود.
- به کاربر مناسب وابسته باشد.
# تفاوت Hash رمز عبور و Hash Token
برای رمز عبور معمولاً از:
password_hash()
استفاده میکنیم.
برای Token تصادفی Reset Password میتوان از Hash سریع مانند SHA-256 استفاده کرد، زیرا Token از ابتدا تصادفی و طولانی تولید شده است.
بنابراین این دو مورد کاربرد یکسانی ندارند:
Password
→ password_hash()
Random Token
→ hash('sha256', $token)
نباید این دو مفهوم را با هم اشتباه بگیریم.
# آیا باید از md5() برای Token استفاده کنیم؟
برای Tokenهای حساس بهتر است از روشهای استانداردتر مانند SHA-256 استفاده شود:
$tokenHash = hash('sha256', $token);
اما نکته مهمتر، تصادفی و غیرقابل حدس بودن Token است:
$token = bin2hex(random_bytes(32));
نباید Token را بر اساس موارد قابل حدس مانند:
time()
یا:
rand()
تولید کنیم.
# استفاده از random_bytes() برای تولید مقدار تصادفی
برای تولید Token یا مقدار تصادفی امنیتی از:
random_bytes()
استفاده کنید.
مثال:
$randomValue = random_bytes(32);
برای تبدیل آن به رشته قابل استفاده در URL:
$token = bin2hex(random_bytes(32));
این روش برای تولید Tokenهای امنیتی مناسبتر از توابع تصادفی عمومی است.
# آیا Salt را باید دستی ذخیره کنیم؟
در استفاده معمول از:
password_hash()
نیازی نیست Salt را جداگانه تولید و ذخیره کنیم.
اطلاعات لازم برای بررسی Hash در خود خروجی تابع قرار میگیرد.
بنابراین کافی است مقدار خروجی را ذخیره کنیم:
$hash = password_hash(
$password,
PASSWORD_DEFAULT
);
و هنگام Login:
password_verify($password, $hash);
را اجرا کنیم.
# آیا باید Hash رمز عبور را Encrypt کنیم؟
در بیشتر سیستمها نیازی نیست Hash رمز عبور را دوباره Encrypt کنیم.
Hash رمز عبور باید به شکل امن در دیتابیس ذخیره شود و دسترسی به دیتابیس نیز محدود باشد.
Encrypt کردن Hash ممکن است در بعضی معماریهای خاص کاربرد داشته باشد، اما معمولاً مهمتر است که:
- دسترسی دیتابیس محدود شود.
- Backupها محافظت شوند.
- رمز عبور خام ذخیره نشود.
- Logها پاکسازی شوند.
- Hash با الگوریتم مناسب تولید شود.
# امنیت رمز عبور در Laravel
در Laravel معمولاً میتوان از سیستم Hash داخلی استفاده کرد.
مثلاً:
use Illuminate\Support\Facades\Hash;
$hash = Hash::make($password);
برای بررسی:
if (Hash::check($password, $user->password)) {
// Login successful
}
Laravel بسیاری از جزئیات Hashing را مدیریت میکند، اما توسعهدهنده همچنان باید سیاستهای زیر را رعایت کند:
- محدودیت تلاش Login
- Session Regeneration
- احراز هویت دومرحلهای
- Reset Password امن
- عدم نمایش رمز عبور در Log
- کنترل دسترسی
- محافظت از Session
# چکلیست امنیت رمز عبور در PHP
قبل از انتشار سیستم ثبتنام و Login، این موارد را بررسی کنید:
- [ ] رمز عبور به صورت متن ساده ذخیره نمیشود.
- [ ] از
password_hash()استفاده شده است. - [ ] برای بررسی رمز از
password_verify()استفاده میشود. - [ ] از MD5 و SHA-1 برای Hash مستقیم رمز عبور استفاده نشده است.
- [ ] ستون رمز عبور طول کافی دارد.
- [ ] رمز عبور در Log ذخیره نمیشود.
- [ ] رمز عبور در URL قرار نمیگیرد.
- [ ] پیام خطای Login عمومی است.
- [ ] تعداد تلاشهای ناموفق محدود میشود.
- [ ] Rate Limiting برای Login وجود دارد.
- [ ] Reset Password با Token تصادفی انجام میشود.
- [ ] Token بازیابی تاریخ انقضا دارد.
- [ ] Token بازیابی یکبارمصرف است.
- [ ] بعد از Login موفق Session ID تغییر میکند.
- [ ] رمز عبورهای بسیار ضعیف رد میشوند.
- [ ] امکان استفاده از رمزهای طولانی وجود دارد.
- [ ] احراز هویت دومرحلهای در صورت نیاز در نظر گرفته شده است.
- [ ] Backup دیتابیس محافظت میشود.
- [ ] دسترسی به دیتابیس محدود است.
- [ ] امکان Rehash با
password_needs_rehash()بررسی میشود.
# نمونه نهایی ثبتنام و ورود امن
فرآیند ثبتنام:
Password Input
↓
Validate Password Policy
↓
password_hash()
↓
Store Hash in Database
فرآیند ورود:
Password Input
↓
Find User
↓
password_verify()
↓
session_regenerate_id(true)
↓
Create Authenticated Session
فرآیند تغییر الگوریتم:
Successful Login
↓
password_needs_rehash()
↓
Generate New Hash
↓
Update Database
فرآیند بازیابی رمز:
Request Reset
↓
Generate Random Token
↓
Store Token Hash
↓
Send Token Link
↓
Validate Token
↓
Hash New Password
↓
Invalidate Token
# جمعبندی
رمز عبور نباید هیچوقت به صورت متن ساده در دیتابیس ذخیره شود.
در PHP باید از:
password_hash()
برای تولید Hash و از:
password_verify()
برای بررسی رمز عبور استفاده کنیم.
همچنین باید از الگوریتمهایی مانند MD5 و SHA-1 برای Hash مستقیم رمز عبور دوری کنیم، زیرا این الگوریتمها برای ذخیره امن Password طراحی نشدهاند.
امنیت رمز عبور فقط به Hash کردن محدود نمیشود. موارد زیر نیز اهمیت دارند:
Password Hashing
+
Rate Limiting
+
Session Regeneration
+
Secure Reset Password
+
Token Expiration
+
Two-Factor Authentication
+
Protected Backups
+
Safe Logging
در نهایت، بهترین سیستم احراز هویت سیستمی است که حتی در صورت افشای بخشی از اطلاعات، امکان سوءاستفاده را تا حد ممکن کاهش دهد.





