bcrypt – یک الگوریتم هش که کرکرها را بیچاره میکند!

نکته: توضیحات پایانی این پست رو هم حتما بخونید (استفاده از کد معرفی شده در این پست، توسط بنده توصیه نمیشود).

اخیرا دارم روی کامل کردن امکانات و امنیت یکی از پروژه های تمرینی و قدیمی خودم که یک سیستم رجیستر و لاگین بود کار میکنم.
اول رفتم سروقت الگوریتم هش کردن پسوردهاش، چون هم از Salt استفاده نمیکنه و هم از الگوریتم md5 برای تولید هش استفاده میکنه که احتمالا میدونید که md5 از دیدگاه متخصصان رمزنگاری و امنیت مدتهاست شکسته شده بحساب میاد، اما هنوز عدهء زیادی بخصوص در PHP و بخصوص برای هش کردن پسورد کاربران ازش استفاده میکنن. ضمنا الگوریتم sha1 هم تقریبا وضعیت مشابه md5 رو داره! البته اینطور نیست که بشه این الگوریتم ها رو به این راحتی در بیشتر موارد شکست داد، ولی چون امنیت مسئلهء واقعا مهم و پیچیده ای است، باید به حرف متخصصان امنیت گوش کرد و استفاده از این الگوریتم ها رو در هر کاربردی کنار گذاشت.

خلاصه در نت جستجو کردم دنبال اینکه از چه الگوریتم و روش هشی برای پسورد کاربران استفاده کنم. خودم در فکر استفاده از sha256 یا sha512 بودم، اما به مطالب جالبی در نت برخوردم و به برنامه/الگوریتمی بنام bcrypt.

این الگوریتم توسط دو نفر بنامهای Niels Provos و David Mazières اختراع و در سال 1999 در USENIX معرفی شد.

بطور خلاصه میتونم بگم این الگوریتم مخصوص هش کردن پسورد کاربران ساخته شده و عمدا طوری طراحی شده که سنگین و کند باشه!

برای ساخت این الگوریتم از نسخه ای تغییریافته از الگوریتم رمزنگاری متقارن Blowfish بنام Eksblowfish استفاده شده.
نکته: با اینکه الگوریتم های هش الگوریتم هایی یک طرفه (بازگشت‌ناپذیر) هستند اما میشه با روشهای مناسب، از الگوریتم های رمزنگاری بازگشت‌پذیر برای ساخت اونها استفاده کرد.

الگوریتم blowfish بخاطر روش تولید کلیدی که داره الگوریتمی سنگین و کند است، و نسخهء تغییر یافته از این الگوریتم بنام Eksblowfish که توسط Niels Provos و David Mazières طراحی شد عمدا حتی از blowfish هم سنگین تر و کندتره و در ضمن تعداد دورهای داخلی اون قابل تنظیم هست و بنابراین کندی اون میتونه با افزایش قدرت سخت افزارها یا نیاز به امنیت بالاتر تطبیق داده بشه.

این سنگین بودن و کندی باعث میشه کرکرها در کرک کردن پسوردهایی که با این روش هش شدن به مشکل بزرگی برخورد کنن که زندگی رو واقعا براشون سخت میکنه!

الگوریتم های دیگری مثل md5 و حتی sha256 از bcrypt بسیار بسیار سریعتر هستن و درواقع عمدا طوری طراحی شدن تا سرعت بالایی داشته باشن و نیز بتونن روی سخت افزارهای مخصوص انجام عملیات رمزنگاری براحتی و با سرعت بالا پیاده سازی و اجرا بشن؛ این خواص درمورد خیلی کاربردهای رمزنگاری مطلوب هستن، اما وقتی از این الگوریتم ها برای هش کردن پسورد کاربران استفاده میشه، عمدتا چون بسیاری از کاربران از پسوردهای ضعیفی استفاده میکنن، ویژگیهای نامطلوبی محسوب میشن که فقط به نفع کرکرها عمل میکنن.

