هش (Hash) کردن کلمات عبور در PHP

از روز اول که وبلاگم رو راه انداخته بودم ، همان طور که تو تیتر و تگش هم هست ، هدفم تا حدود زیادی نوشتن در مورد PHP بود ، ولی تقریبا می شه گفت به غیر از کارهایی که در مورد وردپرس انجام دادم و بلاگ کردم دیگه مطلبی در مورد PHP ننوشتم ، تصمیم گرفتم یه سری مقاله در مبحث “امنیت در برنامه نویسی با PHP” ترجمه کنم یا بنویسم تا هم ضعف هایی که تو این زمینه میون برنامه نویس های ایرانی وجود داره بر طرف بشه و هم کم کم وبلاگم هم رنگ و بوی PHP بگیره.
مقاله حاضر با عنوان Password Hashing توسط James McGlinn نوشته شده و اینجا و در سایت PHP Security Consortium منتشر شده. هش کردن کلمات عبور مبحث جالب و ضروری برای هر برنامه نویس برنامه های کابردی با PHP محسوب می شه.
سطح این مقاله مبتدی و متوسطه و آشنایی مختصری با MySQL هم نیاز داره.

در این مقاله سعی داریم مبحث به رمز درآوردن یک طرفه  کلمات عبور(Password Hashing) در PHP را مورد بحث قرار دهیم، موضوعی که عموما توسط بسیاری از نوآموزان PHP نادیده گرفته می شود.در بسیاری از نرم افزارهای (ضعیفی) که با PHP نگاشته می شوند اطلاعات اعضا (User Profiles) به صورت متن ساده (Plain Text) در بانک اطلاعاتی ذخیره می گردد.  هش کردن کلمات عبور راهی است برای یه رمز در آوردن(Encrypt)  کردن کلمات عبور قبل از ذخیره سازی آن ها در بانک اطلاعاتی تا هنگام دسترسی افراد غیر مجاز به بانک اطلاعاتی ، میزان خسارت کمتر گردد. هش کردن به هیچ وجه روش جدیدی به حساب نمی آید و از مدتها قبل در سیستم های مبتنی بر UNIX برای ذخیره سازی کلمات عبور استفاده می شده است. در این مقاله سعی خواهیم کرد تا با بیان مفاهیم هش ، راه های عملی استفاده از آن در PHPMySQL) را برای دخیره کلمات عبور در قالب مثال هایی تشریح کنیم.


پیش گفتار

هنگام مطالعه این مقاله مشاهده خواهید نمود که ما از روشی به نامه SHA-1(Security Hashing Algorithm 1) برای هش کردن استفاده خواهیم کرد. تا هنگام نگاشته شدن این مقاله گروهی از محققان - Xiaoyun Wang, Yiqun Lisa Yin, Hongbo Yu - نشان داده اند که SHA-1 ضعیف تر از آن چیزی است که به نظر می رسیده است، به این معنی که هم اکنون برای موارد خاص مانند امضاهای دیجیتالی ، الگوریتم های قوی تری مانند SHA-256 و SHA-512 توصیه می گردد ولی برای هش کردن کلمات عبوردر برنامه های امروزی SHA-1 هنوز سطح امنیت بسیار خوبی را فراهم می کند.

برای اطلاعات بیشتر لطفا تحلیل Bruce Schneier را در مورد این موضوع ببینید.
http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html

هش چیست؟

