تابع رندوم امن – خشت اول چون نهاد معمار کج …!

میدونید که در وب از توابع رندوم برای مقاصد امنیتی خیلی زیاد استفاده میشه.
رشته های رندوم در وب کاربرد گسترده و اساسی دارن. ما واقعا بهشون نیازمندیم.
مثلا کد فعال سازی، انواع Token های امنیتی، حتی کلیدهای رمزنگاری، … .
اینها معمولا با استفاده از توابع استاندارد PHP مثل rand, mt_rand, microtime و البته روشهای ترکیبی ای و پیشرفته تری که بعضی ها از خودشون ابداع میکنن تولید میشن.

اما آیا این توابع و روشها بقدر کافی امن هستند که بتونیم برای تمام این موارد و در تمام سطوح حساسیت امنیتی بهشون اعتماد کنیم؟

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

چرا؟

چون توابع رندوم PHP از نوع توابع رندوم امنیتی و مناسب استفاده در مصارف رمزنگاری نیستند. و چون روشهای شخصی اختراع شده توسط برنامه نویسان هم گرچه بعضا میتوانند به امنیت بالاتری دست بیابند اما هنوز از نظر اصول علم امنیت و رمزنگاری ضعفهای زیاد و بزرگی دارند و امنیت آنها درحد استانداردهای رسمی و حرفه ای نیست؛ چون توسط افراد دارای تخصص لازم (تخصص در علم رمزنگاری/Cryptography) طراحی نشده و این روشها تحقیق و دلیل و سند خاصی هم بعنوان پشتوانه ندارند.

و اما بنده در اینجا یک تابع رندوم امن رو براتون میذارم، که توصیه میکنم برای تولید تمام دیتای رندوم خودتون که اهمیت امنیتی داره ازش استفاده کنید:

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

/**
* 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

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

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

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 & "xDFxDFxDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
extract(unpack('Nrandom', pack('H*', sha1(openssl_random_pseudo_bytes(4).$entropy.microtime()))));
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:

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 random_string($length, $chars=null) {

if(is_null($chars)) $chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

$random_string='';

for($i=0; $i<$length; $i++)
$random_string.=$chars[crypt_random(0, strlen($chars)-1)];

return $random_string;

}

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

?>

این تابع رو از کتابخانهء رمزنگاری phpseclib کش رفتم و البته یک مقدار قابل توجهی بهبود و تغییراتی هم درش داده شده که این کار رو با همکاری و کمک و تایید خود توسعه دهندهء کتابخانهء phpseclib انجام دادم.

در کد بالا سه تابع مشاهده میکنید، اما تابع اصلی تابع crypt_random است که میشه گفت معادل امن تابع mt_rand است؛ بقیهء توابع ارائه شده از crypt_random بعنوان هستهء خود استفاده میکنند.

ضمنا اون خط $entropy=sha1… هم که قبل از تابع اومده جزیی از سیستم تولید اعداد رندوم هست ولی باید خارج از تابع قرار بگیره.

مقدار متغییر pepper باید به یک رشتهء 22 کاراکتری متشکل از حروف بزرگ و کوچک و اعداد که بصورت رندوم انتخاب شدن set بشه. البته میتونم بگم وجود/تغییر این متغییر برای تولید اعداد رندوم امن توسط این تابع ضروری نیست (در تقریبا تمام شرایط/سناریوها)، اما بودنش برنامه رو دیگه درحد فوق حرفه ای میکنه.

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

تولید یک عدد رندوم از 1 تا 10:

crypt_random(1, 10)

یک عدد رندوم از 0 تا 2147483647:

crypt_random()

یک رشتهء باینری رندوم با طول 16 بایت (128 بیت):

random_bytes(16)

توضیح اینکه اینطور رشته های رندوم باینری اغلب بعنوان کلیدهای رمزنگاری متقارن استفاده میشن (توسط الگوریتم هایی مثل AES).

یک رشتهء رندوم به طول 22 کاراکتر، متشکل از حروف بزرگ و کوچک و اعداد:

random_string(22)

یک رشتهء رندوم به طول 12 کاراکتر، که فقط از کاراکترهای A, B, C و 1, 2, 3 تشکیل شده باشه:

random_string(12, '123ABC')

10 دیدگاه در “تابع رندوم امن – خشت اول چون نهاد معمار کج …!

  1. با حال بود
    یه جا خونده بودم که چیزی به اسم تابع رندوم وجود نداره و اینکه این اعداد تصادفی (یا رشته های تصادفی ) دنباله ی یک تابع ریاضی هستن

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

  2. بازپینگ: علم خوره

    • این تابع هم در تابعی که بنده گذاشتم استفاده شده. تابعی که بنده گذاشتم کاملتره و همهء موارد رو در خودش جمع و آماده استفاده کرده. مثلا از این تابع openssl روی ویندوز استفاده نمیکنه، چون روی ویندوز به طرز فجیعی کنده. ضمنا اول باید چک کنی ببینی در دسترس هست یا نه.

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

  3. سلام
    میشه pepper را رندم انتخاب کرد ؟
    من این تابع را میخواهم در پروژه خودم استفاده کنم ولی یکم با تغییر نظم کد

    • pepper باید رندوم باشه دیگه! اینکه گفتم 22 کاراکتر رندوم متشکل از اعداد و حروف بزرگ و کوچک، بخاطر اینکه محاسبه کردم تعداد حالتهای ممکن چنین رشته ای، از 2 به توان 128 کمتر نیست. 2 به توان 128 یک استاندارد معمول برای کارهای امنیتی و رمزنگاری است. شما میتونید از کاراکترهای دیگر و طول دیگری هم استفاده کنید، ولی باید محاسبه کنید که تعداد حالتهای ممکن اون کمتر از 2 به توان 128 نشه. اگر کمتر از 2 به توان 128 بشه، اونوقت از نظر امنیت حرفه ای کمتر از حداقله، ولی با این حال هنوزم ممکنه بقدر کافی امن باشه برای کاربرد شما (و حتی اکثرا بیشتر از کافی)، اما اگر بخوایم روزهء شک دار نگیریم و از استانداردها و محاسبات روشن و محکم علم رمزنگاری و چیزی که متخصصان این رشته طراحی و توصیه کردن خارج نشیم و پای شرط و شروط در بین نیاد، باید 2 به توان 128 باشه حداقل.

پاسخ دهید

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

*

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