पासवर्ड भूलने के लिए यादृच्छिक टोकन उत्पन्न करने के लिए सबसे अच्छा अभ्यास


94

मैं पासवर्ड भूल गए के लिए पहचानकर्ता उत्पन्न करना चाहता हूं। मैंने पढ़ा है कि मैं इसे mt_rand () के साथ टाइमस्टैम्प का उपयोग करके कर सकता हूं, लेकिन कुछ लोग कह रहे हैं कि हर बार समय टिकट अद्वितीय नहीं हो सकता है। इसलिए मैं यहाँ थोड़ा भ्रमित हूँ। क्या मैं इसके साथ टाइम स्टैम्प का उपयोग कर सकता हूं?

प्रश्न
कस्टम लंबाई के यादृच्छिक / अद्वितीय टोकन उत्पन्न करने के लिए सबसे अच्छा अभ्यास क्या है?

मुझे पता है कि यहाँ आसपास बहुत सारे प्रश्न पूछे जाते हैं, लेकिन विभिन्न लोगों से अलग-अलग राय पढ़ने के बाद मैं और अधिक भ्रमित हो रहा हूं।


@AlmaDoMundo: एक कंप्यूटर असीमित समय नहीं बांट सकता है।
juergen d

@ जुर्जेन्ड - सॉरी, ऐसा मत करो।
अल्मा दो

यदि आप इसे उदाहरण के लिए एक नैनो सेकंड के अलावा कहते हैं तो आपको समान टाइमस्टैम्प मिलेगा। उदाहरण के लिए कुछ समय फ़ंक्शन केवल 100ns चरणों में समय वापस कर सकते हैं, कुछ केवल कुछ सेकंड में।
juergen d

@ जुर्जेंड आह, वह। हाँ। मैंने केवल सेकंड के साथ 'क्लासिक' टाइमस्टैम्प का उल्लेख किया। लेकिन अगर आपने ऐसा कहा है - हाँ (जो कि हमें गैर-अद्वितीय टाइमस्टैम्प प्राप्त करने के लिए केवल समय मशीन के साथ एक विकल्प छोड़ देता है)
अल्मा दो

1
हेड अप, स्वीकृत उत्तर CSPRNG का लाभ नहीं उठाता है ।
स्कॉट आर्किस्ज़वेस्की

जवाबों:


148

PHP में, का उपयोग करें random_bytes()। कारण: आपके पासवर्ड रिमाइंडर टोकन प्राप्त करने का तरीका खोज रहे हैं, और, यदि यह एक बार लॉगिन करने का प्रमाण है, तो आपके पास वास्तव में सुरक्षा के लिए एक डेटा है (जो - संपूर्ण उपयोगकर्ता खाता है)

तो, कोड इस प्रकार होगा:

//$length = 78 etc
$token = bin2hex(random_bytes($length));

अद्यतन : इस उत्तर के पिछले संस्करणों का जिक्र था uniqid()और यह गलत है अगर सुरक्षा का मामला है और केवल विशिष्टता नहीं है। uniqid()अनिवार्य रूप से सिर्फ microtime()कुछ एन्कोडिंग के साथ है। microtime()आपके सर्वर पर सटीक पूर्वानुमान प्राप्त करने के सरल तरीके हैं । एक हमलावर पासवर्ड रीसेट अनुरोध जारी कर सकता है और फिर संभावित टोकन के एक जोड़े के माध्यम से प्रयास कर सकता है। यह भी संभव है यदि more_entropy का उपयोग किया जाता है, क्योंकि अतिरिक्त एन्ट्रॉपी समान रूप से कमजोर है। इसे इंगित करने के लिए @NikiC और @ScottArciszewski को धन्यवाद ।

अधिक जानकारी के लिए देखें


21
ध्यान दें कि random_bytes()केवल PHP7 के रूप में उपलब्ध है। पुराने संस्करणों के लिए, @yesitsme द्वारा उत्तर सबसे अच्छा विकल्प लगता है।
गेराल्ड श्नाइडर

3
@GeraldSchneider या random_compat , जो इन विशेषताओं के लिए पॉलीफ़िल है जिसे सबसे अधिक सहकर्मी समीक्षा प्राप्त हुई है;)
स्कॉट आर्किस्ज़वेस्की

मैंने इस टोकन को संग्रहीत करने के लिए अपने sql डेटाबेस में एक varchar (64) फ़ील्ड बनाया। मैंने $ 64 की लंबाई निर्धारित की है, लेकिन वापस लौटा स्ट्रिंग 128 वर्ण लंबा है। मैं एक निश्चित आकार के साथ एक स्ट्रिंग कैसे प्राप्त कर सकता हूं (यहां, 64 तब)?
गोर्डी

