برای دههها، قانون طلایی امنیت رمز عبور ساده بود: هرگز رمزها را به صورت متن ساده ذخیره نکنید. با این حال، با افزایش نمایی قدرت محاسباتی، روشهایی که برای محافظت از این رمزها استفاده میکردیم، منسوخ شدهاند. اگر هنوز از MD5، SHA-1 یا حتی SHA-256 ساده برای هش کردن رمزها در برنامه خود استفاده میکنید، احتمالاً دادههای کاربران شما در خطر است. در این پست، ما تکامل هش کردن رمز عبور، دلایل خطرناک بودن الگوریتمهای قدیمی در برابر حملات نیروی خردکننده (brute-force) و جداول رنگینکمانی (rainbow table) را بررسی میکنیم و نحوه پیادهسازی استراتژیهای هش مدرن و مقاوم مانند Argon2 و bcrypt را توضیح میدهیم.
تکامل هش کردن: از خلاصهساز تا تابع استخراج کلید
تمایز بین یک تابع هش رمزنگاری و یک تابع استخراج کلید (KDF) حیاتی است. توابع هش عمومی مانند SHA-256 برای سرعت و تأیید یکپارچگی طراحی شدهاند. آنها قطعی هستند؛ ورودی یکسان همیشه خروجی یکسان تولید میکند. اگرچه SHA-256 نسبت به MD5 برای مهاجمان از نظر محاسباتی پرهزینهتر است، اما همچنان بسیار سریع است. یک GPU مدرن میتواند میلیاردها هش SHA-256 را در ثانیه محاسبه کند که حملات نیروی خردکننده را حتی برای رمزهای عبور طولانی امکانپذیر میسازد.
هش کردن رمز عبور به یک KDF نیاز دارد. این توابع به طور خاص برای کند بودن و پرهزینه بودن از نظر منابع طراحی شدهاند و با گنجاندن نمک (salt) و پارامترهای قابل تنظیم، در برابر حملات شتابیافته با سختافزار مقاومت میکنند. استاندارد صنعت به سمت الگوریتمهایی تغییر یافته است که نه تنها کند، بلکه سختافزار-محور (memory-hard) هستند که استفاده از سختافزارهای تخصصی مانند ASIC یا FPGA را برای مهاجمان دشوار میسازد.
چرا الگوریتمهای قدیمی شکست میخورند
بیایید نگاهی بیندازیم که چرا استراتژیهای قدیمیتر پاسخگو نیستند. MD5 از نظر رمزنگاری شکسته شده و پر از آسیبپذیریهای برخورد (collision) است. این الگوریتم برای اهداف امنیتی عملاً بیاستفاده است. SHA-1 نیز ضعفهای مشابهی دارد و توسط NIST منسوخ شده است. SHA-256، اگرچه از نظر ریاضی همچنان معتبر است، اما مکانیسمهای کندسازی داخلی (مانند تکرارها یا استفاده از حافظه) مورد نیاز برای ذخیرهسازی رمز عبور را ندارد.
یک سوءتفاهم رایج دیگر این است که افزودن «نمک» به یک هش سریع مانند SHA-256 کافی است. اگرچه نمکگذاری با اطمینان از اینکه رمزهای عبور یکسان هشهای متفاوتی داشته باشند، حملات جداول رنگینکمانی را جلوگیری میکند، اما فرآیند نیروی خردکننده را کند نمیکند. یک مهاجم میتواند همچنان میلیونها رمز عبور را در ثانیه به ازای هر GPU حدس بزند، حتی اگر هر رمز عبور منحصر به فرد باشد.
استراتژیهای توصیه شده: bcrypt و Argon2
در حال حاضر، دو الگوریتمی که بیشترین توصیه را برای هش کردن رمز عبور دارند، bcrypt و Argon2 هستند. bcrypt بیش از یک دهه است که استاندارد صنعتی بوده و در تمام زبانهای اصلی به خوبی پشتیبانی میشود. این الگوریتم از رمزنگاری Blowfish استفاده میکند و به شما امکان میدهد یک ضریب هزینه (فاکتور کار) را تنظیم کنید تا هزینه محاسباتی را تعیین نمایید.
با این حال، Argon2 برنده مسابقه هش کردن رمز عبور (PHC) است و به طور فزایندهای به عنوان استاندارد طلای جدید در حال ظهور است. Argon2 سختافزار-محور است، به این معنی که برای محاسبه هش به مقدار قابل توجهی حافظه نیاز دارد. این ویژگی، مزیت حملات سختافزاری موازیسازی شده را خنثی میکند. سه نسخه وجود دارد: Argon2id (برای هش کردن رمز عبور توصیه میشود)، Argon2i و Argon2d.
مثال عملی: پیادهسازی Argon2 در پایتون
پیادهسازی Argon2 با استفاده از کتابخانه argon2-cffi ساده است. در زیر یک مثال عملی از نحوه هش کردن امن یک رمز عبور و تأیید آن آورده شده است.
import argon2
# مقداردهی اولیه هشساز با تنظیمات پیشفرض توصیه شده برای Argon2id
ph = argon2.PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=16,
salt_len=16
)
# هش کردن رمز عبور
password = "user_secure_password_123"
hashed_password = ph.hash(password)
print(f"Hashed Password: {hashed_password}")
# تأیید رمز عبور
try:
is_valid = ph.verify(hashed_password, password)
print(f"Password is valid: {is_valid}")
except argon2.exceptions.VerifyMismatchError:
print("Invalid password")
در این مثال، time_cost نشاندهنده تعداد تکرارها، memory_cost حافظه مصرفی به کیلوبایت (KiB) و parallelism تعداد رشتهها (threads) است. شما باید این مقادیر را بر اساس ظرفیت سرور خود تنظیم کنید تا اطمینان حاصل کنید که هش کردن حدود 0.5 ثانیه در سختافزار خاص شما طول میکشد که تعادل توصیه شده بین تجربه کاربری و امنیت است.
بهترین شیوهها فراتر از الگوریتم
انتخاب الگوریتم مناسب تنها نیمی از راه است. در اینجا بهترین شیوههای اضافی برای اطمینان از امنیت قوی آورده شده است:
- همیشه نمکگذاری کنید: کتابخانههای مدرن مانند Argon2 نمکگذاری را به طور خودکار انجام میدهند. اطمینان حاصل کنید که هرگز تولید نمک خود را انجام نمیدهید.
- هش کردن مجدد هنگام ورود: اگر ضرایب هزینه خود را ارتقا میدهید یا الگوریتم را تغییر میدهید، پس از ورود موفقیتآمیز، رمز عبور را مجدداً هش کنید. این اطمینان حاصل میکند که تمام رمزهای عبور در نهایت به تنظیمات قویتر مهاجرت میکنند.
- محدود کردن نرخ: محدودیت نرخ را در نقاط پایانی احراز هویت خود پیادهسازی کنید تا از حملات نیروی خردکننده آنلاین جلوگیری شود.
- ممیزیهای منظم: وابستگیهای امنیتی خود را به طور دورهای بررسی کنید و از دستورالعملهای OWASP برای ذخیرهسازی رمز عبور پیروی کنید.
نتیجهگیری
هش کردن رمز عبور یک وظیفه «تنظیم کن و فراموش کن» نیست. همانطور که فناوری تکامل مییابد، روشهایی که مهاجمان برای به خطر انداختن دادههای کاربران استفاده میکنند نیز تغییر میکند. با دوری از توابع هش سریع و عمومی و پذیرش الگوریتمهای سختافزار-محور مانند Argon2 یا bcrypt که آزمون خود را پس دادهاند، شما مانع ورود برای بازیگران مخرب را به طور قابل توجهی افزایش میدهید. به یاد داشته باشید، هدف از هش کردن رمز عبور این نیست که رمزها را غیرقابل شکستن کنید، بلکه این است که شکستن آنها را از نظر هزینه و زمان غیرمنطقی و پرهزینه کنید. هوشیار بمانید، کتابخانههای خود را بهروز نگه دارید و امنیت کاربر را در هر لایه از برنامه خود در اولویت قرار دهید.