هش (Hash, Hash Code, Digest, Message Digest هم نامیده می شود) را می توان به صورت اثر انگشت دیجیتالی یک داده در نظر گرفت. با این روش شما می توانید رشته ای اندازه-ثابت (fixed length) از یک داده به دست آورید که با روش های ریاضی به صورت "یک طرفه" رمزنگاری شده است. کشف رشته اصلی از رشته هش آن (عملیات معکوس) به صورت کارا تقریبا  غیر ممکن است. نکته دیگر اینکه هر داده یک رشته هش شده کاملا  منحصر به فرد ایجاد می کند( احتمال یکی شدن رشته های هش دو رشته متفاوت در الگوریتم MD5 یک در ۳/۴۰۲۸۲۳۶۶۹۲۰۹۳۸۴۶۳۴۶۳۳۷۴۶۰۷۴۳۱۷۷e+38 می باشد..  این خواص ، هش کردن را به روشی کارا و ایده آل برای ذخیره سازی کلمات عبور در برنامه های شما تبدیل می کند. چرا؟ برای این که حتی اگر یک نفوذگر(Hacker) بتواند به سیستم و بانک اطلاعاتی شما نفوذ کند و بخشی از اطلاعات شما را به دست آورد (شامل کلمات عبور هش شده) نمی تواند کلمات عبور اولیه را از روی آن ها بازیابی کند.

با این روش چگونه اعضا را شناسایی کنیم ؟

تا کنون نشان داده ایم که بازیابی کلمه عبور اصلی از روی رشته هش تقریبا غیر ممکن است ، خب چگونه برنامه های ما تشخیص دهند که کلمه عبور وارد شده توسط کاربر صحیح است ؟ به سادگی ! با تولید رشته هش کلمه عبور وارد شده توسط کاربر و مقایسه آن با رشته هش ذخیره شده در رکورد  بانک اطلاعاتی مربوط به کاربر می توانید متوجه شوید که آیا دو رشته با هم برابرند یا نه. بگذارید با ذکر یک مثال این بحث را ادامه دهیم.

ثبت نام اعضا و بازبینی کلمات عبور

در طی پروسه ثبت نام ، کاربر جدید کلمه عبور انتخابی خود را در اختیار برنامه ما قرار می دهد(ترجیحا با بازبینی - Verification - و در یک جلسه امن). این کار توسط تکه کدی مانند زیر انجام می گیرد، توجه کنید که نام های کابری و کلمات عبور را در بانک اطلاعاتی ذخیره می کنیم.

<?php
/* Store user details */
$passwordHash = sha1($_POST['password']);
$sql = 'INSERT INTO user (username,passwordHash) VALUES (?,?)';
$result = $db->query($sql, array($_POST['username'], $passwordHash));
?>

در هنگام مراجعه بعدی کاربر برای ورود به سیستم ، برای تایید او از تکه کدی مانند زیر استفاده می کنیم

<?php
/* Check user details */
$passwordHash = sha1($_POST['password']);
$sql = 'SELECT username FROM user WHERE username = ? AND passwordHash = ?';
$result = $db->query($sql, array($_POST['username'], $passwordHash));
if ($result->numRows() < 1)
{
    /* Access denied */
    echo 'Sorry, your username or password was incorrect!';
}
else
{
    /* Log user in */
    printf('Welcome back %s!', $_POST['username']);
}
?>

انواع هش

انواع مختلفی از الگوریتم های قوی هش کردن برای استفاده در برنامه های کاربردی موجود هستند، محبوب ترین آنها که مورد استفاده برنامه نویسان هستند MD5 و SHA-1 می باشند. سیستم های قدیمی تر از DES(Data Encryption Standard) استفاده می کردند. این روش ۵۶ بیتی دیگر  یک روش قوی هش کردن محسوب نمی گردد.

در PHP شما می توانید رشته هش را با استفاده از توابع md5() و sha1 تولید کنید. md5() یک رشته هش ۱۲۸ بیتی (۳۲ کاراکتر هگزادسیمال) برمی گرداند در حالیکه sha1() یک رشته هش ۱۶۰ بیتی (۴۰ کاراکتر هگزادسیمال) را بر می گرداند.

<?php
$string = 'PHP & Information Security';
printf("Original string: %sn", $string);
printf("MD5 hash: %sn", md5($string));
printf("SHA-1 hash: %sn", sha1($string));
?>

این کد خروجی زیر را تولید می کند:

Original string: PHP & Information Security
MD5 hash: 88dd8f282721af2c704e238e7f338c41
SHA-1 hash: b47210605096b9aa0129f88695e229ce309dd362

در MySQL شما می توانید رشته هش را مستقیما و توسط توایع درونی md5() ، password() و یا  sha1() تولید کنید. password() تابعی است که توسط خود MySQL برای سیستم تایید اعضایش یه کار می رود. این تابع یک رشته ۱۶ بایتی (۱۲۸ بیتی) - در MySQL قبل ازنسخه ۴/۱ - و یک رشته ۴۱ بایتی ( ۳۲۸ بیتی) بر پایه الگوریتم SHA-1 دوگانه - در MySQL 4.1 به بالا -  تولید می کند. md5() از نسخه ۳/۲۳/۲ به MySQL اضافه گردید و sha1()  در نسخه ۴/۰/۲ اضافه شد.

 

mysql> select PASSWORD( 'PHP & Information Security' );
+------------------------------------------+
| PASSWORD( 'PHP & Information Security' ) |
+------------------------------------------+
| 379693e271cd3bd6                         |
+------------------------------------------+
1 row in set (0.00 sec)
mysql> select MD5( 'PHP & Information Security' );
+-------------------------------------+
| MD5( 'PHP & Information Security' ) |
+-------------------------------------+
| 88dd8f282721af2c704e238e7f338c41    |
+-------------------------------------+
1 row in set (0.01 sec)

Note: Using MySQL's password() function in your own applications isn't recommended - the algorithm used has changed over time and prior to 4.1 was particularly weak.

شما ممکن است ترجیح دهید تا از MySQL برای تولید رشته هش در برنامه های خود استفاده نمایید. مثلا در مثال این مقاله :

<?php
/* Store user details */
$sql = 'INSERT INTO user (username, passwordHash) VALUES (?, SHA1(?))';
$result = $db->query($sql, array($_POST['username'], $_POST['password']));
?>

نقاط ضعف

از دیدگاه امنیتی ، ذخیره کردن رشته های هش کلمات عبور به صورت تنها در بانک اطلاعاتی کافی است تا کار یک نفوذگر را ده ها برابر مشکل تر کند. حال بگذارید ببینیم  یک نفوذگر پس از دسترسی به این رشته های هش شده چه  تلاش هایی برای بازیابی آن ها می کند، آیا او می تواند کلمات عبور اصلی را بازیابی کند ، یا خیر؟

او ابتدا می تواند با مشاهده جدول اعضا و با پیدا کردن رشته های هش مشایه حساب هایی که دارای کلمات عبور یکسان هستند را بیابد! البته این روش تا زمانی که او کلمه عبور هیچ کدام از اعضا را نداند مفید نیست و خطری را ایجاد نمی کند. روش معمول برای بازیابی رشته اصلی از رشته هش شده شکستن (Crack) کردن - یا به عبارت دیگر brute forcing - می باشد. با استفاده از این روش نفوذگر رشته های هش تمامی عباراتی که احتمال می دهد به عنوان کلمه عبور به کار برده شوند - مثلا با استفاده از یک دیکشنری لغات - را تولید می کند و با مقایسه آن ها با مقادیر ذخیره شده در بانک اطلاعاتی اقدام به یافتن کلمات عبور می نماید.

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

روشمان را بهبود دهیم

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

هنگام بررسی ورود کاربر ما همان پروسه قبلی را پی می گیریم، با این تفاوت که این بار ما از رشته salt که از بانک اطلاعاتی به دست می آوریم استفاده می کنیم نه از حالت تولید تصادفی آن . با مقایسه رشته هشی که با استفاده از ورودی کاربر به دست آمده با مقدار ذخیره شده در بانک اطلاعاتی کاربر می توانیم صحت آن را مشخص کنیم.

<?php
define('SALT_LENGTH', 9);
function generateHash($plainText, $salt = null)
{
    if ($salt === null)
    {
        $salt = substr(md5(uniqid(rand(), true)), 0, SALT_LENGTH);
    }
    else
    {
        $salt = substr($salt, 0, SALT_LENGTH);
    }

    return $salt . sha1($salt . $plainText);
}
?>

Note: The function above is limited in that the maximum salt length is 32 characters. You may wish to write your own salt generator to overcome this limit and increase the entropy of the string.

هنگام فراخوانی generateHash() با استفاده از تنها یک آرگومان(کلمه عبور) یک رشته تصادفی تولید شده و به عنوان رشته salt استفاده می گردد. رشته تولید شده که تشکیل شده از رشته salt و رشته هش شده است در بانک اطلاعاتی ذخیره می گردد. هنگام بررسی ورود کاربر ، پروسه کاملا متفاوت است زیرا شما رشته salt را می دانید. - دقت کنید از آن جایی که طول رشته salt ثابت است ، پس از دریافت اطلاعات کاربر از بانک اطلاعاتی با یک بار استفاده از تابع substr می توانید رشته salt را بیابید - این رشته salt به عنوان پارامتر دوم به همراه کلمه عبور ارسالی کاربر به تابع generateHash() فرستاده می شود و سپس عمل مقایسه صورت می پذیرد.

با استفاده از salt احتمال یکی شدن رشته های هش کابران در صورت یکی بودن کلمات عبورشان در بانک اطلاعاتی از بین می رود.

استفاده از دیکشنری برای حمله نیز دیگر کارایی نخواهد داشت، زیرا نفوذگر باید برای هر رکورد در بانک اطلاعاتی تمامی رشته های هش دیکشنری خود را دوباره محاسبه کند!

 

پاسخ ها(۱۱)

  1. ناشناس گفت:


    ممنون، مختصر و مفید و بدرد بخور


  2. دستتون درد نکنه مطالب جالبی بود.
    مرسی


  3. راستی مانی یک مدلش هم هست که با جاوااسکریپت اطلاعات فیلد پسورد رو قبل از سابمیت شدن فرم با md5 یا هر چیز دیگه ای هش میکنن و بعد فرم سابمیت میشه.

    اینم ایده ی جالبیه.. چون اگه اطلاعات فرم سابمیت شده به گونه ای( که من نمیدونم) اون وسط ربوده بشه، به درد رباینده نخواهد خورد.

  4. نبی گفت:


    سلام
    از مقاله مفیدوتون ممنونم. استفاده کردم .
    یه سوال !
    به نظر شما این کدهای هش شده زیر رو چطوری میشه باز کرد:
    ۶۴ae20c05ccc666e9e5b21cd0c7bd670efaadf5250fd10d6aad3b435b51404ee
    28caad991f14b9a2a3e15e46871d425b75b2fc7afec97a28aad3b435b51404ee
    ae9f0ffe0b072d4d1e17485861a52a88865483f347daf950ff17365faf1ffe89
    703012a81cb23f00294e97c578c7a481ec47f3a90a7fdb2eaad3b435b51404ee
    861ac73e1fb442512098faeb806e7ebbca660861497386ccf2ea805f5399cf05
    هر خط (۶۴ حرف) مربوط به یک پسورد اکانت اینترنت می باشد که توسط نرم افزار ISPUTIL (نرم افزار مدیریتی Isp) رمز شده است.

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

    باتشکر
    نبی

  5. سید جواد حسینی گفت:


    در رابطه با sqlinjection و وارد کردن کدهای اسکریپت و یا کدهای sql در ورودی های سایت مقالاتی را ارائه کنید ؟
    با تشکر


  6. ببینم یادم اول مطلب گفتین که میخواین PHP یاد بدین…
    اما ظاهر این دوستان نظر دهنده چیز دیگه ای برداشت کردن…


  7. به میلاد : من تصمیم به آموزش پی اچ پی ندارم ، من هنوز خیلی کار داره تا یه معلم بشم ، فعلا شاگردی می کنیم ! فقط سعی می کنم تجربیاتم رو با دیگران به اشتراک بذارم.

  8. dividersun گفت:


    مقاله جالبی بود . ممنون . اما یه سوال کوچیک برام پیش اومد اینکه بعد از دسترسی به کدهای هش شده چطور میتونم کدهای هش شده جدیدی جایگزینش کنم؟ با این روش دیگه احتیاجی به خوندن کد های هش شده نیست . مثلا فایل SAM ویندوز که کدهای هش شده ی اونو پیدا کردم میخوام کد هش شده ی دلخواهم رو جاش بذارم . میتونین کمکم کنین؟
    از سایت پر محتوی تون هم ممنونم . فقط اگه خواستین لطف کنین جواب رو به میلم بفرستین . بازم ممنون.

  9. bk7 گفت:


    سلام متشکرماز خدمات شما
    لطفا درباره کاربرد hash توضیح دهید.


  10. باید به عرض نبی برسونم که توابع hash (md5) به هیچ عنوان نمی شه برگردوننده بشن . چون توسط یکسری معادلات ریاضی یک طرفه بدون بازگشت رمزنگاری شدند.


  11. راستی آقای مدیر می شه ترجمه اون جمله header رو واسمون بگی؟؟؟ مفهومی بود نفهمیدم!

نظر دهید