PDA

View Full Version : یک روش هش پسورد حرفه ای



eshpilen
چهارشنبه 07 تیر 1391, 21:15 عصر
مجبور میشم قسمتهایی از پروژهء رجیستر و لاگین امنیتی خودم رو بصورت مجزا قرار بدم، چون واقعا مفید هستن و بعضی ها هم سوالهایی میپرسن و درخواست روش میکنن. الان این تاپیک رو هم بخاطر درخواست یک نفر که در پست خصوصی روش هش کردن پسورد رو خواسته بود ایجاد کردم. چون در پست خصوصی نمیشه فایل ضمیمه کرد، و نمایش کدهای فروم هم که کدها رو خراب میکنه.

بهرصورت همه میتونن از این روش واقعا قوی هش کردن استفاده کنن.

تعریف توابع:


<?php
if(ini_get('register_globals')) exit("<center><h3>Error: Turn that damned register globals off!</h3></center>");

//################################################## #######################

$secure_hash_rounds=16; //note that actual number of rounds is 2^$secure_hash_rounds

$pepper="89JPa36HW7Uiq348dX10ks"; //composed of at least 22 chars of upper and lowercase letters + digits

//################################################## #######################

/**
* Random Number Generator
*
* @category Crypt
* @package Crypt_Random
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version $Id: Random.php,v 1.9 2010/04/24 06:40:48 terrafrost Exp $
* @link http://phpseclib.sourceforge.net
*/

// crypt_random modified by hamidreza_mz -=At=- yahoo -=Dot=- com

@$entropy=sha1(microtime().$pepper.$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].$_SERVER['HTTP_USER_AGENT'].serialize($_POST).serialize($_GET).serialize($_CO OKIE));

function crypt_random($min = 0, $max = 0x7FFFFFFF)
{
if ($min == $max) {
return $min;
}

global $entropy;

if (function_exists('openssl_random_pseudo_bytes')) {
// openssl_random_pseudo_bytes() is slow on windows per the following:
// http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
extract(unpack('Nrandom', pack('H*', sha1(openssl_random_pseudo_bytes(4).$entropy.micro time()))));
return abs($random) % ($max - $min) + $min;
}
}

// see http://en.wikipedia.org/wiki//dev/random
static $urandom = true;
if ($urandom === true) {
// Warning's will be output unles the error suppression operator is used. Errors such as
// "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
$urandom = @fopen('/dev/urandom', 'rb');
}
if (!is_bool($urandom)) {
extract(unpack('Nrandom', pack('H*', sha1(fread($urandom, 4).$entropy.microtime()))));
// say $min = 0 and $max = 3. if we didn't do abs() then we could have stuff like this:
// -4 % 3 + 0 = -1, even though -1 < $min
return abs($random) % ($max - $min) + $min;
}


if(function_exists('mcrypt_create_iv') and version_compare(PHP_VERSION, '5.3.0', '>=')) {
@$tmp16=mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
if($tmp16!==false) {
extract(unpack('Nrandom', pack('H*', sha1($tmp16.$entropy.microtime()))));
return abs($random) % ($max - $min) + $min;
}
}


/* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called.
Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here:

http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/

The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro:

http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */
static $seeded;
if (!isset($seeded) and version_compare(PHP_VERSION, '5.2.5', '<=')) {
$seeded = true;
mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF));
}

extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF).$entropy.microtime()))));
return abs($random) % ($max - $min) + $min;

}

//################################################## #######################

function random_bytes($length) {

$bytes = '';

for($i = 0; $i < $length; $i++) $bytes.=chr(crypt_random(0, 255));

return $bytes;
}

//################################################## #######################

function create_secure_hash($password, $rounds=null) {

global $secure_hash_rounds;

if(is_null($rounds)) $rounds=$secure_hash_rounds;

$salt=random_bytes(16);
//16 bytes = 128 bits -- note that our salt is a binary salt

global $pepper;

$hash=hash('sha256', $pepper.$salt.$password, true);

$tmp17=pow(2, $rounds)-1;

while($tmp17--) $hash=hash('sha256', $hash.$password, true);

return $rounds.'*'.$salt.$hash;

}

//==================================

