MD5، SHA-1 یا SHA-256: از کدام هش استفاده کنیم؟
برای هر کاری که به امنیت ربط دارد از SHA-256 استفاده کنید؛ MD5 و SHA-1 فقط جایی قابل قبولاند که ساختن یک هش جعلی یکسان برای هیچکس سودی نداشته باشد.
MD5، SHA-1 و SHA-256 هر سه یک کار انجام میدهند: ورودی با هر طولی را به یک اثر انگشت کوتاه با اندازه ثابت تبدیل میکنند که به آن چکیده یا digest میگویند. یک ورودی همیشه همان چکیده را میدهد و تغییر حتی یک بیت، کل نتیجه را عوض میکند. تفاوت این سه در طول چکیده، در سرعت و مهمتر از همه در این است که آیا کسی میتواند عمداً دو ورودی متفاوت با چکیده یکسان بسازد یا نه. این کار برای MD5 و SHA-1 بهطور علنی انجام شده است. برای SHA-256 نه.
نگاه کلی به سه الگوریتم
| MD5 | SHA-1 | SHA-256 | |
|---|---|---|---|
| اندازه چکیده | ۱۲۸ بیت، ۳۲ نویسه هگز | ۱۶۰ بیت، ۴۰ نویسه هگز | ۲۵۶ بیت، ۶۴ نویسه هگز |
| انتشار | ۱۹۹۲ (RFC 1321) | ۱۹۹۵ (FIPS 180-1) | ۲۰۰۱، بهعنوان بخشی از خانواده SHA-2 |
| تصادم (collision) | عملی از سال ۲۰۰۴ | عملی از سال ۲۰۱۷ | حمله شناختهشدهای ندارد |
| پیدا کردن ورودی برای یک چکیده مشخص | حمله عملی ندارد | حمله عملی ندارد | حمله عملی ندارد |
| مناسب برای امروز | چکسامهای غیرامنیتی و کلیدهای کش | فقط سازگاری با سیستمهای قدیمی | انتخاب پیشفرض برای کارهای جدید |
در ادامه چکیده کامل پنج بایت hello را میبینید، همانطور که md5sum، sha1sum و sha256sum چاپ میکنند. تولیدکننده هش همین مقدارها را، بهعلاوه SHA-384 و SHA-512، کنار هم نشان میدهد؛ هم برای متن و هم برای فایلی تا حجم ۵۰ مگابایت.
MD5 5d41402abc4b2a76b9719d911017c592
SHA-1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
SHA-256 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
بیتهای بیشتر چه چیزی به شما میدهند
طول چکیده هزینه یک حمله عمومی را تعیین میکند؛ یعنی حملهای که تابع هش را مثل یک جعبه سیاه در نظر میگیرد و از ساختار داخلی آن استفاده نمیکند. به خاطر پارادوکس روز تولد، پیدا کردن هر دو ورودی که چکیده یکسان داشته باشند برای یک چکیده n بیتی حدود 2^(n/2) تلاش لازم دارد: 2^64 برای MD5، 2^80 برای SHA-1 و 2^128 برای SHA-256. اما پیدا کردن ورودیای که با یک چکیده مشخص جور دربیاید (که به آن preimage یا پیشنگاره میگویند) حدود 2^n تلاش میخواهد.
این عددها فقط تا وقتی درستاند که طراحی الگوریتم هیچ میانبری نداشته باشد. هم MD5 و هم SHA-1 برای ساختن تصادم میانبر دارند و به همین دلیل طول چکیدهشان دیگر همه ماجرا را نمیگوید. برای SHA-256 هیچ میانبر شناختهشدهای وجود ندارد و 2^128 بسیار فراتر از هر توان محاسباتی است که امروز وجود دارد.
MD5 و SHA-1 چطور شکسته شدند
حمله تصادم به کسی اجازه نمیدهد یک هش را معکوس کند. کاری که میکند این است که مهاجم از قبل دو ورودی متفاوت با یک چکیده مشترک آماده میکند، کاری میکند که یکی از آنها مورد اعتماد قرار بگیرد و بعد دیگری را جایش میگذارد. نوع خطرناک این حمله، تصادم با پیشوند انتخابی (chosen-prefix) است: مهاجم دو شروع معنادار انتخاب میکند، مثلاً دو گواهی دیجیتال با نامهای متفاوت، و بعد بایتهای اضافهای محاسبه میکند که چکیده هر دو را برابر میکند.
- MD5، سال ۲۰۰۴: Xiaoyun Wang و همکارانش تصادمهای عملی برای MD5 منتشر کردند.
- MD5، سال ۲۰۰۸: گروهی از پژوهشگران با یک تصادم MD5 با پیشوند انتخابی، گواهی جعلی یک مرجع صدور گواهی (CA) ساختند که مرورگرها به آن اعتماد میکردند؛ چون یک CA تجاری هنوز گواهیها را با MD5 امضا میکرد.
- MD5، سال ۲۰۱۲: بدافزار Flame با استفاده از یک تصادم MD5 یک گواهی امضای کد مایکروسافت را جعل کرد.
- SHA-1، سال ۲۰۱۷: گوگل و CWI آمستردام حمله SHAttered را اعلام کردند: دو فایل PDF متفاوت با چکیده SHA-1 یکسان. به گزارش این تیم، این کار حدود ۶٬۵۰۰ سال پردازش CPU و ۱۱۰ سال پردازش GPU لازم داشت.
- SHA-1، سال ۲۰۲۰: Gaëtan Leurent و Thomas Peyrin یک تصادم عملی SHA-1 با پیشوند انتخابی منتشر کردند و با آن خود را جای صاحب یک کلید PGP جا زدند.
مرورگرها از سال ۲۰۱۷ پذیرفتن گواهیهای TLS با SHA-1 را متوقف کردند و NIST در دسامبر ۲۰۲۲ اعلام کرد که SHA-1 باید تا پایان ۲۰۳۰ کاملاً کنار گذاشته شود. با این حال، نه MD5 و نه SHA-1 حمله preimage عملی ندارند: اگر فقط چکیده را داشته باشید، هیچکس نمیتواند ورودیای پیدا کند که همان چکیده را تولید کند. به همین دلیل هر دو هنوز برای تشخیص خرابیهای تصادفی کار میکنند.
سرعت: اندازهگیریشده، نه فرضی
معمولاً MD5 را الگوریتم سریع میدانند. ما ۲۵۶ مگابایت داده تصادفی یکسان را با Node 24 (OpenSSL 3.5) روی یک کامپیوتر رومیزی هش کردیم و هر آزمایش را سه بار تکرار کردیم:
| الگوریتم | توان عملیاتی روی همان دستگاه |
|---|---|
| SHA-1 | حدود ۱٬۰۰۰ مگابایت بر ثانیه |
| MD5 | حدود ۷۳۰ مگابایت بر ثانیه |
| SHA-512 | حدود ۶۹۰ مگابایت بر ثانیه |
| SHA-256 | حدود ۴۶۰ مگابایت بر ثانیه |
این ترتیب به پردازنده بستگی دارد. پردازنده این آزمایش افزونههای مجموعه دستورالعمل SHA را ندارد؛ پردازندههایی که این افزونهها را دارند، SHA-1 و SHA-256 را در سطح سختافزار شتاب میدهند و در آن حالت SHA-256 میتواند از MD5 هم جلو بزند. اینجا SHA-512 از SHA-256 سریعتر بود، چون روی کلمههای ۶۴ بیتی کار میکند. برای ورودیهای کوتاه این تفاوت از بین میرود: یک میلیون هش از یک آدرس ایمیل کوتاه با هرکدام از MD5، SHA-1 و SHA-256 بین ۱٫۸ و ۲٫۱ ثانیه طول کشید، چون سربار هر فراخوانی از خود عمل هش کردن بیشتر است. سرعت تقریباً هیچوقت دلیل واقعی برای انتخاب MD5 نیست.
هرکدام هنوز برای چه کاری مناسب است
- MD5: تشخیص خرابی تصادفی، پیدا کردن فایلهای تکراری در فایلهای خودتان، کلیدهای کش و ETagها، و ارتباط با سیستمهای قدیمی که آن را انتظار دارند. هرگز برای امضا، گواهی یا بررسی سلامتی که مهاجم ممکن است خود فایل را تهیه کرده باشد.
- SHA-1: سیستمهای موجودی که هنوز نمیتوانید تغییرشان دهید. Git مدتهاست اشیا را با SHA-1 نامگذاری میکند و از یک پیادهسازی مقاومشده استفاده میکند که حملههای تصادم شناختهشده را تشخیص میدهد؛ ضمناً از مخزنهایی که با SHA-256 کار میکنند هم پشتیبانی میکند. HMAC-SHA1، الگوریتم پیشفرضی که پشت کدهای TOTP در اپلیکیشنهای احراز هویت قرار دارد، تحت تأثیر حملههای تصادم نیست.
- SHA-256: چکسام فایلهای دانلودی، امضای دیجیتال، گواهیها، HMAC، آدرسدهی بر اساس محتوا و هر کار جدیدی. وقتی چکسام باید در برابر دستکاری مقاوم باشد از هش SHA-256 استفاده کنید و هش MD5 را برای مقدارهای قدیمیای نگه دارید که هنوز باید با آنها تطبیق دهید.
میخواهید یک فایل دانلودی را بررسی کنید؟ بررسی چکسام فایل فایل را روی دستگاه خودتان هش میکند و میگوید چکسامی که چسباندهاید با کدام الگوریتم جور درمیآید.
هیچکدام برای ذخیره رمز عبور مناسب نیستند
هر سه الگوریتم طوری طراحی شدهاند که سریع باشند، و سرعت دقیقاً همان چیزی است که مهاجمی که میلیاردها رمز عبور را امتحان میکند میخواهد. نمک (salt) جلوی جدولهای ازپیشمحاسبهشده را میگیرد، اما سرعت حدس زدن را کم نمیکند. برای ذخیره رمز عبور تابعی لازم است که عمداً کند باشد و در حالت ایدهآل حافظه زیادی هم مصرف کند (memory-hard). روی همان دستگاه قبلی:
| تابع و تنظیمات | زمان برای هر رمز عبور |
|---|---|
| SHA-256، یک فراخوانی از Node | حدود ۲٫۵ میکروثانیه |
| Argon2id، ۱۹ مبیبایت حافظه، ۲ تکرار، ۱ lane | حدود ۳۳ میلیثانیه |
| PBKDF2-HMAC-SHA256، ۶۰۰٬۰۰۰ تکرار | حدود ۲۷۰ میلیثانیه |
| scrypt، N=2^17, r=8, p=1 | حدود ۳۱۰ میلیثانیه |
این تنظیمات حداقلهایی است که راهنمای OWASP Password Storage Cheat Sheet توصیه میکند. این راهنما در درجه اول Argon2id را پیشنهاد میکند، بعد scrypt، برای سیستمهای قدیمی bcrypt با work factor برابر ۱۰ یا بیشتر، و PBKDF2 را جایی که انطباق با FIPS-140 الزامی است. بهجای اینکه خودتان چیزی را با یک هش ساده سر هم کنید، از API هش رمز عبور فریمورک خودتان استفاده کنید.
HMAC: وقتی هش به کلید نیاز دارد
برای اینکه ثابت کنید پیامی از طرف کسی آمده که یک راز را در اختیار دارد، sha256(secret + message) را محاسبه نکنید. MD5، SHA-1 و SHA-256 همگی در برابر حمله گسترش طول (length extension) آسیبپذیرند: مهاجمی که چکیده یک پیام را میداند، میتواند بدون دانستن راز، چکیده معتبری برای همان پیام بههمراه دادهای که به انتهایش اضافه شده محاسبه کند. HMAC (RFC 2104) این مشکل را ندارد. انتخاب استاندارد HMAC-SHA256 است و همه این دستورها مقدار یکسانی چاپ میکنند:
// Node.js
crypto.createHmac('sha256', 'secret').update('hello').digest('hex')
# Python
hmac.new(b'secret', b'hello', hashlib.sha256).hexdigest()
// PHP
hash_hmac('sha256', 'hello', 'secret')
// 88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b
مقدارهای HMAC را با یک تابع زمانثابت (constant-time) مثل crypto.timingSafeEqual، hmac.compare_digest یا hash_equals مقایسه کنید، نه با ==.
هش کردن در خط فرمان و در کد
# Linux (coreutils)
md5sum file.iso
sha1sum file.iso
sha256sum file.iso
# macOS
md5 file.iso
shasum -a 1 file.iso
shasum -a 256 file.iso
# Windows PowerShell
Get-FileHash file.iso -Algorithm MD5
Get-FileHash file.iso -Algorithm SHA1
Get-FileHash file.iso # SHA256 is the default
# Any system with OpenSSL
openssl dgst -sha256 file.iso
# Hashing a string: printf adds no newline, echo does
printf '%s' hello | sha256sum # 2cf24dba5fb0a30e...
echo hello | sha256sum # 5891b5b522d5df08...
در PowerShell اگر الگوریتم را مشخص نکنید، بهطور پیشفرض SHA-256 محاسبه میشود. برای هش کردن یک رشته هم دقت کنید که printf خط جدید به انتها اضافه نمیکند ولی echo اضافه میکند، و به همین دلیل دو خط آخر نتیجه متفاوتی دارند.
در مرورگر، crypto.subtle.digest() از SHA-1، SHA-256، SHA-384 و SHA-512 پشتیبانی میکند اما MD5 را نه؛ درخواست MD5 خطای NotSupportedError میدهد. به همین دلیل ابزارهای مبتنی بر مرورگر، از جمله ابزار همین سایت، MD5 را با یک پیادهسازی جداگانه در JavaScript محاسبه میکنند.