PDA

View Full Version : bcrypt hash - یک الگوریتم هش که کرکرها را بیچاره میکند!



Hooman.Prog
یک شنبه 01 آبان 1390, 22:22 عصر
اخیرا دارم روی کامل کردن امکانات و امنیت یکی از پروژه های تمرینی و قدیمی خودم که یک سیستم رجیستر و لاگین بود کار میکنم.
اول رفتم سروقت الگوریتم هش کردن پسوردهاش، چون هم از 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 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx yz0123456789';

$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$QdNt6GbsP1aWp9Jkhex4qepFumQlmtEXnfeWqU4rN7y mQygXZmtzy

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

<?php

include 'class_bcrypt.php';

$bcrypt = new Bcrypt();

$hash = '$2a$12$QdNt6GbsP1aWp9Jkhex4qepFumQlmtEXnfeWqU4rN7 ymQygXZmtzy';

$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 خودش کاملا کافی هست.

Hooman.Prog
یک شنبه 01 آبان 1390, 22:39 عصر
راستی نرم افزار فروم ممکنه کدها رو خراب کنه. مثلا در بین کاراکترهای هش فاصله دیده میشه (فاصله رو حذف کنید).

tux-world
یک شنبه 01 آبان 1390, 23:04 عصر
خیلی جالبه. دستتون درد نکنه. ضریب امنیتیش چطوره؟ از نوع سالت بهتره؟

Hooman.Prog
یک شنبه 01 آبان 1390, 23:27 عصر
مثل اینکه خوب نخوندی. اینم سالت داره. سالت یه چیزی هست که در هر سیستم هش پسورد کاربران باید استفاده کرد.

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

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

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

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

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

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

Hooman.Prog
دوشنبه 02 آبان 1390, 15:42 عصر
بنظرم بهتره/باید برای اصولی تر بودن مطلب، چندتا از منابع استفاده شده رو هم بذارم:
http://en.wikipedia.org/wiki/Bcrypt
http://stackoverflow.com/questions/4795385/how-do-you-use-bcrypt-for-hashing-passwords-in-php
http://chargen.matasano.com/chargen/2007/9/7/enough-with-the-rainbow-tables-what-you-need-to-know-about-s.html

idocsidocs
دوشنبه 02 آبان 1390, 16:00 عصر
تمام خصوصیات این الگوریتم طوری انتخاب شدن که هیچوقت نشه اون رو با سرعت بالا اجرا کرد. یعنی حتی در آینده با پیشرفت قدرت سخت افزار و همچنین احتمال ایجاد تئوریها و الگوریتم های جدید در این زمینه.شما همه چیز رو گفتید ولی نگفتید که این الگوریتم چقدر کنده؟
این الگوریتم برای هش کردن یه کلمه رمز 10 کاراکتری چه مدت زمان نیاز داره؟

Hooman.Prog
دوشنبه 02 آبان 1390, 18:23 عصر
این الگوریتم برای هش کردن یه کلمه رمز 10 کاراکتری چه مدت زمان نیاز داره؟ بستگی به پارامتری دوری که بهش میدی داره و همچنین سرعت سیستمی که برنامه روش اجرا میشه.
من با این کد روی سیستم خودم تست کردم:

<?php

include 'class_bcrypt.php';

$bcrypt = new Bcrypt();

echo $t1=microtime(true);
echo '<br>', $bcrypt->hash('0123456789');
echo '<br>', $t2=microtime(true);
echo '<hr>', ($t2-$t1);

?>
اجرای الگوریتم هش در این کد حدود 0.5 ثانیه طول کشید.
bcrypt رو با md5 جایگزین کردم و تقریبا 0.000025 ثانیه طول کشید.
یعنی سرعت اجرای md5 تقریبا 20 هزار برابر bcrypt است!
البته کد بالا با مقدار 12 (پیشفرض) بعنوان پارامتری که برای محاسبهء round ها استفاده میشه هست.
این عدد رو بطور مثال 1 تعیین کردم و اختلاف سرعت به 15 برابر رسید. اما واضحه که نباید چنین عددی داد و برای امنیت باید عدد بزرگتری باشه.
با عدد 15 بعنوان پارامتر، 3.5 ثانیه طول کشید.
با عدد 17 بعنوان پارامتر، 15 ثانیه طول کشید.
خلاصه سرعتش قابل تنظیمه.
من فکر میکنم باید روی یک عددی بذاریم که روی سرورهای معمولی درحدود 0.5 تا 1 ثانیه طول بکشه. چون این زمان حداکثر زمانی هست که در تقریبا تمام موارد، قابل توجه و آزاردهنده نیست. البته شاید از نظر دیگران بشه زمان بیشتری هم تعیین کرد؛ نمیدونم! بنظرت چقدر قابل قبوله؟
خلاصه باید روی بیشترین زمانی تنظیم کنی که قابل قبول باشه. اینطوری برای شما مشکل و هزینهء خاصی نداره، ولی سرعت یک کرکر وقتی 20 هزار برابر کمتر بشه براش یه فاجعه هست. یعنی قبلا میتونست مثلا با md5 تعداد خیلی زیادی ترکیب پسورد رو تست کنه (چه از نوع دیکشنری چه brute-force ساده چه حمله های ترکیبی) اما الان حداکثر تعدادی که تست کردنش براش عملی هست یا صرف میکنه، به یک بیست هزارم تعداد سابق رسیده.

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

idocsidocs
دوشنبه 02 آبان 1390, 18:41 عصر
بستگی به پارامتری دوری که بهش میدی داره و همچنین سرعت سیستمی که برنامه روش اجرا میشه.
خب اینطوری که کرکر هم می تونه تنظیمات این الگوریتم رو طوری قرار بده که با بالاترین سرعت ممکن کار کنه.

این یه ضعف نیست؟

Hooman.Prog
دوشنبه 02 آبان 1390, 18:51 عصر
اشتباه نکن!
کرکر مجبوره برای کرک هر هش، مقدار پارامتر رو برابر اون مقداری قرار بده که اون هش باهاش تولید شده.
اگر شما با پارامتر 12 هش رو تولید کرده باشی، کرکر هم مجبوره با پارامتر 12 کار کنه.