# اول سؤال درست را بپرسیم: چه چیزی باید در دیتابیس ذخیره شود؟
decrypt() وجود ندارد. سرور نیز نباید گزینهای با عنوان «نمایش رمز فعلی کاربر» داشته باشد. اگر کاربر رمز را فراموش کرد، راه درست ساختن رمز جدید از طریق فرایند Password Reset است؛ نه ارسال رمز قبلی برای او.
# Encryption، Hash و Password Hashing یک چیز نیستند
#
چرا SHA-256(password) هنوز امن نیست؟
123456 داشته باشند، Hash هر صد نفر دقیقاً یکسان خواهد بود و مهاجم حتی پیش از پیدا کردن رمز میفهمد این حسابها از یک رمز مشترک استفاده کردهاند.
SHA-256 برای سریعبودن طراحی شده است. سرعت برای بررسی تمامیت فایل و امضای دیجیتال مزیت است، اما در ذخیره رمز عبور به مهاجم اجازه میدهد حدسهای بسیار زیادی را با هزینه کم امتحان کند. یک حمله آموزشی کوچک را ببینید:
Password found: sunshine
# Salt چیست؟ یک رمز یکسان، دو خروجی متفاوت
sunshine است، اما چون Saltها فرق دارند، خروجیها نیز متفاوتاند. مهاجم دیگر نمیتواند یک بار Hash رمز sunshine را محاسبه کند و آن را با تمام کاربران مقایسه کند؛ باید برای Salt هر رکورد محاسبه جداگانهای انجام دهد. جدولهای ازپیشمحاسبهشده مانند Rainbow Table نیز ارزش قبلی خود را از دست میدهند.
SHA-256 مشکل خروجیهای تکراری را حل میکند، اما SHA-256 را به یک Password Hash مناسب تبدیل نمیکند؛ الگوریتم همچنان سریع است. Salt جلوی محاسبه از پیش و حمله همزمان به چند حساب را میگیرد، ولی جلوی حدسزدن جداگانه هر رمز ضعیف را نمیگیرد.
# Pepper چیست؟ Saltی که نباید داخل دیتابیس باشد
برای هر رمز متفاوت، غیرمحرمانه، کنار Hash در دیتابیس ذخیره میشود.
میان رکوردها مشترک، محرمانه، و هرگز کنار Hash ذخیره نمیشود.
password + pepper به رمز چسباند. یک روش استانداردتر استفاده از HMAC است؛ یعنی Pepper نقش کلید یک Hash کلیددار را بازی کند:
SHA-256 سریع همچنان طراحی خوبی ندارد.
# Work Factor؛ چرا الگوریتم رمز عبور عمداً باید کند باشد؟
۱ ورود × هزینه Hash
میلیونها حدس × همان هزینه
Argon2id است. سه پارامتر مهم آن عبارتاند از:
m=19456 KiB، t=2 و p=1 را پیشنهاد میکند. این عدد نقطه شروع است، نه عددی مقدس برای تمام سرورها؛ تنظیمات باید روی سختافزار واقعی Benchmark شوند و با رشد توان پردازشی در آینده افزایش یابند.
# بیایید یک سیستم واقعی با Argon2id بسازیم
argon2-cffi استفاده میکنیم. این کتابخانه Salt تصادفی را برای هر Hash میسازد و تمام اطلاعات لازم برای Verify را در قالب استاندارد رشته خروجی قرار میدهد:
$argon2id$...password_hash در همان جدول بگذاریم، لایه امنیتی اضافهشده عملاً از بین میرود.
# چرا یک رمز یکسان هر بار Hash متفاوتی میسازد؟
$argon2id$...$SALT_B$HASH_B
Different: True
First valid: True — Second valid: True
# اگر تنظیمات Argon2 در آینده عوض شود چه میشود؟
check_needs_rehash() در نمونه انجام میدهد. اگر مقدار دوم خروجی تابع verify_password() یک رشته بود، Application باید آن را جایگزین Hash قدیمی کاربر کند. کاربرانی که دیگر Login نمیکنند خودکار ارتقا پیدا نمیکنند؛ بسته به حساسیت سیستم، میتوان Hashهای بسیار قدیمی را منقضی کرد و هنگام مراجعه بعدی، Password Reset اجباری خواست.
# با Pepper قدیمی چه کنیم؟
# اگر سیستم قدیمی SHA-256 یا MD5 دارد، چطور مهاجرت کنیم؟
password_scheme داشت:
# این طراحی در برابر کدام حملهها مقاومت میکند؟
# اشتباههای رایجی که کل طراحی را خراب میکنند
SHA256(salt + password) نیز عمداً کند و Memory-hard نیست.
# نسخه نهایی جریان ثبتنام و ورود
# رمز عبور را نگه نمیداریم؛ امکان بررسی آن را نگه میداریم
Salt عمومی و منحصربهفرد است، در حالی که Pepper محرمانه بوده و خارج از دیتابیس نگهداری میشود.
امنیت واقعی نیز از ترکیب صحیح و هماهنگ این اجزا به دست میآید.