function verify_secure_hash($password, $hash) {

$rounds=substr($hash, 0, strpos($hash, '*'));

$salt=substr($hash, strpos($hash, '*')+1, strlen($hash)-strlen($rounds)-32-1);

$hash=substr($hash, strlen($hash)-32);

global $pepper;

$tmp17=$hash;

$hash=hash('sha256', $pepper.$salt.$password, true);

$rounds=pow(2, $rounds)-1;

while($rounds--) $hash=hash('sha256', $hash.$password, true);

return $tmp17===$hash;

}

//################################################## #######################

?>


مثال استفاده:


<?php

require 'secure_hash.php';

$password='user password';

$t1=microtime(true);

$hash=create_secure_hash($password);

$t2=microtime(true);

echo '<hr>';

var_dump($hash);

echo '<hr>';

echo 'Hashing took: ', sprintf('%1.2f', $t2-$t1), ' second';

echo '<hr>';

if(verify_secure_hash($password, $hash)) echo 'Password correct.';
else echo 'Password wrong!';

?>

کدها رو در اینجا بخاطر امکان مطالعه مستقیم درج کردم.
چون نمایش کدهای فروم اغلب کدها رو خراب میکنه، فایلهای مرتبط ضمیمه شدن.

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

متغییر secure_hash_rounds هم تعداد دورهای استفاده شده برای Key stretching رو تعیین میکنه. البته تعداد واقعی دورها برابر 2 به توان secure_hash_rounds است.
معمولا مقدار secure_hash_rounds رو باید روی عددی قرار بدید که زمان صرف شده برای عملیات هش کردن مثلا یک دهم یا چند دهم ثانیه باشه. هرچی این زمان بیشتر باشه امنیت هش بیشتره. ولی اگر سرور و سایت شما ترافیک سنگینی داره میتونید مقدار secure_hash_rounds رو کمتر کنید که عملیات هش زمان کمتری صرف کنه. همیشه طوری تنظیم کنید که حداکثری باشه که بنظرتون مشکل پرفورمنس جدی ایجاد نمیکنه. منظورم از مشکل پرفورمنس مشکل واقعی است، یعنی در عمل مشکل دیده بشه، نه اونی که اغلب توی خیالات افراد هست و برای 1 میلی ثانیه هم خودشون رو میکشن! (بیماری وسواس در بهینه سازی!!)
بهرصورت ترافیک در بخش رجیستر و لاگین سایتها اغلب کسر کوچکی از کل ترافیک سایت است و بنابراین نباید در بیشتر موارد هیچ مشکلی با این قضیه وجود داشته باشه.

راستی خروجی تابع هش امنیتی ما یک رشتهء باینری 51 بایتی است که بنابراین باید در دیتابیس براش یک فیلد مثلا varbinary(51) تعریف کنید.

ضمنا حتما دقت کردید که بخش اول فایل secure_hash.php یک تابع رندوم امنه (که اخیرا در تاپیک دیگری ارائه کرده بودمش). تابع create_secure_hash تابع رندوم امن رو برای تولید salt نیاز داره.

eshpilen
چهارشنبه 07 تیر 1391, 21:27 عصر
البته باید بگم الگوریتمهای خفن تر از اینم وجود دارن، ولی اونا چون یه مقدار مشکلات ساپورت ممکنه بربخوریم بعضی جاها (من حداقل یک مورد دیدم) یا اینکه باید خودمون تحقیق و پیاده سازی کنیم، ترجیح دادم از روش ساده تری استفاده کنم. بنظر بنده امنیت همین روش هم برای کاربردهای عادی به حد کافی بالاست. خلاصه زیاد هم نباید گیر داد. بخصوص که امنیت بقیهء بخشهای سیستم هم معمولا بالاتر از این حد استاندارد نیست. این الگوریتم که ارائه شده صدها برابر روشهای درپیت و اختراعی شخصی که اکثرا ارائه میشه امنیت داره، چون بر اساس موارد پیشرفته علم رمزنگاری تهیه شده. سالت رندوم به ازای هر پسورد داره، سالت ثابت در کد (pepper) داره، و از متد Key stretching هم استفاده میکنه، ضمنا از SHA256 بعنوان الگوریتم هش پایه استفاده میکنه.