คำตอบที่ได้รับการยอมรับ ( md5(uniqid(mt_rand(), true))
) เวอร์ชันก่อนหน้านั้นไม่ปลอดภัยและให้ผลลัพธ์ที่เป็นไปได้ประมาณ 2 ^ 60 เท่านั้นซึ่งอยู่ในช่วงของการค้นหากำลังดุร้ายในเวลาประมาณหนึ่งสัปดาห์สำหรับผู้โจมตีที่มีงบประมาณต่ำ:
เนื่องจากคีย์ DES 56 บิตสามารถบังคับได้ในเวลาประมาณ 24 ชั่วโมงและกรณีโดยเฉลี่ยจะมีเอนโทรปีประมาณ 59 บิตเราจึงคำนวณได้ 2 ^ 59/2 ^ 56 = ประมาณ 8 วัน ขึ้นอยู่กับวิธีการตรวจสอบโทเค็นนี้จะดำเนินการมันอาจจะเป็นไปได้ที่จะรั่วไหลของข้อมูลเวลาจริงและอนุมานแรก N ไบต์ของโทเค็นการตั้งค่าที่ถูกต้อง
เนื่องจากคำถามเกี่ยวกับ "แนวทางปฏิบัติที่ดีที่สุด" และเปิดด้วย ...
ฉันต้องการสร้างตัวระบุสำหรับลืมรหัสผ่าน
... เราสามารถสรุปได้ว่าโทเค็นนี้มีข้อกำหนดด้านความปลอดภัยโดยปริยาย และเมื่อคุณเพิ่มข้อกำหนดด้านความปลอดภัยให้กับตัวสร้างตัวเลขแบบสุ่มแนวทางปฏิบัติที่ดีที่สุดคือใช้ตัวสร้างหมายเลขหลอกที่มีการเข้ารหัสลับและปลอดภัยเสมอ(CSPRNG แบบย่อ)
ใช้ 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))
.
การแยกการค้นหาออกจาก Validator
การดึงข้อมูลจากงานก่อนหน้าของฉันเกี่ยวกับคุกกี้ "จำฉัน" ที่ปลอดภัยใน 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 ของโทเค็นแบบสุ่มในฐานข้อมูล ใช้ตัวเลือกที่จะคว้ากัญชาและ User ID คำนวณ SHA-256 hash_equals()
ของโทเค็นผู้ใช้ให้กับคนที่เก็บไว้ในฐานข้อมูลโดยใช้
ตัวอย่างรหัส
การสร้างโทเค็นรีเซ็ตใน PHP 7 (หรือ 5.6 พร้อม random_compat) ด้วย PDO:
$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'));
$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
'userid' => $userId,
'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'])) {
}
}
ข้อมูลโค้ดเหล่านี้ไม่ใช่โซลูชันที่สมบูรณ์ (ฉันละทิ้งการตรวจสอบอินพุตและการรวมเฟรมเวิร์ก) แต่ควรใช้เป็นตัวอย่างของสิ่งที่ต้องทำ