Application Security

فراتر از MD5: تسلط بر استراتژی‌های هش پسورد مدرن

در دنیای امنیت برنامه‌ها، هیچ مسئولیتی به اندازه مدیریت اعتبارنامه‌های کاربر حیاتی نیست. برای دهه‌ها، صنعت به توابع هش یک‌طرفه سریع مانند MD5 و SHA-1 متکی بود. با این حال، با پیشرفت قابلیت‌های سخت‌افزاری، به ویژه با ظهور پردازنده‌های گرافیکی (GPU) و ASICهای تخصصی، این الگوریتم‌های قدیمی به شدت منسوخ و خطرناک شده‌اند. امروزه ذخیره پسوردها با استفاده از هش‌های ساده، مانند قفل کردن در ورودی خانه با یک تکه نخ است. این پست استراتژی‌های مدرن هش پسورد را بررسی می‌کند و بر توابع هش تطبیق‌پذیری تمرکز دارد که برای مقاومت در برابر حملات نیروی خام طراحی شده‌اند.

اصول بنیادین ذخیره‌سازی امن پسورد

قبل از ورود به الگوریتم‌های خاص، درک سه ستون اصلی ذخیره‌سازی امن پسورد ضروری است:

  1. عملکرد یک‌طرفه: باید از نظر محاسباتی غیرممکن باشد که هش را معکوس کرده و پسورد اصلی را بازیابی کنید.
  2. استفاده از نمک (Salting): هر پسورد باید قبل از هش شدن با یک رشته داده تصادفی و یکتا (نمک) ترکیب شود. این کار از حملات جدول رنگین‌کمانی جلوگیری کرده و تضمین می‌کند که دو کاربر با پسورد یکسان، هش‌های متفاوتی داشته باشند.
  3. تطبیق‌پذیری (فاکتور کار): فرآیند هش باید عمداً کند و قابل پیکربندی باشد. این به توسعه‌دهندگان اجازه می‌دهد تا هزینه محاسباتی را با بهبود سخت‌افزار در طول زمان افزایش دهند و هزینه حملات را برای مهاجمان بازدارنده نگه دارند.

استاندارد طلایی: bcrypt و Argon2

در حال حاضر، bcrypt و Argon2 استانداردهای توصیه‌شده هستند. Argon2 برنده مسابقه هش پسورد (PHC) است و به دلیل ماهیت سخت‌افزار-محور (memory-hard) آن که در برابر حملات مبتنی بر GPU مقاوم است، معمولاً برای پروژه‌های جدید ترجیح داده می‌شود. Bcrypt همچنان به طور گسترده پشتیبانی می‌شود و اگر Argon2 در استک خاص شما در دسترس نباشد، انتخابی ایمن است.

چرا SHA-256 نه؟

توسعه‌دهندگان اغلب به اشتباه از SHA-256 یا SHA-512 برای ذخیره‌سازی پسورد استفاده می‌کنند. اگرچه این‌ها هش‌های رمزنگاری امنی هستند، اما برای سریع بودن طراحی شده‌اند. یک مهاجم می‌تواند میلیاردها هش SHA-256 را در ثانیه روی یک GPU واحد محاسبه کند. در مقابل، الگوریتم‌های هش پسورد برای کند بودن طراحی شده‌اند. آن‌ها یک پارامتر "هزینه" را گنجانده‌اند که تعداد تکرارها یا بلوک‌های حافظه مورد استفاده را تعیین می‌کند و هم تلاش‌های احراز هویت قانونی و هم تلاش‌های نفوذ نیروی خام را کند می‌کند.

پیاده‌سازی عملی در پایتون

بیایید نگاهی بیندازیم که چگونه می‌توان هش پسورد امن را با استفاده از کتابخانه passlib پیاده‌سازی کرد که پیچیدگی کتابخانه‌های زیرین مانند bcrypt یا argon2 را انتزاع می‌کند.

استفاده از bcrypt