2
@ गोर्डी की लंबाई 32 है, प्रत्येक बाइट 2 हेक्स वर्ण है
JohnHoulderUK

क्या होना चाहिए $length? उपयोगकर्ता की आईडी? और क्या?
ढेर

71

यह 'सर्वश्रेष्ठ यादृच्छिक' अनुरोध का जवाब देता है:

Adi के जवाब 1 से Security.StackExchange के पास इसके लिए एक समाधान है:

सुनिश्चित करें कि आपके पास OpenSSL समर्थन है, और आप इस एक-लाइनर के साथ कभी भी गलत नहीं होंगे

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. आदि, सोम 12 नवंबर 2018, सेलेरिटास, "पुष्टिकरण ई-मेल के लिए एक अनुपयुक्त टोकन उत्पन्न करना", 20 सितंबर को 7:06 बजे, https://security.stackexchange.com/a/40314/


24
openssl_random_pseudo_bytes($length)- समर्थन: PHP 5> = 5.3.0, ......................................। ................... (PHP 7 और ऊपर के लिए, उपयोग करें random_bytes($length)) ...................... .................... (PHP के नीचे 5.3 के लिए - 5.3 से नीचे PHP का उपयोग न करें)
jave.web

54

स्वीकृत उत्तर का पूर्व संस्करण ( md5(uniqid(mt_rand(), true))) असुरक्षित है और केवल 2 ^ 60 संभावित आउटपुट प्रदान करता है - एक कम बजट के हमलावर के लिए लगभग एक सप्ताह के समय में एक क्रूर बल खोज की सीमा के भीतर:

चूंकि 56-बिट डेस कुंजी को लगभग 24 घंटों में क्रूरता-मजबूर किया जा सकता है , और एक औसत मामले में लगभग 59 बिट्स एन्ट्रापी होंगे, हम 2 ^ 59/2 ^ 56 = लगभग 8 दिनों की गणना कर सकते हैं। यह टोकन सत्यापन कैसे लागू किया जाता है, इसके आधार पर, यह संभव हो सकता है कि समय-समय पर जानकारी लीक हो और एक वैध रीसेट टोकन के पहले एन बाइट्स का अनुमान लगाया जाए

चूंकि सवाल "सर्वोत्तम प्रथाओं" के बारे में है और इसके साथ खुलता है ...

मैं पासवर्ड भूल गए के लिए पहचानकर्ता उत्पन्न करना चाहता हूं

... हम अनुमान लगा सकते हैं कि इस टोकन में अंतर्निहित सुरक्षा आवश्यकताएं हैं। और जब आप एक यादृच्छिक संख्या जनरेटर के लिए सुरक्षा आवश्यकताओं को जोड़ते हैं, तो सबसे अच्छा अभ्यास हमेशा क्रिप्टोग्राफिक रूप से सुरक्षित छद्म आयामी संख्या जनरेटर (संक्षिप्त सीएसआरएनजी) का उपयोग करना है।


CSPRNG का उपयोग करना

PHP 7 में, आप bin2hex(random_bytes($n))(जहाँ $nपूर्णांक 15 से बड़ा है) का उपयोग कर सकते हैं ।

PHP 5 में, आप random_compatउसी API को एक्सपोज़ करने के लिए उपयोग कर सकते हैं ।

वैकल्पिक रूप से, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))यदि आपने ext/mcryptस्थापित किया है। एक और अच्छा वन-लाइनर है bin2hex(openssl_random_pseudo_bytes($n))

लुकअप को वैलिडेटर से अलग करना

PHP में सुरक्षित "मुझे याद रखें" कुकीज़ पर अपने पिछले काम से खींचकर , पूर्वोक्त समय रिसाव (आमतौर पर डेटाबेस क्वेरी द्वारा पेश) को कम करने का एकमात्र प्रभावी तरीका है कि लुकअप को सत्यापन से अलग किया जाए।

यदि आपकी तालिका इस प्रकार है (MySQL) ...

CREATE TABLE account_recovery (
    id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT 
    userid INTEGER(11) UNSIGNED NOT NULL,
    token CHAR(64),
    expires DATETIME,
    PRIMARY KEY(id)
);

... आपको एक और कॉलम जोड़ने की जरूरत है selector, जैसे:

CREATE TABLE account_recovery (
    id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT 
    userid INTEGER(11) UNSIGNED NOT NULL,
    selector CHAR(16),
    token CHAR(64),
    expires DATETIME,
    PRIMARY KEY(id),
    KEY(selector)
);