و bcrypt نقطهء مقابل این ویژگیهاست! bcrypt باعث میشه کار کرک کردن حتی پسوردهای ضعیف خیلی مشکل تر بشه و پسوردهایی که کمی قوی تر باشن کم و بیش غیرقابل کرک شدن خواهند بود (مگر اینکه کرکر بتونه چند حدس معدود رو با موفقیت تست کنه – اما این دیگه brute-force نیست).
موضوع خیلی ساده است! برای کرک کردن هش های bcrypt نیاز به توان پردازشی بسیار بسیار بیشتری نسبت به هش های دیگر است. بنابراین کرک کردن (به روش brute-force) پسوردهای خیلی ضعیف تری در این روش غیرممکن خواهد شد که در روشهای دیگر قابل کرک هستند.

ضمنا توجه داشته باشید که این روش هم مثل بقیهء روشها از Salt استفاده میکنه. Salt یک روش ضروری برای امن کردن تمام هش ها محسوب میشه. اما یک خوبی دیگر این برنامه اینه که خودش Salt رو بصورت خودکار تولید میکنه و Salt در رشتهء خروجی گنجانده میشه؛ در نتیجه شما نیازی نیست به تولید و ذخیرهء جداگانهء Salt بپردازید؛ این برنامه تمام این کارها رو خودش انجام میده. شما فقط کافیه رشتهء خروجی از اون رو که شامل هش و Salt و یکسری مشخصات دیگه هست در دیتابیس ذخیره کنید.

توجه کنید که محاسبهء هش bcrypt برای ثبت نام یا لاگین کردن کاربران، حداقل با ترافیک های معمولی سرورها برای اینطور اعمال، فشار زیادی به سرور نمیاره، اما چون یک کرکر برای کرک موثر نیاز به محاسبهء هش های زیادی در زمان کوتاه داره، این الگوریتم پدرش رو درمیاره!!

خب اینم یک کلاس که الگوریتم هش bcrypt رو در PHP براحتی در اختیارمون میذاره:

<?php