در اینجا یک مثال پایتون نشان داده شده است که نحوه هش کردن یک پسورد و بررسی آن در برابر هش ذخیره‌شده را نشان می‌دهد. توجه داشته باشید که ما از یک تولیدکننده نمک ثابت برای اطمینان از یکتایی هر پسورد استفاده می‌کنیم.

import bcrypt

def hash_password(plain_password: str) -> str:
    # تولید نمک و هش کردن پسورد
    # bcrypt به طور خودکار نمک را مدیریت کرده و آن را در خروجی گنجانده است
    salt = bcrypt.gensalt(rounds=12)
    hashed = bcrypt.hashpw(plain_password.encode('utf-8'), salt)
    return hashed.decode('utf-8')

def verify_password(plain_password: str, hashed_password: str) -> bool:
    # بررسی پسورد ارائه‌شده در برابر هش ذخیره‌شده
    return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))

# مثال استفاده
password = "MySuperSecret123!"
hashed = hash_password(password)
print(f"Hashed Password: {hashed}")

# تأیید
if verify_password(password, hashed):
    print("Access Granted")
else:
    print("Access Denied")

استفاده از Argon2

Argon2id واریانت توصیه‌شده است که مقاومت در برابر حملات کانال جانبی را با سخت‌افزار-محوری مقاوم در برابر GPU ترکیب می‌کند.

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher(
    time_cost=3,      # تعداد تکرارها
    memory_cost=65536, # استفاده از حافظه به کیلوبایت
    parallelism=4,    # تعداد رشته‌های اجرا
    hash_len=16,      # طول هش خروجی
    salt_len=16       # طول نمک
)

def hash_argon2(password: str) -> str:
    return ph.hash(password)

def verify_argon2(password: str, hash: str) -> bool:
    try:
        ph.verify(hash, password)
        return True
    except VerifyMismatchError:
        return False

# مثال استفاده
h = hash_argon2("MySuperSecret123!")
print(f"Argon2 Hash: {h}")
print(verify_argon2("MySuperSecret123!", h))

بهترین شیوه‌ها برای توسعه‌دهندگان

  • هرگز رمزنگاری خود را پیاده‌سازی نکنید: همیشه از کتابخانه‌های تثبیت‌شده استفاده کنید. آسیب‌پذیری‌های کانال جانبی در پیاده‌سازی‌های سفارشی رایج است.
  • حملات زمان‌بندی را مدیریت کنید: اطمینان حاصل کنید که توابع تأیید شما از روش‌های مقایسه با زمان ثابت استفاده می‌کنند. اکثر کتابخانه‌های مدرن (مانند bcrypt و argon2) این کار را به صورت داخلی انجام می‌دهند، اما اگر منطق سفارشی می‌سازید، محتاط باشید.
  • برای چرخش آماده باشید: همان‌طور که استانداردها تکامل می‌یابند، باید برنامه‌ریزی کنید که پسوردها را در هنگام ورود کاربر مجدداً هش کنید اگر هش فعلی از الگوریتم ضعیف‌تر یا فاکتور هزینه پایین‌تری استفاده می‌کند. به این کار "هش مجدد تنبل" (lazy re-hashing) گفته می‌شود.
  • سیاست‌های قوی را اعمال کنید: اگرچه هش کردن از پایگاه داده محافظت می‌کند، اما اعمال سیاست‌های قوی پسورد (طول، پیچیدگی) احتمال شکست آفلاین موفق را در صورت وقوع نقض امنیتی کاهش می‌دهد.

نتیجه‌گیری

هش پسورد یک وظیفه "تنظیم کن و فراموش کن" نیست. این کار نیاز به هوشیاری مداوم دارد، زیرا قدرت محاسباتی افزایش می‌یابد. با مهاجرت از هش‌های سریع مانند SHA-256 و پذیرش الگوریتم‌های سخت‌افزار-محور مانند Argon2 یا الگوریتم‌های تطبیقی مانند bcrypt، توسعه‌دهندگان می‌توانند نردبان را برای مهاجمان به طور قابل توجهی بالا ببرند. امروز از اعتبارنامه‌های کاربران خود محافظت کنید تا از نقض‌های فاجعه‌بار فردا جلوگیری نمایید.

Share: