احراز هویت مانند نگهبان دم در اپلیکیشن شماست. هر بار که کسی ثبت‌نام می‌کند یا وارد حساب خود می‌شود، سیستم شما باید تصمیم بگیرد که آیا او واقعاً همان کسی است که ادعا می‌کند یا خیر. اگر اشتباه کنید، فقط با یک خطای ورود (login) روبرو نیستید؛ بلکه در حال به خطر انداختن داده‌های واقعی کاربران هستید که مستقیماً از در خارج می‌شوند. انجام صحیح این کار مستلزم دو ابزار قابل اعتماد است: Bcrypt برای محافظت از رمزهای عبور ذخیره‌شده (at rest)، و JSON Web Tokens برای تأیید هویت در حین پیمایش کاربر در اپلیکیشن.

چرا ذخیره‌سازی به صورت متن ساده (Plaintext) شکست می‌خورد

اگر قرار است فقط یک نکته از این مقاله به یاد بسپارید، این است: هرگز رمزهای عبور را به صورت متن ساده در پایگاه داده خود ذخیره نکنید. فرقی نمی‌کند که پایگاه داده شما پشت یک فایروال باشد یا به تک‌تک مهندسان تیم اعتماد داشته باشید. نوشتن رمزهای عبور در جداول به شکل خام، مانند گذاشتن کلید خانه زیر پادری است. لحظه‌ای که کسی از طریق یک API پیکربندی‌شده‌ی اشتباه، یک نسخه پشتیبان لو رفته یا یک حمله تزریق (injection attack)، به آن پایگاه داده دسترسی پیدا کند، تمام اطلاعات کاربری فوراً فاش می‌شود.

آسیب زمانی چندین برابر می‌شود که افراد از رمزهای عبور تکراری استفاده می‌کنند. یک نفوذ واحد می‌تواند نه تنها اپلیکیشن شما، بلکه ایمیل، حساب‌های بانکی و شبکه‌های اجتماعی کاربر را نیز به خطر بیندازد. به همین دلیل است که ما رمزهای عبور را هش (hash) می‌کنیم. هش کردن، رمز عبور را به یک رشته به‌هم‌ریخته تبدیل می‌کند که هیچ شباهت آشکاری به نسخه اصلی ندارد. این فرآیند یک‌طرفه و برگشت‌ناپذیر است. شما نمی‌توانید یک «اسید ریاضی» روی یک هش بریزید تا آن را دوباره به رمز عبوری که ساخته بود تبدیل کنید.

هش کردن رمزهای عبور با Bcrypt

Bcrypt یک تابع هش است که مخصوص رمزهای عبور ساخته شده است. این تابع رشته ساده را می‌گیرد، آن را از الگوریتم Blowfish عبور می‌دهد و نتیجه‌ای شبیه به این برمی‌گرداند: $2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy. آن پیشوند، الگوریتم و فاکتور هزینه (cost factor) را به شما می‌گوید. بخش انتهایی طولانی نیز ترکیبی از salt و خودِ هش است.

Salt داده‌ای تصادفی است که قبل از شروع فرآیند هش کردن، با رمز عبور ترکیب می‌شود. از آنجایی که salt برای هر کاربر منحصربه‌فرد است، دو نفر که هر دو "password123" را انتخاب کنند، در نهایت هش‌های کاملاً متفاوتی در پایگاه داده شما خواهند داشت. این تفاوت کوچک، «جدول‌های رنگین‌کمانی» (rainbow tables) را — که دیکشنری‌های از پیش محاسبه‌شده از هش‌های رمزهای عبور رایج هستند — بی‌اثر می‌کند؛ زیرا مهاجمان باید برای هر salt منحصربه‌فرد، کل جدول را دوباره تولید کنند.

Bcrypt همچنین به عمد کند طراحی شده است. سخت‌افزارهای مدرن می‌توانند با الگوریتم‌های سریع مانند SHA-256، میلیاردها هش را در ثانیه حدس بزنند، اما Bcrypt سرعت خود را پایین نگه می‌دارد. این تابع فرآیند هش کردن را چندین بار اجرا می‌کند که توسط یک فاکتور هزینه کنترل می‌شود؛ فاکتوری که می‌توانید با سریع‌تر شدن کامپیوترها، آن را افزایش دهید. این کندی، هر کسی را که سعی دارد با روش حمله brute-force به یک پایگاه داده سرقت‌شده نفوذ کند، مجازات می‌کند. یک کاربر قانونی که هنگام ورود ۲۰۰ میلی‌ثانیه بیشتر منتظر می‌ماند، متوجه نخواهد شد، اما مهاجمی که سعی دارد میلیون‌ها حدس را آزمایش کند، قطعاً متوجه خواهد شد.

نحوه عملکرد JSON Web Tokens

در حالی که Bcrypt مسئول در ورودی است، JWT مسئول اجازه عبور در راهروهاست. یک JSON Web Token یک رشته فشرده و امن برای URL است که ثابت می‌کند کاربر قبلاً احراز هویت شده است. آن را مانند یک کارت شناسایی دیجیتال تصور کنید که سرور صادر می‌کند و کلاینت آن را با خود حمل می‌کند.

یک JWT شامل سه بخش است که با نقطه از هم جدا شده‌اند: header، payload و signature. بخش header نوع توکن و الگوریتم امضا را مشخص می‌کند. بخش payload حاوی ادعاها (claims) است — یعنی اطلاعاتی درباره کاربر و خودِ توکن — مانند شناسه کاربر، نام کاربری و زمان انقضا. بخش signature همان مهر رمزنگاری شده است. سرور آن را با کدگذاری header و payload و سپس اعمال یک کلید مخفی (secret key) بر آن‌ها ایجاد می‌کند. اگر کسی در payload دستکاری کند، signature دیگر مطابقت نخواهد داشت و سرور توکن را مستقیماً رد می‌کند.