class Bcrypt {
private $rounds;
public function __construct($rounds = 12) {
if(CRYPT_BLOWFISH != 1) {
throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
}

$this->rounds = $rounds;
}

public function hash($input) {
$hash = crypt($input, $this->getSalt());

if(strlen($hash) > 13)
return $hash;

return false;
}

public function verify($input, $existingHash) {
$hash = crypt($input, $existingHash);

return $hash === $existingHash;
}

private function getSalt() {
$salt = sprintf('$2a$%02d$', $this->rounds);

$bytes = $this->getRandomBytes(16);

$salt .= $this->encodeBytes($bytes);

return $salt;
}

private $randomState;
private function getRandomBytes($count) {
$bytes = '';

if(function_exists('openssl_random_pseudo_bytes') &&
(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
$bytes = openssl_random_pseudo_bytes($count);
}

if($bytes === '' && is_readable('/dev/urandom') &&
($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
$bytes = fread($hRand, $count);
fclose($hRand);
}

if(strlen($bytes) < $count) {
$bytes = '';

if($this->randomState === null) {
$this->randomState = microtime();
if(function_exists('getmypid')) {
$this->randomState .= getmypid();
}
}

for($i = 0; $i < $count; $i += 16) {
$this->randomState = md5(microtime() . $this->randomState);

if (PHP_VERSION >= '5') {
$bytes .= md5($this->randomState, true);
} else {
$bytes .= pack('H*', md5($this->randomState));
}
}

$bytes = substr($bytes, 0, $count);
}

return $bytes;
}

private function encodeBytes($input) {
// The following is code from the PHP Password Hashing Framework
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

$output = '';
$i = 0;
do {
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16) {
$output .= $itoa64[$c1];
break;
}

$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;

$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);

return $output;
}
}

?>

نکته: ظاهرا وجود ثابت CRYPT_BLOWFISH که در این کد چک میشه، به این معناست که تابع crypt الگوریتم Eksblowfish رو ساپورت میکنه و بنابراین میتونه برای هش کردن ازش استفاده کنه (تاجاییکه میدونم تابع crypt میتونه از الگوریتم های مختلفی برای تولید هش ها استفاده کنه).

روش استفاده رو در زیر نشون میدیم.
هش کردن:

<?php

include 'class_bcrypt.php';

$bcrypt = new Bcrypt();

$hash = $bcrypt->hash('password');

echo $hash;

?>

برنامهء بالا هشی مشابه این رو تولید میکنه (گفتم بذارم تا بدون تست کردن بتونید فرمتش رو بررسی کنید):

$2a$12$QdNt6GbsP1aWp9Jkhex4qepFumQlmtEXnfeWqU4rN7ymQygXZmtzy

و روش Verify کردن هش درمقابل پسورد وارد شده:

<?php

include 'class_bcrypt.php';

$bcrypt = new Bcrypt();

$hash = '$2a$12$QdNt6GbsP1aWp9Jkhex4qepFumQlmtEXnfeWqU4rN7ymQygXZmtzy';

$isGood = $bcrypt->verify('password', $hash);

if($isGood) echo 'Password correct.';
else echo 'Password incorrect!';

?>

موقع ایجاد یک نمونه از کلاس Bcrypt میتونید تعداد round ها یا دورهای داخلی الگوریتم رو تعیین کنید. این تعداد بصورت پیشفرض روی 12 هست، ولی بطور مثال در کد زیر اون رو 15 تعیین میکنیم:

$bcrypt = new Bcrypt(15);

البته جای اشاره داره که تعداد round های داخلی در الگوریتم در اصل از محاسبهء مقدار 2 به توان عددی که شما مشخص میکند حاصل میشه، نه اینکه برابر با خود این عدد باشه.

کند بودن این الگوریتم موقع اجرا در مرورگر کاملا مشهود است. اگر مشخص نبود میتونید تعداد round رو زیاد کنید تا ببینید چقدر کنده! البته فکر نمیکنم نیازی باشه کندی اون اینقدر زیاد باشه که یک تاخیر مثلا چند ثانیه ای در مرورگر دیده بشه. احتمالا همون مقدار 12 خودش کاملا کافی هست.

—————————

توجه کنید که  این الگوریتم هم سالت داره (سالت در بخشی از رشتهء خروجی گنجانده شده). سالت یه چیزی هست که در هر سیستم هش پسورد کاربران باید استفاده کرد.

این الگوریتم تفاوت و مزیتش در سالت نیست، در کند بودن الگوریتم هش اصلی اونه. الگوریتمش طوری هست که خیلی کنده و ضمنا میشه کندی اون رو تنظیم کرد. مثلا 10 سال دیگه اگر سخت افزارها خیلی قویتر شدن میشه براحتی اون رو تنظیم کرد که بازم بقدر کافی مقاوم باشه.

یه مسائل ظریفی در این ارتباط وجود داره.
مثلا بخشهای مختلف بعضی الگوریتم ها رو میشه بصورت موازی اجرا کرد و بنابراین میشه عمل کرک یک هش خاص رو بصورت همزمان روی چند CPU انجام داد. ولی این الگوریتم فکر میکنم این قابلیت رو نداره (و بنابراین امن تره).
برای اجرای بعضی الگوریتم ها ممکنه روشهای خیلی سریعتری در آینده پیدا بشه، ولی برای چنین الگوریتم هایی این احتمال خیلی کمتره.

تمام خصوصیات این الگوریتم طوری انتخاب شدن که هیچوقت نشه اون رو با سرعت بالا اجرا کرد. یعنی حتی در آینده با پیشرفت قدرت سخت افزار و همچنین احتمال ایجاد تئوریها و الگوریتم های جدید در این زمینه.
البته در امنیت نمیشه هیچ چیزی رو تضمین کرد، ولی بهرحال روشها از نظر اطمینان و احتمال شکست در آینده با هم تفاوت دارن.

ضمنا توجه داشته باشید که هدف و کاربرد این الگوریتم برای هش کردن پسورد کاربران هست و نه چیز دیگه. چون خیلی کنده!

در رمزنگاری در خیلی کاربردها از الگوریتم های هش استفاده میشه. در خیلی از اون کاربردها، کند بودن الگوریتم هش مزیت نیست و بلکه یه عیب و مشکل هست. مثلا وقتی هش یک فایل رو محاسبه میکنیم معمولا هدف حملهء کرک هش قرار نداره، چون در خیلی کاربردها دلیل/اهمیتی نداره که کرکر بخواد از هش به محتویات فایل (یا دادهء مشابه دیگری) برسه یا این عمل اصلا با توجه به حجم زیاد دیتا و آنتروپی بالای اون غیرممکن است.
یا مثلا وقتی پسورد یا کلید استفاده شده، به هردلیلی منجمله تولید خودکار بقدر کافی قوی باشه، استفاده از یک الگوریتم هش سنگین و کند مزیت خاصی نداره چون بهرحال کرکر قادر به کرک کردن هش حاصل از پسوردهای قوی نخواهد بود.

البته فراموش نکنید استفاده از md5 و sha1 رو باید بطور کلی کنار گذاشت، چون این الگوریتم ها از نظر متخصصان، دیگه خصوصیاتی که یک الگوریتم هش با استحکام لازم در زمینهء رمزنگاری باید داشته باشه رو ندارن.

—————————-

بنظرم بهتره/باید برای اصولی تر بودن مطلب، چندتا از منابع استفاده شده رو هم بذارم:
http://en.wikipedia.org/wiki/Bcrypt
http://stackoverflow.com/questions/4…sswords-in-php
http://chargen.matasano.com/chargen/…w-about-s.html

—————————-

ضمنا نباید فکر کنیم استفاده از این الگوریتم به معنای اینه که خیالمون راحت بشه و مثلا هر پسورد زپرتی ای هم که انتخاب بشه مشکلی نیست! یا نباید فکر کنیم این الگوریتم میتونه کاملا جای روشهای دیگر مثل سیستم قفل کردن اکانت درصورت چندبار وارد شدن اشتباه پسورد رو بگیره.
این الگوریتم فقط یه محکم کاری بیشتر هست و حمایت بیشتر از امنیت پسورد کاربران سایت که بهرصورت هرکاری بکنی بازم پسوردهای ضعیف زیادی رو استفاده میکنن؛ چون انسان در بخاطر سپردن پسوردها مشکل داره و چون بعضیا ناشی، ناآگاه، یا سهل انگار هستن. ما فقط ضریب امنیت رو تاجاییکه میتونیم بالا میبریم تا موارد فاجعه‌بار هک و کرک کمتر بشن (نه اینکه به این آسونی تعدادشون نزدیک به صفر برسه).
اینکه توصیه میشه از پسوردهای قوی استفاده کنیم کماکان فرقی نمیکنه.
مسلمه که اگر یکی پسوردش رو بذاره love007، این الگوریتم نمیتونه کاری براش بکنه. اما دیگه برای کرکر در خیلی موارد صرف نمیکنه هزاران عدد رو بعد از love و هزاران کلمه دیگه بذاره و تست کنه. و دیگه نمیتونه پسورد کلی از کاربران یک دیتابیس دزدیده شده رو پیدا کنه.
بهرصورت love2456 هم پسورد خوبی نیست. چون به احتمال زیاد اون عدد شماره شناسنامه ای چیزی هست و یکی که از مشخصات طرف اطلاع داشته باشه میتونه اون رو حدس بزنه و با تعداد خیلی خیلی کمتری تست به نتیجه برسه. حتی اگر 2456 یک عدد تصادفی باشه بازم پسورد قوی ای نیست، چون پسورد باید حداقل 6 یا 8 کاراکتر رندوم یا نیمه رندوم داشته باشه.

—————————–

بعنوان توضیحات پایانی عرض کنم که میتونید یک سالت ثابت (بعضی جاها به اسم pepper معروف شده) رو هم همراه هر پسورد به ورودی این الگوریتم اضافه کنید تا روش هش شما کاملتر و امن تر باشه. کاربرد سالت ثابت اینه که اگر کرکر به هش پسوردها دسترسی پیدا کرده باشه ولی هنوز نتونسته کدمنبع برنامه رو بخونه (در نتیجه نتونسته مقدار سالت ثابت سایت شما رو بدست بیاره)، براش غیرممکنه بتونه از هش ها به پسوردهای اولیه برسه.

مورد دیگر اینکه من روی یکی از هاستها با مشکل ساپورت CRYPT_BLOWFISH برخورد کردم و بخاطر همین ترجیح دادم از این الگوریتم استفاده نکنم و الگوریتم خودم رو نوشتم که در پست دیگری تحت عنوان «چگونه پسوردهای کاربران را به نحو امنی هش کنیم» خدمتتون معرفی کردم. در اون تابع هم از Key stretching و سالت ثابت و سالت رندوم استفاده کردم، بعلاوهء یک تابع رندوم امن که ضمیمهء اونه. درواقع بنظر بنده اون تابع رندوم از تابع رندومی که در کدهای کلاس Bcrypt درج شده در اینجا دیده میشه کاملتر و امن تره! (بعدا با تحقیقات و کار بیشتری تهیه شده و چیزهای خوبی بهش اضافه شده) ولی بهرصورت درحال حاضر وقت و اولویتش رو ندارم که تابع رندوم این کلاس رو تعویض کنم. مطالب این پست درمورد الگوریتم bcrypt فعلا محض اطلاع از داستان هش bcrypt درج شده و بنده شخصا استفاده از کد درج شده در این پست رو توصیه نمیکنم (کدهاش هم از خودم نیست). الگوریتم bcrypt خودش الگوریتم قوی ای هست، اما در مسائل رمزنگاری فقط یک بخش مهم نیست، بلکه تمام بخشهای دیگر هم باید قوی باشن؛ بقول معروف میگن امنیت مثل یک زنجیر است که استحکام اون به اندازهء استحکام ضعیف ترین حلقهء اونه. در اینجا هم تابع رندوم ممکنه حلقهء ضعیف زنجیر باشه و به حد کافی قوی نباشه. ضمنا سالت ثابت رو هم گفتم که برای امنیت بیشتر اضافه کنید (در این کدها استفاده نشده).

2 دیدگاه در “bcrypt – یک الگوریتم هش که کرکرها را بیچاره میکند!

  1. “خودش Salt رو بصورت خودکار تولید میکنه” خب اینکه نمیشه … اگه هر بار سالت تغییر کنه دوتا پسورد یکسان هش شده دیگه مثل هم نمیشن (یا من اشتباه متوجه شدم )
    و اینکه اگه یکی یه اسکریپت بنویسه که روی صفحه ی لاگین زود زود یوزر نیم و پسورد وارد کنه پدر سیستم خودمون در میاد
    ولی در کل نوشته ی جالبی بود
    در مورد md5 , shal1 هم اگه ترکیبی با هم و با یه سالت قوی اجرا بشن فک نکنم هیچ سیستمی قادر به بازگردانی باشه …

    • ببخشید این مطالب در پاسخ شما رو کاربری بنام محمد فرستاده بود که اشتباها کامنتش رو پاک کردم:
      اتفاقاً با ترکیب md5 و sha1 بازم با حملات بروت فرس میشه پسورد رو پیدا کرد حتی اگه از الگریتم های با تعداد کاراکتر هش بیشتر مثل sha512 هم فرقی ایجاد نمیکنه چون این الگریتم ها خیلی سریع هستند ولی bcrypt چون کند اجرا میشه باعث میشه خیلی دیرتر پسورد قابل پیدا کردن باشه مثلاً با یک سیستم خانگی متوسط احتمال پیدا کردن پسورد که با md5 هش شده ، به روش دیکشنری اتک یا بروت فرس هر 40 ثانیه یک بار است در حالی که اگه همین پسورد رو با bcrypt هش کنیم احتمال به سالی یک بار میرسه چون الگریتم کند اجرا میشه

      برای جلو گیری از فشار به سرور هم باید تعداد round های مناسب رو انتخاب کنید علاوه بر اون استفاده از کپتچا میتونه خیلی کمک کنه که ربات ها و یا اسکریپت های خودکار نتونند فرم لاگین رو پر کنند

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>