در پست دیگه ای در مورد فاجعه ادوب نوشتم و دیدیم که چطور پسورد کاربرها لو رفت. در اون پست دیدیم که روش نگهداری پسوردها توسط ادوب افتضاح بود و نکته وقتی بیشتر به چشم میاد که بدونیم نگهداری صحیح پسوردها، چندان هم سخت نیست.
این پست یک آموزش برنامه نویسی نیست. در مورد اینکه با کدوم زبان برنامه نویسی و توی کدوم دیتابیس می خوایم پسوردها رو نگهداریم، حرف نمی زنم. فقط با هم روش ها رو مرور می کنیم.
روش اول – پسوردها رو بدون رمزنگاری نگهداریم
با توجه به اینکه قصد دارید و باید که جلوی دزدیده شدن اطلاعات کاربری رو بگیرید، ممکنه تصمیم بگیرید که اطلاعات رو به شیوه ای که استفاده راحت تری داشته باشه، نگهدارید.
در صورتیکه یک شبکه کوچیک دارید و پشتیبانی نفر به نفر ارائه می دید، احتمالا به نظرتون بهتر میاد که پسوردها رو به صورت متن ساده نگهدارید که اگر کسی پسوردش رو فراموش کرد، شما نگاه کنید و بهش بگید. این کار رو نکنید.
اولین دلیل اینه که هرکس یه نگاه به فایل بندازه، می تونه پسوردها رو ببینه و به جای دیگران لاگین کنه.
قسمت بدترش اینه که با دیدن نمونه پسوردهای انتخاب شده، می شه فهمید آدم ها چه جور پسوردی رو دوست دارند و بنابراین پسورد اکانت های دیگه شون رو حدس زد. برای مثال آلفرد با اسم خودش و یه عدد کوتاه، پسورد انتخاب می کنه و دیوید یه تاریخ رو انتخاب کرده که احتمالا شخصا براش مهمه.
نکته اینه که نه شما و نه هیچکدوم از ادمین هاتون نباید بتونن پسورد کاربرها رو ببینن. این نکته در مورد اعتماد نیست، بلکه تعریف اولیه پسورده: پسورد باید مثل PIN باشه، به عنوان جزییات تعیین هویت باهاش برخورد بشه که به هیچکس دیگه هم مربوط نیست.
روش دوم – رمز کردن پسوردها در دیتابیس
رمزگذاری پسورد به نظر شیوه بهتری میاد. می تونیم تنظیم کنیم که کلید رمزگشایی در سرور دیگه ای نگهداری بشه و فقط درمواقع نیاز، دیتابیس اون کلید رو فرابخونه و هیچوقت هم در حافظه نگهش نداره. اینجوری دیگه پسوردهای ساده رو روی سرور نمیذارید و کسی هم نمی تونه اونها رو با یه نگاه بخونه. حتی اگر دزدیده بشه هم پسوردها یه سری اطلاعات ناخواناست.
پسوردهای بالا با روش DES رمزگذاری شده. استفاده از DES روش بدیه چونکه از یک کلید ۵۶ بیتی برای رمزگذاری استفاده می کنه. کلیدهای ۵۶ بیتی امکان ۱۰۰ میلیون میلیون پسورد رو ایجاد می کنه که ابزارهای کرک جدید در کمتر از یک روز می تونن بشکوننش.
شاید فکر کنید استفاده از این روش می تونه مفید باشه و هروقت تصمیم بگیرید کلید رو تغییر بدید و یا الگوریتم های دیگه استفاده کنید، می تونید پسورد رو رمزگشایی کنید و با الگوریتم جدید، مجددا رمز کنید. هیچ وقت پسوردها به شیوه های برگشت پذیر، رمز نکنید.
مشکل روش اول همچنان پابرجاست: نه شما و نه هیچکدوم از ادمین ها نباید بتونید پسوردها رو ببینید. در ضمن اگر کسی به سرور شما نفوذ کنه، روش دوم فرق چندانی با روش اول نداره.
این روش یه مشکل دیگه هم داره. رمز کردن پسوردها با DES باعث می شه که یک پسورد در هر بار رمز شدن، یک خروجی ثابت داشته باشه. بابراین در مثال بالا بلافاصله مییتونیم بفهمیم که چارلی و داک پسوردهای مشابهی دارند، حتی بدون نیاز کلید رمز گشایی. همچنین طول پسورد رمز شده می تونه به ما بگه پسورد اصلی چند حرفه.
بنابراین ما باید روشی رو انتخاب کنیم که:
- پسورد کاربرها قابل بازیابی از دیتابیس نباشه
- رمزگذاری پسوردهای مشابه، نتیجه متفاوتی داشته باشه
- دیتابیس نباید شامل اطلاعاتی باشه که طول پسورد رو مشخص بکنه
روش سوم – هش کردن پسوردها
الزام شماره یک در لیست گفته شده ما رو به سمت استفاده از روشی می بره که برگشت ناپذیر باشه. به نظر غیرممکن میاد اما اینجور نیست. می شه با روشی که به هش کردن شناخته می هش، یه پسورد با طول دلخواه رو گرفت و یه خروجی که شبیه یه مقدار رندوم باشه رو تولید کرد.
از نظر ریاضی، هش یک تابع یک طرفه است. می تونیم از یک متن یه مقدار هش تولید کنیم اما از خروجی هش شده نمی شه به متن اصلی رسید. روش های هش کردن جوری طراحی شده که در مقابل تلاش برای معکوس کردنش مقاومت کنه.
در تئوری می شه گفت:
- شما به غیر از استفاده از شانس، با هیچ روش دیگه ای نمی تونید یه خروجی از پیش تعیین شده از هش کردن به دست بیارید
- شما با هیچ روشی غیر از شانس نمی تونید دو فایل رو پیدا کنید که نتیجه هش اونها یکی باشه
- شما از نتیجه هش هیچ چیزی در مورد ورودی نمی فهمید، حتی طولش
از الگوریتم های شناخته شده و پرکاربرد هش کردن می شه به MD5، SHA-1 و SHA-256 اشاره کرد. از این الگوریتم ها، MD5 پیچیدگی کمتری داره و معنیش اینه که خیلی سریعتر می تونید با استفاده از شانس، دو تا فایل پیدا کنید که نتیجه هش اونها مثل هم باشه.
SHA-1 از نظر محاسباتی مثل MD5 است و کارشناسان معتقدند که به زودی مشخص می شه که ضعف های مشابهی هم داره.
ما روی نمونه پسوردها از الگوریتم SHA-256 استفاده کردیم و هش های نتیجه شده رو در تصویر زیر می تونید ببینید. البته برای جا شدن نتایج، بخشی از اون رو در تصویر نیاوردیم.
همه هش ها یک طول دارند و بنابراین ما طلاعاتی در مورد طول پسورد اولیه نمی دیم.
همچنین با توجه به اینکه ما می تونیم پیش بینی کنیم که چقدر دیتا برای ذخیره پسورد استفاده می شه، نیازی نیست محدودیتی برای طول پسورد بذاریم. البته برای جلوگیری از فشار روی سرور و ذخیره اطلاعات بیش از حد می شه محدودیت ۱۲۸ یا ۲۵۶ کاراکتری گذاشت دیگه چیزی به عنوان محدودیت ۱۶ کاراکتری برای پسورد وجود نداره.
برای ورود کاربرها هم کافیه پسورد وارد شده رو هش کنیم و با چیزی که در دیتابیس ذخیره شده، مقایسه کنیم و اگر مشابه بود، اجازه ورود بدیم.
اما روش سوم هم مشکل داره. همونطور که در تصویر می بینید چارلی و داک همچنان هش های ذخیره شده مشابهی دارند که در واقع داریم لو می دیم که پسوردهاشون مشابهه. در واقع کلمه password همیشه نتیجه هش یکسانی برابر با ۵E884898DA28..EF721D1542D8 داره و فرقی وجود نداره. این مشکل به هکرها اجازه می ده که با تولید جدولی از پسوردهای پرکاربر (و یا حتی تولید همه پسوردهای ممکن تا یک طول خاص) و هش کردن اونها، پسورد کاربرها رو با یک جستجو و مقایسه ساده، پیدا کنند.
روش چهارم – هش کردن و استفاده از salt
می تونیم با اضافه کردن یه سری اطلاعات به پسورد، نتایج بهتری از هش به دست بیاریم. این اطلاعات اضافه شده رو salt یا نمک می گن چونکه در واقع داریم به هش های تولید شده یه چاشنی اضافه می کنیم. سالت رو اصطلاحا nonce هم می گن که خلاصه شده number used once یا “عددی که یک بار استفاده می شه” است. به زبان ساده ما یک مجموعه کاراکتر رندوم تولید می کنیم که قبل از هش کردن به پسورد اضافه می شه.
سالت چیز خاصی نیست و می شه کنار نام کاربری ذخیره بشه. در واقع هدف استفاده از این متغیر رندوم، ایجاد تفاوت در نتیجه هش در پسوردهای مشابه است. برای کاهش احتمال ایجاد سالت مشابه در دو پسورد مشابه، ۱۶ بایت رو به این متغیر اختصاص می دیم که احتمال تکرار رو به اندازه کافی کاهش می ده.
حالا دیتابیس ما چیزی شبیه تصویر زیر می شه (بخشی از متغیرها برای جا شدن در تصویر، نشون داده نشده)
آخرین ستون در این جدول، پسوردهای هش شده است. با ترکیب سالت و پسورد و هش کردن این رشته به دست آمده با الگوریتم SHA-256، اطلاعات ذخیره شده به عنوان پسورد برای چارلی و داک کاملا متفاوته.
مطمئن بشید که سالت های رندومی رو انتخاب می کنید و هرگز از شماره گذاری پیاپی مثل ۰۰۰۰۰۱، ۰۰۰۰۰۲ و مانند آن استفاده نکنید. همچنین از توابع تولید عدد رندوم ضعیفی مثل دستور random در زبان C استفاده نکنید. اگر اینکار رو بکنید سالت های تولید شده می تونه شبیه دیتابیس های دیگه باشه و یا قابل پیش بینی باشه.
با استفاده از توابع رندوم مناسب برای تولید سالت (برای مثال CryptoAPI در ویندوز و /dev/urandom در سیستم های یونیکس) تضمین می کنید که سالت یکه استفاده می کنید و واقعا عددی است که یک بار استفاده می شه.
تموم شد؟ تقریبا، اما هنوز یکم مونده.
ما همه الزاماتی که شمردیم رو برآورده کردیم اما الگوریتم هش کردن به اندازه کافی سریع است که با سیستم های قوی، بشه با روش محاسبه و مقایسه، پسوردهای اصلی رو پیدا کرد.
با مبلغی کمتر از ۲۰ هزار دلار می شه سیستمی رو با ترکیب کارت های گرافیک ایجاد کرد که در هر ثانیه حدود ۱۰۰ میلیارد پسورد هش شده با الگوریتم SHA-256 تولید کنه.
ما باید به اندازه کافی الگوریتم رو زمان بر بکنیم که بشه جلوی شکسته شدن اونها رو گرفت.
روش پنجم – توسعه هش
هش کردن به این معنیه که برای شکستن کد پسوردها، نمی شه رو به عقب رفت اما می شه با روش رو به جلو و امتحان چندین و چندباره لیست پسوردهای احتمالی، به صورت شانسی به پسورد اصلی رسید. یعنی اگر خرابکارها دیتابیس شما رو به دست بیارن و بتونن آفلاین روش کار کنن، چیزی غیر از توانایی CPU جلوی حدس زدن پسوردها رو نمی گیره. اونها می تونن تک تک پسوردهای احتمای از AA..AA تا ZZ..ZZ رو با هر سالت موجود در دیتابیس شما تست بکنن و با مقایسه نتیجه هش، به پسورد کاربر برسن.
با توجه به اینکه لیست های پسورد احتمالی و یا الگوریتم های تولید پپسوردهای احتمالی به اندازه کافی توانایی دارند تا پسوردهای پرکاربرد رو در اولویت بذارن، پس پسورد کاربرهایی که انتخاب های دم دستی و ضعیف داشته اند، زودتر به دست میاد.
توجه کنید که حتی با امکان تولید یک میلیارد پسورد در ثانیه، یک پسورد ۱۲ کاراکتری که خوب انتخاب شده باشه به اندازه کافی قوی و غیرقابل حدس می شه. چرا که احتمال پسورد ۱۲ کراکتری شامل حروف کوچک و بزرگ و عدد، یک هزار میلیون میلیارده 🙂
بنابراین بهتره که الگوریتم هش کردن رو به اندازه کافی کند کنیم که برای مثال شامل هزاربار اجرای تکراری هش باشه. این قضیه، اونقدر زمان ورود رو کند نمی کنه که کاربر اذیت بشه یا حتی متوجه بشه اما زمان کرک آفلاین پسورد رو به همون اندازه تکرار هش، طولانی تر می کنه.
با اینحال هرگز خودتون الگوریتمی برای تکرار هش اختراع نکنید. می تونید یکی از این الگوریتم ها رو انتخاب کنید: PBKDF2، bcrypt و یا scrypt
من PBKDF2 رو پیشنهاد می کنم چرا که بر اساس اصول اولیه هش کردن و استانداردهای ملی و بین المللی رو رعایت کرده.
پیشنهاد من استفاده از PBKDF2 با الگوریتم HMAC-SHA-256 و ۱۰ هزار بار تکراره.
HMAC-SHA-256 یک روش خاص از پیاده سازی الگوریتم SHA-256 است که اجازه می ده هش با سالت ترکیب بشه:
- ایجاد یک رشته رندم مثل K و چرخوندن یه سری بیت ها و ایجاد K1
- هش کردن مجموعه پسورد و سالت با الگوریتم SHA-256 و تولید H1
- تغییر بیت های دیگری در K و ایجاد K2
- محاسبه پسورد هش شده نهایی یا H2 با هش کردن ترکیب H1 و K2
به زبان ساده شما پسورد رو یه بار با یه سالت، هش می کنید و سپس نتیجه رو با نسخه تغییر کرده سالت، دوباره هش می کنید. در PBKDF2 ما یک بار پسورد و سالت رو به الگوریتم HMAC-SHA-256 می دیم و یک دور از ۱۰ هزار دور انجام می شه. سپس خروجی این دور اول رو با پسورد مجددا به الگوریتم HMAC-SHA-256 می دیم و یک دور دیگه اون رو اجرا می کنیم و اینجوری ۹۹۹۹ دور باقیمونده رو انجام میدیم.
حالا ما باید هش، سالت و تعداد دورها رو در دیتابیس ذخیره کنیم
با قدرتمند شدن سیستم های مورد استفاده هکرها، می شه تعداد دورها رو زیادتر کرد، برای مثال هر سال دوبرابرش کرد. هر وقت یه یوزر با روش قدیمی لاگین می کنه، پسوردش دوباره با تعداد دورهای جدید هش بشه و در دیتابیس جایگزین بشه (چراکه فقط در یک لاگین موفقه که شما می دونید پسورد اولیه کاربر چی بوده). می تونید اکانت کاربرهایی که زمان زیادی لاگین نمی کنن رو غیرفعال کنید و مجبورشون کنید که از روش reset کردن پسورد استفاده کنن.
در پایان حداقال های پیشنهادی برای نگهداری امن از پسورد کاربرها رو می گم:
- از یک تابع قوی تولید عدد رندوم برای تولید یک سالت ۱۶ بایتی یا بیشتر استفاده کنید.
- پسورد و سالت رو به الگوریتم PBKDF2 بدید.
- از HMAC-SHA-256 به عنوان هسته هش کردن درون الگوریتم PBKDF2 استفاده کنید.
- از ۱۰ هزار دور یا بیشتر استفاده کنید (در ابتدای سال ۲۰۱۴).
- پسورد هش شده ۳۲ بایتی (۲۵۶ بیت) خروجی الگوریتم PBKDF2 خواهد بود.
- پسورد هش شده، تعداد دورها و سالت رو در دیتابیس ذخیره کنید.
- برای مقابله با افزایش قدرت پردازنده ها، به صورت دوره ای تعداد دوره های هش رو افزایش بدید.
هرکاری می کنید سعی نکنید برای خودتون روش نگهداری پسورد خلق کنید. برای ادوب نتیجه خوبی نداشت، برای شما هم نخواهد داشت.