نشت دیتابیس برای هر سایتی ممکن است اتفاق بیفتد. چیزی که تعیین میکند بعد از نشت چه بلایی سر کاربران میآید، این است که رمزهایشان چطور ذخیره شدهاند. در این راهنما میبینیم چرا Argon2id امروز بهترین انتخاب برای رمزهاست و چطور در چند دقیقه آن را در یک پروژهٔ جنگو فعال کنیم، بدون اینکه کاربرهای فعلی مجبور به عوض کردن رمز شوند.
رمز عبور هیچوقت نباید خودش ذخیره شود؛ فقط «هش» آن. ولی هر هشی مناسب نیست. الگوریتمهایی مثل SHA-256 برای سرعت ساخته شدهاند و همین سرعت به نفع مهاجم است: با کارت گرافیک میشود در هر ثانیه میلیاردها حدس را امتحان کرد.
الگوریتم مخصوص رمز باید عمداً کند باشد و حافظهٔ زیادی بخواهد تا حمله با سختافزار موازی گران شود. Argon2 برندهٔ مسابقهٔ Password Hashing Competition بود و نسخهٔ Argon2id آن همان چیزی است که OWASP امروز اول از همه پیشنهاد میکند.
جنگو بهطور پیشفرض از PBKDF2 استفاده میکند که امن است، ولی در برابر حملهٔ کارت گرافیک از Argon2id ضعیفتر است، چون حافظهٔ زیادی نمیخواهد. خبر خوب این است که جنگو خودش از Argon2 پشتیبانی میکند و فقط باید روشنش کنید.
pip install "django[argon2]"
این دستور بستهٔ argon2-cffi را نصب میکند. آن را به requirements پروژه هم اضافه کنید.
در فایل تنظیمات پروژه:
PASSWORD_HASHERS = [ "django.contrib.auth.hashers.Argon2PasswordHasher", "django.contrib.auth.hashers.PBKDF2PasswordHasher", "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", "django.contrib.auth.hashers.ScryptPasswordHasher", ]
اولین مورد فهرست برای رمزهای جدید استفاده میشود. بقیه را نگه دارید تا رمزهای قدیمی هنوز قابل بررسی باشند.
لازم نیست کاری بکنید یا از کاربرها بخواهید رمز عوض کنند. وقتی کاربری با رمز قدیمی (PBKDF2) وارد میشود، جنگو بعد از بررسی موفق رمز، آن را خودکار با Argon2id دوباره هش و ذخیره میکند. یعنی با گذشت زمان، همهٔ کاربران فعال بیصدا ارتقا پیدا میکنند.
برای اطمینان، رمز یک کاربر را در shell نگاه کنید. بعد از ورود، باید با argon2 شروع شود:
python manage.py shell -c "from django.contrib.auth import get_user_model; print(get_user_model().objects.get(username='admin').password[:30])" # argon2$argon2id$v=19$m=...
سه پارامتر سختی را تعیین میکنند: زمان (تعداد دور)، حافظه و موازیسازی. میتوانید با یک کلاس کوچک آنها را تغییر دهید:
# myproject/hashers.py from django.contrib.auth.hashers import Argon2PasswordHasher class TunedArgon2PasswordHasher(Argon2PasswordHasher): time_cost = 3 memory_cost = 65536 # کیلوبایت = ۶۴ مگابایت parallelism = 2
و در PASSWORD_HASHERS بهجای خط اول بنویسید "myproject.hashers.TunedArgon2PasswordHasher". حداقلی که OWASP پیشنهاد میکند ۱۹ مگابایت حافظه، ۲ دور و موازیسازی ۱ است. روی سرور خودتان بسنجید: یک بار بررسی رمز حدود ۰٫۱ تا ۰٫۵ ثانیه طول بکشد خوب است. روی هاست اشتراکی، حافظه را خیلی بالا نبرید. اگر پارامترها را بعداً عوض کنید، جنگو در ورود بعدی رمزها را با پارامترهای جدید دوباره هش میکند.
قویترین الگوریتم هم رمز «123456» را نجات نمیدهد. اعتبارسنجهای داخلی جنگو را روشن کنید:
AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 10}}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ]
نکته برای سایتهای فارسی: فهرست رمزهای رایج جنگو انگلیسی است و رمزهایی مثل «salam1234» یا واژههای فینگلیش را نمیشناسد. میتوانید با پارامتر password_list_path یک فهرست سفارشی به CommonPasswordValidator بدهید. بهتر است ارقام فارسی و عربی و «ی» و «ک» عربی را هم قبل از مقایسه یکسان کنید.
نصب argon2-cffi و گذاشتن Argon2PasswordHasher در اول فهرست، تقریباً همهٔ کار است.
رمزهای قدیمی در اولین ورود خودکار ارتقا پیدا میکنند.
پارامترها را برای سرور خودتان تنظیم کنید و رمزهای ضعیف را رد کنید.
ما همین روش را، بههمراه فهرست رمزهای رایج فارسی و فینگلیش، در سیستم مدیریت محتوای متنباز راویکس (Ravix CMS) پیاده کردهایم. اگر سؤالی داشتید، در نظرها بپرسید.