CSPRNG का उपयोग करें जब पासवर्ड रीसेट टोकन जारी किया जाता है, तो उपयोगकर्ता को दोनों मान भेजें, डेटाबेस में यादृच्छिक टोकन के चयनकर्ता और SHA-256 हैश को संग्रहीत करें। हैश और यूजर आईडी को हथियाने के लिए चयनकर्ता का उपयोग करें, उपयोगकर्ता द्वारा प्रदान किए गए टोकन के SHA-256 हैश की गणना डेटाबेस में संग्रहीत एक के साथ करता है hash_equals()

उदाहरण कोड

PDO के साथ PHP 7 (या random_compat के साथ 5.6) में एक रीसेट टोकन उत्पन्न करना:

$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);

$urlToEmail = 'http://example.com/reset.php?'.http_build_query([
    'selector' => $selector,
    'validator' => bin2hex($token)
]);

$expires = new DateTime('NOW');
$expires->add(new DateInterval('PT01H')); // 1 hour

$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
    'userid' => $userId, // define this elsewhere!
    'selector' => $selector,
    'token' => hash('sha256', $token),
    'expires' => $expires->format('Y-m-d\TH:i:s')
]);

उपयोगकर्ता द्वारा प्रदान किए गए रीसेट टोकन का सत्यापन:

$stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()");
$stmt->execute([$selector]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
    $calc = hash('sha256', hex2bin($validator));
    if (hash_equals($calc, $results[0]['token'])) {
        // The reset token is valid. Authenticate the user.
    }
    // Remove the token from the DB regardless of success or failure.
}

ये कोड स्निपेट पूर्ण समाधान नहीं हैं (मैंने इनपुट सत्यापन और ढांचे को एकीकृत किया है), लेकिन उन्हें उदाहरण के रूप में काम करना चाहिए।


जब आप उपयोगकर्ता द्वारा प्रदान किए गए रीसेट टोकन को सत्यापित करते हैं, तो आप यादृच्छिक टोकन के द्विआधारी प्रतिनिधित्व का उपयोग क्यों करते हैं? क्या आपको लगता है कि यह संभव है (और सुरक्षित?) से: 1) स्टोर में DB में हैश के हेक्स मान के साथ hash('sha256', bin2hex($token)), 2) के साथ सत्यापित करें if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? धन्यवाद!
गीकरा

हां, हेक्स स्ट्रिंग्स की तुलना करना सुरक्षित है। यह वास्तव में वरीयता की बात है। मैं कच्चे बाइनरी पर सभी क्रिप्टो संचालन करना पसंद करता हूं और केवल ट्रांसमिशन या भंडारण के लिए कभी भी हेक्स / बेस 64 में परिवर्तित करता हूं।
स्कॉट आर्किसेवस्की

हाय स्कॉट, यह मूल रूप से न केवल आपके उत्तर के लिए एक प्रश्न है, बल्कि "रिमेम्बर मी" फीचर के बारे में पूरे लेख के लिए है। idचयनकर्ता के रूप में अद्वितीय का उपयोग क्यों नहीं करना चाहिए ? मेरा मतलब है, account_recoveryतालिका की प्राथमिक कुंजी । हमें चयनकर्ता के लिए सुरक्षा की अतिरिक्त परत की आवश्यकता नहीं है, क्या हम? धन्यवाद!
आंद्रे पॉलीकिनिन

id:secretठीक है। selector:secretठीक है। secretखुद नहीं है। लक्ष्य डेटाबेस क्वेरी (जो समय-टपका हुआ है) को प्रमाणीकरण प्रोटोकॉल से अलग करना है (जो निरंतर समय होना चाहिए)।
स्कॉट आर्किस्विस्की

अगर PHP 5.6 चल रहा है तो क्या openssl_random_pseudo_bytesइसके बजाय उपयोग में कोई नुकसान है random_bytes? इसके अलावा, क्या आपको सिर्फ चयनकर्ता को नहीं जोड़ना चाहिए और लिंक के क्वेरिस्ट्रिंग में सत्यापनकर्ता को नहीं?
greg

7

आप DEV_RANDOM का उपयोग भी कर सकते हैं, जहाँ 128 = 1/2 उत्पन्न टोकन लंबाई है। नीचे कोड 256 टोकन उत्पन्न करता है।

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));

4
मैं सुझाव MCRYPT_DEV_URANDOMदूंगा MCRYPT_DEV_RANDOM
स्कॉट आर्किस्ज़ेव्स्की

2

जब भी आपको बहुत ही यादृच्छिक टोकन की आवश्यकता होती है तो यह सहायक हो सकता है

<?php
   echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.