แนวทางปฏิบัติที่ดีที่สุดในการสร้างโทเค็นแบบสุ่มสำหรับลืมรหัสผ่าน


96

ฉันต้องการสร้างตัวระบุสำหรับลืมรหัสผ่าน ฉันอ่านว่าฉันสามารถทำได้โดยใช้การประทับเวลากับ mt_rand () แต่บางคนบอกว่าการประทับเวลาอาจไม่ซ้ำกันทุกครั้ง ดังนั้นฉันค่อนข้างสับสนที่นี่ ฉันสามารถใช้การประทับเวลากับสิ่งนี้ได้หรือไม่?

คำถาม
แนวทางปฏิบัติที่ดีที่สุดในการสร้างโทเค็นแบบสุ่ม / ไม่ซ้ำแบบกำหนดเองคืออะไร?

ฉันรู้ว่ามีคำถามมากมายที่ถามอยู่ที่นี่ แต่ฉันเริ่มสับสนมากขึ้นหลังจากอ่านความเห็นที่แตกต่างจากคนอื่น


@AlmaDoMundo: คอมพิวเตอร์ไม่สามารถแบ่งเวลาได้ไม่ จำกัด
juergen วันที่

@juergend - ขออภัยไม่เข้าใจ
Alma Do

คุณจะได้รับการประทับเวลาเดียวกันหากคุณเรียกมันเช่นเวลาห่างกันนาโนวินาที ฟังก์ชันเวลาบางฟังก์ชันสามารถคืนเวลาได้ในขั้นตอน 100ns เท่านั้นบางฟังก์ชันในขั้นตอนไม่กี่วินาทีเท่านั้น
juergen วันที่

@juergend อ่าว่า. ใช่. ฉันพูดถึงการประทับเวลาแบบ "คลาสสิก" ด้วยเวลาเพียงไม่กี่วินาที แต่ถ้าทำเหมือนที่คุณเคยพูด - ใช่ (ซึ่งทำให้เรามีตัวเลือกกับไทม์แมชชีนเท่านั้นเพื่อรับการประทับเวลาที่ไม่ซ้ำใคร)
Alma Do

1
ใหญ่ขึ้นคำตอบที่ได้รับการยอมรับไม่ได้ใช้ประโยชน์จากCSPRNG
Scott Arciszewski

คำตอบ:


152

ใน PHP ให้ใช้random_bytes()ไฟล์. เหตุผล: คุณกำลังมองหาวิธีรับโทเค็นการแจ้งเตือนรหัสผ่านและหากเป็นข้อมูลรับรองการเข้าสู่ระบบเพียงครั้งเดียวแสดงว่าคุณมีข้อมูลที่จะปกป้อง (ซึ่งก็คือ - บัญชีผู้ใช้ทั้งหมด)

ดังนั้นรหัสจะเป็นดังนี้:

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

อัปเดต : เวอร์ชันก่อนหน้าของคำตอบนี้อ้างถึงuniqid()และไม่ถูกต้องหากมีเรื่องของความปลอดภัยและไม่เพียง แต่เป็นเอกลักษณ์เท่านั้น uniqid()โดยพื้นฐานแล้วเป็นเพียงการmicrotime()เข้ารหัสบางอย่าง มีวิธีง่ายๆในการคาดการณ์ที่แม่นยำของmicrotime()เซิร์ฟเวอร์ของคุณ ผู้โจมตีสามารถส่งคำขอรีเซ็ตรหัสผ่านจากนั้นลองใช้โทเค็นที่เป็นไปได้สองสามรายการ นอกจากนี้ยังเป็นไปได้หากใช้ more_entropy เนื่องจากเอนโทรปีเพิ่มเติมมีความอ่อนแอในทำนองเดียวกัน ขอบคุณ@NikiCและ@ScottArciszewski ที่ชี้ประเด็นนี้

ดูรายละเอียดเพิ่มเติมได้ที่


21
โปรดทราบว่าrandom_bytes()พร้อมใช้งานใน PHP7 เท่านั้น สำหรับเวอร์ชันเก่าคำตอบของ @yesitsme น่าจะเป็นตัวเลือกที่ดีที่สุด
Gerald Schneider

3
@GeraldSchneider หรือrandom_compatซึ่งเป็นโพลีฟิลสำหรับคุณสมบัติเหล่านี้ที่ได้รับการรีวิวจากเพื่อนมากที่สุด)
Scott Arciszewski

ฉันสร้างฟิลด์ varchar (64) ในฐานข้อมูล sql เพื่อเก็บโทเค็นนี้ ฉันตั้งค่าความยาว $ เป็น 64 แต่สตริงที่ส่งกลับมีความยาว 128 อักขระ ฉันจะหาสตริงที่มีขนาดคงที่ได้อย่างไร (ที่นี่ 64 แล้ว)
gordie

2
@gordie ตั้งค่าความยาวเป็น 32 แต่ละไบต์เป็น 2 ตัวอักษร hex
JohnHoulderUK

ควรเป็น$lengthอย่างไร รหัสของผู้ใช้? หรืออะไร?
กอง

72

สิ่งนี้ตอบสนองคำขอ 'สุ่มที่ดีที่สุด':

คำตอบของ Adi 1จาก Security.StackExchange มีวิธีแก้ปัญหานี้:

ตรวจสอบให้แน่ใจว่าคุณได้รับการสนับสนุน OpenSSL และคุณจะไม่มีทางผิดพลาดกับซับเดียวนี้

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Adi, จันทร์ 12 พฤศจิกายน 2018, Celeritas, "การสร้างโทเค็นที่ไม่สามารถยอมรับได้สำหรับอีเมลยืนยัน", 20 ก.ย. 56 เวลา 07:06 น. https://security.stackexchange.com/a/40314/


25
openssl_random_pseudo_bytes($length)- รองรับ: PHP 5> = 5.3.0, ....................................... ................... (สำหรับ PHP 7 ขึ้นไปให้ใช้random_bytes($length)) ...................... .................... (สำหรับ PHP ต่ำกว่า 5.3 - อย่าใช้ PHP ต่ำกว่า 5.3)
jave.web

54

คำตอบที่ได้รับการยอมรับ ( 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')); // 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) จัดเก็บค่าฐานสิบหกที่แฮชของโทเค็นด้วยhash('sha256', bin2hex($token))2) ตรวจสอบด้วยif (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? ขอบคุณ!
Guicara

ใช่การเปรียบเทียบสตริงฐานสิบหกก็ปลอดภัยเช่นกัน มันเป็นเรื่องของความชอบจริงๆ ฉันชอบที่จะดำเนินการเข้ารหัสลับทั้งหมดบนไบนารีดิบและเคยแปลงเป็นฐานสิบหก / ฐาน 64 สำหรับการส่งหรือการจัดเก็บเท่านั้น
Scott Arciszewski

สวัสดีสก็อตเป็นคำถามที่ไม่เพียง แต่สำหรับคำตอบของคุณ แต่สำหรับบทความทั้งหมดเกี่ยวกับคุณลักษณะ "จดจำฉัน" ทำไมไม่ใช้ unique idเป็นตัวเลือกล่ะ? ฉันหมายถึงคีย์หลักของaccount_recoveryตาราง เราไม่ต้องการชั้นความปลอดภัยเพิ่มเติมสำหรับตัวเลือกใช่ไหม ขอบคุณ!
Andre Polykanine

id:secretก็โอเค selector:secretก็โอเค secretตัวเองไม่ได้ เป้าหมายคือการแยกการสืบค้นฐานข้อมูล (ซึ่งเป็นเวลาที่รั่วไหล) ออกจากโปรโตคอลการตรวจสอบสิทธิ์ (ซึ่งควรเป็นเวลาที่คงที่)
Scott Arciszewski

มีอันตรายในการใช้openssl_random_pseudo_bytesแทนหรือไม่random_bytesหากใช้ PHP 5.6? นอกจากนี้คุณไม่ควรต่อท้ายเพียงตัวเลือกและไม่ใช่ตัวตรวจสอบความถูกต้องในสตริงการสืบค้นของลิงก์ใช่หรือไม่
greg

7

คุณยังสามารถใช้ DEV_RANDOM โดยที่ 128 = 1/2 ของความยาวโทเค็นที่สร้างขึ้น โค้ดด้านล่างสร้าง 256 โทเค็น

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

4
ผมจะแนะนำมากกว่าMCRYPT_DEV_URANDOM MCRYPT_DEV_RANDOM
Scott Arciszewski

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.