รหัสผ่าน“ การแฮ็ชสองเท่า” นั้นมีความปลอดภัยน้อยกว่าแค่เพียงครั้งเดียวหรือไม่


293

การแฮชรหัสผ่านสองครั้งก่อนที่จะจัดเก็บข้อมูลมีความปลอดภัยมากกว่าหรือน้อยกว่าเพียงแค่ทำการแฮชรหัสครั้งเดียวหรือไม่?

สิ่งที่ฉันกำลังพูดถึงคือการทำสิ่งนี้:

$hashed_password = hash(hash($plaintext_password));

แทนที่จะเป็นแค่นี้:

$hashed_password = hash($plaintext_password);

หากมีความปลอดภัยน้อยกว่าคุณสามารถให้คำอธิบายที่ดี (หรือลิงค์ไปยังหนึ่ง) ได้หรือไม่?

ฟังก์ชั่นแฮชใช้เพื่อสร้างความแตกต่างหรือไม่? มันสร้างความแตกต่างหรือไม่ถ้าคุณผสม md5 และ sha1 (ตัวอย่าง) แทนที่จะทำซ้ำฟังก์ชันแฮชเดียวกัน

หมายเหตุ 1: เมื่อฉันพูดว่า "การแฮ็กสองครั้ง" ฉันกำลังพูดถึงการแฮชรหัสผ่านสองครั้งเพื่อพยายามทำให้มันถูกบดบังมากขึ้น ฉันไม่ได้พูดเกี่ยวกับเทคนิคในการแก้ไขปัญหาการชนกัน

หมายเหตุ 2: ฉันรู้ว่าฉันต้องเพิ่มเกลือแบบสุ่มเพื่อให้ปลอดภัยจริงๆ คำถามคือว่า hashing สองครั้งด้วยอัลกอริทึมเดียวกันช่วยหรือเจ็บกัญชา


2
Hash(password)และHash(Hash(password))ไม่มั่นคงเท่ากัน ทั้งขาดความคิดของความหมายของการรักษาความปลอดภัย นั่นคือผลลัพธ์จะแตกต่างจากการสุ่ม ยกตัวอย่างเช่นเป็นMD5("password") 5f4dcc3b5aa765d61d8327deb882cf99ฉันรู้ว่ากัญชา MD5 ของpasswordและมันเป็นความแตกต่างจากการสุ่ม คุณควรใช้ HMAC แทน พิสูจน์แล้วว่าปลอดภัยและเป็น PRF
jww

คำตอบ:


267

การแฮ็รหัสผ่านครั้งเดียวไม่ปลอดภัย

ไม่แฮชหลายตัวไม่ปลอดภัยน้อยกว่า พวกเขาเป็นส่วนสำคัญของการใช้รหัสผ่านที่ปลอดภัย

การวนซ้ำการแฮชเพิ่มเวลาที่ผู้โจมตีจะลองใช้รหัสผ่านแต่ละรายการในรายชื่อผู้สมัคร คุณสามารถเพิ่มเวลาที่ใช้ในการโจมตีรหัสผ่านจากชั่วโมงเป็นปีได้อย่างง่ายดาย

การทำซ้ำง่ายไม่เพียงพอ

การแฮชเอาท์พุทการแฮงค์เฉพาะกับอินพุตไม่เพียงพอสำหรับความปลอดภัย การวนซ้ำควรเกิดขึ้นในบริบทของอัลกอริทึมที่รักษารหัสผ่านของเอนโทรปี โชคดีที่มีอัลกอริทึมที่เผยแพร่หลายอย่างที่มีการพิจารณาอย่างเพียงพอที่จะให้ความมั่นใจในการออกแบบของพวกเขา

อัลกอริธึมที่ได้รับคีย์ที่ดีเช่น PBKDF2 แทรกรหัสผ่านลงในแต่ละรอบของการแฮชลดความกังวลเกี่ยวกับการชนในเอาต์พุตแฮช PBKDF2 สามารถใช้สำหรับการพิสูจน์ตัวตนด้วยรหัสผ่านตามที่เป็นอยู่ Bcrypt ตามมาด้วยกุญแจด้วยขั้นตอนการเข้ารหัส; ด้วยวิธีนี้หากค้นพบวิธีการย้อนกลับอย่างรวดเร็วของคีย์ผู้โจมตีก็ยังต้องทำการโจมตีแบบธรรมดาที่รู้จัก

วิธีแบ่งรหัสผ่าน

รหัสผ่านที่จัดเก็บต้องการการป้องกันจากการโจมตีออฟไลน์ หากรหัสผ่านไม่ได้รับการชำระก็สามารถถูกทำลายได้ด้วยการโจมตีด้วยพจนานุกรมที่คำนวณล่วงหน้า (ตัวอย่างเช่นการใช้ Rainbow Table) มิเช่นนั้นผู้โจมตีจะต้องใช้เวลาในการคำนวณแฮชสำหรับแต่ละรหัสผ่านและดูว่าตรงกับแฮชที่เก็บไว้หรือไม่

รหัสผ่านทั้งหมดไม่น่าจะเท่ากัน ผู้โจมตีอาจค้นหารหัสผ่านสั้น ๆ ทั้งหมดอย่างละเอียด แต่พวกเขารู้ว่าโอกาสในการประสบความสำเร็จอย่างดุเดือดนั้นลดลงอย่างรวดเร็วเมื่อมีตัวละครเพิ่มเติมแต่ละตัว แต่จะใช้รายการสั่งซื้อรหัสผ่านที่เป็นไปได้มากที่สุดแทน พวกเขาเริ่มต้นด้วย "รหัสผ่าน 123" และความคืบหน้าในการใช้รหัสผ่านที่ใช้บ่อยน้อยลง

สมมติว่ารายชื่อผู้โจมตีมีความยาวโดยมีผู้สมัครกว่า 10,000 ล้านคน สมมติว่าระบบเดสก์ท็อปสามารถคำนวณ 1 ล้านแฮชต่อวินาที ผู้โจมตีสามารถทดสอบรายการทั้งหมดของเธอน้อยกว่าสามชั่วโมงหากใช้การวนซ้ำเพียงครั้งเดียว แต่ถ้ามีการใช้ซ้ำ 2,000 ครั้งเวลานั้นจะขยายไปถึงเกือบ 8 เดือน เพื่อกำจัดผู้โจมตีที่มีความซับซ้อนยิ่งขึ้น - ผู้ที่สามารถดาวน์โหลดโปรแกรมที่สามารถแตะพลังของ GPU ของพวกเขาได้ - คุณต้องมีการทำซ้ำมากกว่า

เท่าไหร่พอ?

จำนวนการใช้ซ้ำเป็นการแลกเปลี่ยนระหว่างความปลอดภัยและประสบการณ์ผู้ใช้ ฮาร์ดแวร์เฉพาะที่ผู้โจมตีสามารถใช้งานได้นั้นมีราคาถูก แต่ก็ยังสามารถทำซ้ำได้หลายร้อยล้านครั้งต่อวินาที ประสิทธิภาพของระบบของผู้โจมตีกำหนดระยะเวลาในการทำลายรหัสผ่านที่ได้รับจากการวนซ้ำ แต่แอปพลิเคชันของคุณไม่น่าจะใช้ฮาร์ดแวร์พิเศษนี้ จำนวนการทำซ้ำที่คุณสามารถทำได้โดยไม่ทำให้ผู้ใช้ซ้ำเติมขึ้นอยู่กับระบบของคุณ

คุณอาจอนุญาตให้ผู้ใช้รออีกประมาณหนึ่งวินาทีในระหว่างการตรวจสอบสิทธิ์ จัดทำแพลตฟอร์มเป้าหมายของคุณและใช้การทำซ้ำได้มากเท่าที่คุณสามารถจ่ายได้ แพลตฟอร์มที่ฉันทดสอบ (ผู้ใช้หนึ่งรายบนอุปกรณ์มือถือหรือผู้ใช้หลายรายบนแพลตฟอร์มเซิร์ฟเวอร์) สามารถรองรับPBKDF2 ได้อย่างสะดวกสบายระหว่าง 60,000 และ 120,000 iterations หรือbcryptด้วยต้นทุนที่ 12 หรือ 13

พื้นหลังเพิ่มเติม

อ่าน PKCS # 5 สำหรับข้อมูลที่เชื่อถือได้เกี่ยวกับบทบาทของเกลือและการวนซ้ำในการแปลงแป้นพิมพ์ แม้ว่า PBKDF2 นั้นมีไว้สำหรับสร้างคีย์การเข้ารหัสจากรหัสผ่าน แต่ก็ใช้งานได้ดีเช่นเดียวกับการแฮชแบบทางเดียวสำหรับการตรวจสอบรหัสผ่าน การทำซ้ำ bcrypt แต่ละครั้งจะมีราคาแพงกว่าแฮช SHA-2 ดังนั้นคุณจึงสามารถใช้การทำซ้ำได้น้อยลง แต่ความคิดนั้นเหมือนกัน Bcrypt ยังก้าวไปไกลกว่าโซลูชันที่ใช้ PBKDF2 ส่วนใหญ่โดยใช้คีย์ที่ได้รับเพื่อเข้ารหัสข้อความธรรมดาที่รู้จักกันดี ข้อความตัวเลขที่ได้จะถูกเก็บเป็น "แฮช" พร้อมกับเมตาดาต้าบางตัว อย่างไรก็ตามไม่มีอะไรหยุดคุณจากการทำสิ่งเดียวกันกับ PBKDF2

นี่คือคำตอบอื่น ๆ ที่ฉันเขียนในหัวข้อนี้:


68
การทำอัลกอริทึมช้าโดยเจตนาเป็นวิธีปฏิบัติที่ได้รับการยอมรับเมื่อคุณพยายามป้องกันการโจมตีด้วยพจนานุกรมจากร้านค้ารับรองความถูกต้องที่ถูกบุกรุก เทคนิคนี้เรียกว่า "การเพิ่มความแข็งแกร่งของคีย์" หรือ "การยืดคีย์" ดูen.wikipedia.org/wiki/Key_stretching

17
@RoBorg: มันไม่สำคัญว่าการติดตั้งของคุณจะช้าแค่ไหน แต่การใช้งานของผู้โจมตีจะช้าแค่ไหน: ถ้าแฮชตัวเองช้าลงเป็นพัน ๆ ครั้ง
orip

5
เนื้อหาที่คุณต้องการชนภายในพื้นที่ 128- บิต 0 ถึง 2 ^ 128-1 หากพื้นที่เอาท์พุทของแฮชอัลกอริธึมของ 2 ^ 128 นั้นสมบูรณ์แบบตามหลักทฤษฏีคุณก็จะมีรหัสแทนด้วยตัวอักษร 2 ^ 128 ร่ายมนตร์
jmucchiello

13
@devin - ไม่ใช่ "โซลูชันของฉัน" เป็นวิธีปฏิบัติที่ได้รับการยอมรับอย่างกว้างขวางสร้างขึ้นในมาตรฐานการเข้ารหัสด้วยรหัสผ่านเช่น PKCS # 5 และแนะนำโดยผู้เชี่ยวชาญเช่น Robert Morris สามารถปรับขนาดได้อย่างมากเนื่องจากเวลาที่ใช้ในการตรวจสอบสิทธิ์ผู้ใช้มีขนาดเล็กในแอปพลิเคชันที่ถูกกฎหมาย มันยากที่จะไต่ระดับเมื่อแอปพลิเคชันของคุณถอดรหัสรหัสผ่านดังนั้นคำแนะนำ แน่นอนว่าพื้นที่การค้นหาของแฮชมีขนาดเล็กกว่ารหัสผ่านที่เป็นไปได้ แต่แม้แต่พื้นที่ 128- บิตนั้นใหญ่เกินไปสำหรับการค้นหาที่โหดร้าย ภัยคุกคามที่จะป้องกันการโจมตีคือพจนานุกรมออฟไลน์
erickson

6
ฉันอ้างถึงความไม่สะดวกสำหรับผู้ใช้แต่ละคน แต่เน้นว่าเซิร์ฟเวอร์จะถูกวางไว้หากคุณมีฐานผู้ใช้จำนวนมากเพราะคุณต้องพึ่งพา CPU ในการโหลดจำนวนการร้องขอที่ช้าลง หมายความว่าหากคุณเพิ่มพลัง CPU มากขึ้นคุณกำลังลดข้อ จำกัด ของผู้โจมตีที่กำลังดุร้าย - อย่างไรก็ตามคุณมีความถูกต้องอย่างสมบูรณ์เกี่ยวกับความสามารถในการปรับขนาดและการปฏิบัติที่เป็นที่ยอมรับอย่างกว้างขวาง ฉันผิดเกี่ยวกับเกือบทุกสิ่งที่ฉันพูดในความคิดเห็นก่อนหน้านี้ ขออภัย :)
DevinB

227

ให้กับผู้ที่บอกว่ามันเป็นที่เชื่อถือได้ของพวกเขาจะถูกต้องโดยทั่วไป การแฮ็ช "คู่" (หรือการขยายตรรกะของการทำซ้ำฟังก์ชันแฮช) จะปลอดภัยอย่างแน่นอนหากทำถูกต้องสำหรับข้อกังวลเฉพาะ

สำหรับผู้ที่พูดว่าไม่ปลอดภัยพวกเขาจะถูกต้องในกรณีนี้ รหัสที่ถูกโพสต์ในคำถามคือไม่ปลอดภัย มาพูดคุยเกี่ยวกับสาเหตุ:

$hashed_password1 = md5( md5( plaintext_password ) );
$hashed_password2 = md5( plaintext_password );

มีคุณสมบัติพื้นฐานสองอย่างของฟังก์ชันแฮชที่เรากังวล:

  1. การต่อต้านภาพก่อน - ได้รับกัญชา$hมันควรจะยากที่จะหาข้อความ$mเช่นนั้น$h === hash($m)

  2. การต้านทานภาพที่สองล่วงหน้า - รับข้อความ$m1ควรหาข้อความ$m2อื่นที่ยากเช่นนั้นhash($m1) === hash($m2)

  3. ความต้านทานการชน - มันควรจะยากที่จะหาคู่ของข้อความ($m1, $m2)เช่นนั้นhash($m1) === hash($m2)(โปรดทราบว่าสิ่งนี้คล้ายกับการต่อต้าน Second-Pre-Image แต่ที่แตกต่างกันตรงนี้ผู้โจมตีมีอำนาจควบคุมข้อความทั้งสอง) ...

สำหรับการจัดเก็บรหัสผ่านสิ่งที่เราสนใจคือการต้านทานภาพล่วงหน้า อีกสองคนจะเป็นที่สงสัยเนื่องจาก$m1เป็นรหัสผ่านของผู้ใช้ที่เราพยายามรักษาความปลอดภัย ดังนั้นหากผู้โจมตีมีอยู่แล้วแฮชจะไม่ป้องกัน ...

การปฏิเสธความรับผิด

ทุกอย่างที่เป็นไปตามจะขึ้นอยู่กับสมมติฐานที่ว่าทุกสิ่งที่เราดูแลเกี่ยวกับPre-ภาพต้านทาน คุณสมบัติพื้นฐานอีกสองอย่างของฟังก์ชั่นแฮชอาจไม่ (และโดยทั่วไปไม่ได้) ค้างไว้ในลักษณะเดียวกัน ดังนั้นข้อสรุปในโพสต์นี้จะใช้ได้เฉพาะเมื่อใช้ฟังก์ชันแฮชสำหรับการจัดเก็บรหัสผ่าน พวกเขาไม่สามารถใช้งานได้โดยทั่วไป ...

มาเริ่มกันเลย

เพื่อประโยชน์ของการสนทนานี้ลองประดิษฐ์ฟังก์ชันแฮชของเราเอง:

function ourHash($input) {
    $result = 0;
    for ($i = 0; $i < strlen($input); $i++) {
        $result += ord($input[$i]);
    }
    return (string) ($result % 256);
}

ตอนนี้มันควรจะชัดเจนว่าฟังก์ชันแฮชนี้ทำอะไร ซึ่งจะรวมค่า ASCII ของอักขระแต่ละตัวของอินพุตเข้าด้วยกันแล้วนำโมดูโลของผลลัพธ์นั้นมาพร้อมกับ 256

ดังนั้นลองทดสอบดู:

var_dump(
    ourHash('abc'), // string(2) "38"
    ourHash('def'), // string(2) "47"
    ourHash('hij'), // string(2) "59"
    ourHash('klm')  // string(2) "68"
);

ตอนนี้เรามาดูกันว่าจะเกิดอะไรขึ้นถ้าเราเรียกใช้ฟังก์ชันรอบสองสามครั้ง:

$tests = array(
    "abc",
    "def",
    "hij",
    "klm",
);

foreach ($tests as $test) {
    $hash = $test;
    for ($i = 0; $i < 100; $i++) {
        $hash = ourHash($hash);
    }
    echo "Hashing $test => $hash\n";
}

ผลลัพธ์นั้น:

Hashing abc => 152
Hashing def => 152
Hashing hij => 155
Hashing klm => 155

อืมว้าว เราได้สร้างการชนกัน !!! ลองดูสาเหตุ:

นี่คือผลลัพธ์ของการแฮชสตริงของเอาต์พุตแฮชที่เป็นไปได้แต่ละรายการ:

Hashing 0 => 48
Hashing 1 => 49
Hashing 2 => 50
Hashing 3 => 51
Hashing 4 => 52
Hashing 5 => 53
Hashing 6 => 54
Hashing 7 => 55
Hashing 8 => 56
Hashing 9 => 57
Hashing 10 => 97
Hashing 11 => 98
Hashing 12 => 99
Hashing 13 => 100
Hashing 14 => 101
Hashing 15 => 102
Hashing 16 => 103
Hashing 17 => 104
Hashing 18 => 105
Hashing 19 => 106
Hashing 20 => 98
Hashing 21 => 99
Hashing 22 => 100
Hashing 23 => 101
Hashing 24 => 102
Hashing 25 => 103
Hashing 26 => 104
Hashing 27 => 105
Hashing 28 => 106
Hashing 29 => 107
Hashing 30 => 99
Hashing 31 => 100
Hashing 32 => 101
Hashing 33 => 102
Hashing 34 => 103
Hashing 35 => 104
Hashing 36 => 105
Hashing 37 => 106
Hashing 38 => 107
Hashing 39 => 108
Hashing 40 => 100
Hashing 41 => 101
Hashing 42 => 102
Hashing 43 => 103
Hashing 44 => 104
Hashing 45 => 105
Hashing 46 => 106
Hashing 47 => 107
Hashing 48 => 108
Hashing 49 => 109
Hashing 50 => 101
Hashing 51 => 102
Hashing 52 => 103
Hashing 53 => 104
Hashing 54 => 105
Hashing 55 => 106
Hashing 56 => 107
Hashing 57 => 108
Hashing 58 => 109
Hashing 59 => 110
Hashing 60 => 102
Hashing 61 => 103
Hashing 62 => 104
Hashing 63 => 105
Hashing 64 => 106
Hashing 65 => 107
Hashing 66 => 108
Hashing 67 => 109
Hashing 68 => 110
Hashing 69 => 111
Hashing 70 => 103
Hashing 71 => 104
Hashing 72 => 105
Hashing 73 => 106
Hashing 74 => 107
Hashing 75 => 108
Hashing 76 => 109
Hashing 77 => 110
Hashing 78 => 111
Hashing 79 => 112
Hashing 80 => 104
Hashing 81 => 105
Hashing 82 => 106
Hashing 83 => 107
Hashing 84 => 108
Hashing 85 => 109
Hashing 86 => 110
Hashing 87 => 111
Hashing 88 => 112
Hashing 89 => 113
Hashing 90 => 105
Hashing 91 => 106
Hashing 92 => 107
Hashing 93 => 108
Hashing 94 => 109
Hashing 95 => 110
Hashing 96 => 111
Hashing 97 => 112
Hashing 98 => 113
Hashing 99 => 114
Hashing 100 => 145
Hashing 101 => 146
Hashing 102 => 147
Hashing 103 => 148
Hashing 104 => 149
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 146
Hashing 111 => 147
Hashing 112 => 148
Hashing 113 => 149
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 147
Hashing 121 => 148
Hashing 122 => 149
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 148
Hashing 131 => 149
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 149
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 160
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 160
Hashing 179 => 161
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 160
Hashing 188 => 161
Hashing 189 => 162
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 160
Hashing 197 => 161
Hashing 198 => 162
Hashing 199 => 163
Hashing 200 => 146
Hashing 201 => 147
Hashing 202 => 148
Hashing 203 => 149
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 147
Hashing 211 => 148
Hashing 212 => 149
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 148
Hashing 221 => 149
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 149
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

สังเกตุแนวโน้มไปสู่ตัวเลขที่สูงขึ้น นั่นกลายเป็นความตายของเรา ใช้แฮช 4 ครั้ง ($ hash = ourHash ($ hash) `, สำหรับแต่ละองค์ประกอบ) จบลงด้วยการให้เรา:

Hashing 0 => 153
Hashing 1 => 154
Hashing 2 => 155
Hashing 3 => 156
Hashing 4 => 157
Hashing 5 => 158
Hashing 6 => 150
Hashing 7 => 151
Hashing 8 => 152
Hashing 9 => 153
Hashing 10 => 157
Hashing 11 => 158
Hashing 12 => 150
Hashing 13 => 154
Hashing 14 => 155
Hashing 15 => 156
Hashing 16 => 157
Hashing 17 => 158
Hashing 18 => 150
Hashing 19 => 151
Hashing 20 => 158
Hashing 21 => 150
Hashing 22 => 154
Hashing 23 => 155
Hashing 24 => 156
Hashing 25 => 157
Hashing 26 => 158
Hashing 27 => 150
Hashing 28 => 151
Hashing 29 => 152
Hashing 30 => 150
Hashing 31 => 154
Hashing 32 => 155
Hashing 33 => 156
Hashing 34 => 157
Hashing 35 => 158
Hashing 36 => 150
Hashing 37 => 151
Hashing 38 => 152
Hashing 39 => 153
Hashing 40 => 154
Hashing 41 => 155
Hashing 42 => 156
Hashing 43 => 157
Hashing 44 => 158
Hashing 45 => 150
Hashing 46 => 151
Hashing 47 => 152
Hashing 48 => 153
Hashing 49 => 154
Hashing 50 => 155
Hashing 51 => 156
Hashing 52 => 157
Hashing 53 => 158
Hashing 54 => 150
Hashing 55 => 151
Hashing 56 => 152
Hashing 57 => 153
Hashing 58 => 154
Hashing 59 => 155
Hashing 60 => 156
Hashing 61 => 157
Hashing 62 => 158
Hashing 63 => 150
Hashing 64 => 151
Hashing 65 => 152
Hashing 66 => 153
Hashing 67 => 154
Hashing 68 => 155
Hashing 69 => 156
Hashing 70 => 157
Hashing 71 => 158
Hashing 72 => 150
Hashing 73 => 151
Hashing 74 => 152
Hashing 75 => 153
Hashing 76 => 154
Hashing 77 => 155
Hashing 78 => 156
Hashing 79 => 157
Hashing 80 => 158
Hashing 81 => 150
Hashing 82 => 151
Hashing 83 => 152
Hashing 84 => 153
Hashing 85 => 154
Hashing 86 => 155
Hashing 87 => 156
Hashing 88 => 157
Hashing 89 => 158
Hashing 90 => 150
Hashing 91 => 151
Hashing 92 => 152
Hashing 93 => 153
Hashing 94 => 154
Hashing 95 => 155
Hashing 96 => 156
Hashing 97 => 157
Hashing 98 => 158
Hashing 99 => 150
Hashing 100 => 154
Hashing 101 => 155
Hashing 102 => 156
Hashing 103 => 157
Hashing 104 => 158
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 155
Hashing 111 => 156
Hashing 112 => 157
Hashing 113 => 158
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 156
Hashing 121 => 157
Hashing 122 => 158
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 157
Hashing 131 => 158
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 158
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 151
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 151
Hashing 179 => 152
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 151
Hashing 188 => 152
Hashing 189 => 153
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 151
Hashing 197 => 152
Hashing 198 => 153
Hashing 199 => 154
Hashing 200 => 155
Hashing 201 => 156
Hashing 202 => 157
Hashing 203 => 158
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 156
Hashing 211 => 157
Hashing 212 => 158
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 157
Hashing 221 => 158
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 158
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

เราได้คอตัวเองลงไป 8 ค่า ... ที่ไม่ดี ... ฟังก์ชั่นเดิมของเราแมปบนS(∞) S(256)นั่นคือเราได้สร้างฟังก์ชั่น surjectiveทำแผนที่ไป$input$output

เนื่องจากเรามีฟังก์ชั่น Surjective เราจึงไม่รับประกันว่าการแมปสำหรับชุดย่อยของอินพุตจะไม่มีการชน (ในความเป็นจริงในทางปฏิบัติพวกเขาจะ)

นั่นคือสิ่งที่เกิดขึ้นที่นี่! ฟังก์ชั่นของเราไม่ดี แต่นั่นไม่ใช่เหตุผลว่าทำไมสิ่งนี้ถึงได้ผล

MD5สิ่งเดียวกันที่เกิดขึ้นกับ มันแผนที่บนS(∞) S(2^128)เนื่องจากไม่มีการรับประกันว่าการรันMD5(S(output))จะเป็นแบบInjectiveซึ่งหมายความว่าจะไม่เกิดการชน

มาตรา TL / DR

ดังนั้นเนื่องจากการป้อนเอาต์พุตกลับไปที่md5โดยตรงสามารถสร้างการชนได้ทุกการวนซ้ำจะเพิ่มโอกาสในการชน นี่เป็นการเพิ่มเชิงเส้นซึ่งหมายความว่าในขณะที่ชุดผลลัพธ์ของ2^128ลดลง แต่ก็ไม่ได้ลดลงเร็วพอที่จะเป็นข้อบกพร่องที่สำคัญ

ดังนั้น,

$output = md5($input); // 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities

ยิ่งคุณทำซ้ำมากเท่าไหร่

การแก้ไข

โชคดีสำหรับเรามีวิธีแก้ไขปัญหานี้เล็กน้อย : ป้อนข้อมูลบางอย่างลงในการทำซ้ำเพิ่มเติม:

$output = md5($input); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities    

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

เดี๋ยวก่อนใช่มั้ย ลองทดสอบด้วยourHash()ฟังก์ชันของเรากัน สลับเป็น$hash = ourHash($input . $hash);100 ซ้ำ:

Hashing 0 => 201
Hashing 1 => 212
Hashing 2 => 199
Hashing 3 => 201
Hashing 4 => 203
Hashing 5 => 205
Hashing 6 => 207
Hashing 7 => 209
Hashing 8 => 211
Hashing 9 => 204
Hashing 10 => 251
Hashing 11 => 147
Hashing 12 => 251
Hashing 13 => 148
Hashing 14 => 253
Hashing 15 => 0
Hashing 16 => 1
Hashing 17 => 2
Hashing 18 => 161
Hashing 19 => 163
Hashing 20 => 147
Hashing 21 => 251
Hashing 22 => 148
Hashing 23 => 253
Hashing 24 => 0
Hashing 25 => 1
Hashing 26 => 2
Hashing 27 => 161
Hashing 28 => 163
Hashing 29 => 8
Hashing 30 => 251
Hashing 31 => 148
Hashing 32 => 253
Hashing 33 => 0
Hashing 34 => 1
Hashing 35 => 2
Hashing 36 => 161
Hashing 37 => 163
Hashing 38 => 8
Hashing 39 => 4
Hashing 40 => 148
Hashing 41 => 253
Hashing 42 => 0
Hashing 43 => 1
Hashing 44 => 2
Hashing 45 => 161
Hashing 46 => 163
Hashing 47 => 8
Hashing 48 => 4
Hashing 49 => 9
Hashing 50 => 253
Hashing 51 => 0
Hashing 52 => 1
Hashing 53 => 2
Hashing 54 => 161
Hashing 55 => 163
Hashing 56 => 8
Hashing 57 => 4
Hashing 58 => 9
Hashing 59 => 11
Hashing 60 => 0
Hashing 61 => 1
Hashing 62 => 2
Hashing 63 => 161
Hashing 64 => 163
Hashing 65 => 8
Hashing 66 => 4
Hashing 67 => 9
Hashing 68 => 11
Hashing 69 => 4
Hashing 70 => 1
Hashing 71 => 2
Hashing 72 => 161
Hashing 73 => 163
Hashing 74 => 8
Hashing 75 => 4
Hashing 76 => 9
Hashing 77 => 11
Hashing 78 => 4
Hashing 79 => 3
Hashing 80 => 2
Hashing 81 => 161
Hashing 82 => 163
Hashing 83 => 8
Hashing 84 => 4
Hashing 85 => 9
Hashing 86 => 11
Hashing 87 => 4
Hashing 88 => 3
Hashing 89 => 17
Hashing 90 => 161
Hashing 91 => 163
Hashing 92 => 8
Hashing 93 => 4
Hashing 94 => 9
Hashing 95 => 11
Hashing 96 => 4
Hashing 97 => 3
Hashing 98 => 17
Hashing 99 => 13
Hashing 100 => 246
Hashing 101 => 248
Hashing 102 => 49
Hashing 103 => 44
Hashing 104 => 255
Hashing 105 => 198
Hashing 106 => 43
Hashing 107 => 51
Hashing 108 => 202
Hashing 109 => 2
Hashing 110 => 248
Hashing 111 => 49
Hashing 112 => 44
Hashing 113 => 255
Hashing 114 => 198
Hashing 115 => 43
Hashing 116 => 51
Hashing 117 => 202
Hashing 118 => 2
Hashing 119 => 51
Hashing 120 => 49
Hashing 121 => 44
Hashing 122 => 255
Hashing 123 => 198
Hashing 124 => 43
Hashing 125 => 51
Hashing 126 => 202
Hashing 127 => 2
Hashing 128 => 51
Hashing 129 => 53
Hashing 130 => 44
Hashing 131 => 255
Hashing 132 => 198
Hashing 133 => 43
Hashing 134 => 51
Hashing 135 => 202
Hashing 136 => 2
Hashing 137 => 51
Hashing 138 => 53
Hashing 139 => 55
Hashing 140 => 255
Hashing 141 => 198
Hashing 142 => 43
Hashing 143 => 51
Hashing 144 => 202
Hashing 145 => 2
Hashing 146 => 51
Hashing 147 => 53
Hashing 148 => 55
Hashing 149 => 58
Hashing 150 => 198
Hashing 151 => 43
Hashing 152 => 51
Hashing 153 => 202
Hashing 154 => 2
Hashing 155 => 51
Hashing 156 => 53
Hashing 157 => 55
Hashing 158 => 58
Hashing 159 => 0
Hashing 160 => 43
Hashing 161 => 51
Hashing 162 => 202
Hashing 163 => 2
Hashing 164 => 51
Hashing 165 => 53
Hashing 166 => 55
Hashing 167 => 58
Hashing 168 => 0
Hashing 169 => 209
Hashing 170 => 51
Hashing 171 => 202
Hashing 172 => 2
Hashing 173 => 51
Hashing 174 => 53
Hashing 175 => 55
Hashing 176 => 58
Hashing 177 => 0
Hashing 178 => 209
Hashing 179 => 216
Hashing 180 => 202
Hashing 181 => 2
Hashing 182 => 51
Hashing 183 => 53
Hashing 184 => 55
Hashing 185 => 58
Hashing 186 => 0
Hashing 187 => 209
Hashing 188 => 216
Hashing 189 => 219
Hashing 190 => 2
Hashing 191 => 51
Hashing 192 => 53
Hashing 193 => 55
Hashing 194 => 58
Hashing 195 => 0
Hashing 196 => 209
Hashing 197 => 216
Hashing 198 => 219
Hashing 199 => 220
Hashing 200 => 248
Hashing 201 => 49
Hashing 202 => 44
Hashing 203 => 255
Hashing 204 => 198
Hashing 205 => 43
Hashing 206 => 51
Hashing 207 => 202
Hashing 208 => 2
Hashing 209 => 51
Hashing 210 => 49
Hashing 211 => 44
Hashing 212 => 255
Hashing 213 => 198
Hashing 214 => 43
Hashing 215 => 51
Hashing 216 => 202
Hashing 217 => 2
Hashing 218 => 51
Hashing 219 => 53
Hashing 220 => 44
Hashing 221 => 255
Hashing 222 => 198
Hashing 223 => 43
Hashing 224 => 51
Hashing 225 => 202
Hashing 226 => 2
Hashing 227 => 51
Hashing 228 => 53
Hashing 229 => 55
Hashing 230 => 255
Hashing 231 => 198
Hashing 232 => 43
Hashing 233 => 51
Hashing 234 => 202
Hashing 235 => 2
Hashing 236 => 51
Hashing 237 => 53
Hashing 238 => 55
Hashing 239 => 58
Hashing 240 => 198
Hashing 241 => 43
Hashing 242 => 51
Hashing 243 => 202
Hashing 244 => 2
Hashing 245 => 51
Hashing 246 => 53
Hashing 247 => 55
Hashing 248 => 58
Hashing 249 => 0
Hashing 250 => 43
Hashing 251 => 51
Hashing 252 => 202
Hashing 253 => 2
Hashing 254 => 51
Hashing 255 => 53

ยังคงมีรูปแบบหยาบมี แต่ทราบว่ามันไม่มากขึ้นของรูปแบบมากกว่าฟังก์ชั่นพื้นฐานของเรา (ซึ่งมีอยู่แล้วค่อนข้างอ่อนแอ)

โปรดสังเกตว่า0และ3กลายเป็นความขัดแย้งแม้ว่าพวกเขาจะไม่ได้อยู่ในการวิ่งครั้งเดียว นั่นเป็นแอปพลิเคชันที่ฉันพูดไว้ก่อนหน้านี้ (ความต้านทานการชนยังคงเหมือนเดิมสำหรับชุดของอินพุตทั้งหมด แต่เส้นทางการชนที่เฉพาะเจาะจงอาจเปิดขึ้นเนื่องจากข้อบกพร่องในอัลกอริทึมพื้นฐาน)

มาตรา TL / DR

ด้วยการป้อนข้อมูลกลับเข้าไปในการวนซ้ำแต่ละครั้งเราจะทำลายการชนที่อาจเกิดขึ้นในการทำซ้ำก่อนหน้าอย่างมีประสิทธิภาพ

ดังนั้นmd5($input . md5($input));ควรจะเป็น ( ในทางทฤษฎีอย่างน้อย) md5($input)เป็นที่แข็งแกร่งเป็น

สิ่งนี้สำคัญหรือไม่

ใช่. นี้เป็นหนึ่งในเหตุผลที่ PBKDF2 แทนที่ PBKDF1 ในRFC 2898 ลองพิจารณาลูปด้านในของทั้งสอง ::

PBKDF1:

T_1 = Hash (P || S) ,
T_2 = Hash (T_1) ,
...
T_c = Hash (T_{c-1}) 

cการนับซ้ำอยู่ที่ไหนโดยPใช้รหัสผ่านและSเป็นเกลือ

PBKDF2:

U_1 = PRF (P, S || INT (i)) ,
U_2 = PRF (P, U_1) ,
...
U_c = PRF (P, U_{c-1})

ที่ PRF เป็นเพียง HMAC แต่สำหรับจุดประสงค์ของเราที่นี่สมมุติว่าPRF(P, S) = Hash(P || S)(นั่นคือ PRF ของ 2 อินพุตเหมือนกันพูดคร่าวๆเหมือนแฮชกับทั้งสองตัวต่อกัน) มันไม่มากแต่สำหรับวัตถุประสงค์ของเรามันเป็น

ดังนั้น PBKDF2 จึงรักษาความต้านทานการชนของHashฟังก์ชันพื้นฐานโดยที่ PBKDF1 ไม่ได้

การผูกทั้งหมดเข้าด้วยกัน:

เรารู้ถึงวิธีการแฮชอย่างปลอดภัย ในความเป็นจริง:

$hash = $input;
$i = 10000;
do {
   $hash = hash($input . $hash);
} while ($i-- > 0);

โดยทั่วไปแล้วจะปลอดภัย

ทีนี้มาดูว่าทำไมเราถึงต้องการแฮชลองวิเคราะห์การเคลื่อนที่ของเอนโทรปี

กัญชาจะใช้เวลาอยู่ในชุดที่ไม่มีที่สิ้นสุด: และผลิตขนาดเล็กชุดกลางอย่างต่อเนื่องS(∞) S(n)การทำซ้ำครั้งถัดไป (สมมติว่าอินพุตถูกส่งกลับเข้าไป) แผนที่S(∞)เข้าสู่S(n)อีกครั้ง:

S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)

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

อย่างไรก็ตามมีประโยชน์ในการวนซ้ำ มันทำให้กระบวนการ hashing ช้าลงอย่างผิด ๆ และนั่นเป็นเหตุผลที่การทำซ้ำอาจเป็นความคิดที่ดี ในความเป็นจริงมันเป็นหลักการพื้นฐานของอัลกอริทึมการแฮ็กรหัสผ่านที่ทันสมัยที่สุด

ช้าเป็นสิ่งที่ดีเพราะมันเป็นการต่อสู้กับภัยคุกคามความปลอดภัยหลัก: บังคับให้เดรัจฉาน ยิ่งเราสร้างอัลกอริทึมการแฮชที่ช้ากว่าผู้โจมตีที่ยากกว่าต้องทำงานเพื่อโจมตีแฮ็กรหัสผ่านที่ถูกขโมยไป และนั่นเป็นสิ่งที่ดี !!!


1
$output = md5($output); // < 2^128 possibilities--- มันเป็นที่เข้มงวดจริงๆ<หรือ<=?
zerkms

2
@zerkms: มันไม่เคร่งครัดอะไรเลย เราจำเป็นต้องทราบรายละเอียดที่เฉพาะเจาะจงของฟังก์ชั่นพื้นฐาน ( md5()ในกรณีนี้) เพื่อที่จะทราบได้อย่างแน่นอน แต่โดยทั่วไปก็จะเป็น<และไม่ได้<=... จำไว้ว่าเรากำลังพูดถึงเกี่ยวกับขนาดของชุดที่$outputสำหรับทุกคน$inputsที่เป็นไปได้ ดังนั้นหากเรามีการชนเพียงครั้งเดียวมันจะเป็น<เช่น<นั้น
ircmaxell

2
@ TomášFejfarฉันคิดว่าคำถามนั้นไม่ได้เกี่ยวกับการชนโดยทั่วไป แต่เป็นการชนในชุดเอาต์พุตที่เข้มงวด (2 ^ 128 เอาต์พุตแต่ละตัวกว้าง 128 บิต) นั่นอาจเป็น Injective แต่เท่าที่ฉันรู้ว่าการพิสูจน์ทั่วไปนั้นเป็นไปไม่ได้ (เป็นเพียงตัวอย่างการพิสูจน์โดยการชนกันของอัลกอริทึมเฉพาะ) พิจารณาฟังก์ชั่นแฮชที่เพียงแค่คืนค่าอินพุตหากเป็น 128 บิต (และแฮ็กเป็นอย่างอื่น) โดยทั่วไปแล้วมันจะ surjective แต่เมื่อเลี้ยงเอาท์พุทของมันก็มักจะฉีด ... นั่นคือจุดของการแข่งขัน ...
ircmaxell

3
ขอให้เรายังคงอภิปรายนี้ในการแชท
ircmaxell

6
สำหรับผู้ที่ต้องการประหยัดเวลาโดยไม่ต้องไปตรวจสอบว่าการสนทนาระหว่าง Dan & ircmaxell เสร็จสิ้นแล้วก็ทำได้ดี : Dan เห็นด้วยกับ ircmaxell
jeromej

51

ใช่การแฮชอีกครั้งจะลดพื้นที่การค้นหา แต่ไม่สำคัญ - การลดประสิทธิภาพไม่มีนัยสำคัญ

การ hashing อีกครั้งจะเพิ่มเวลาที่ใช้ในการเดรัจฉาน แต่การทำเช่นนั้นเพียงสองครั้งก็ไม่ดี

สิ่งที่คุณต้องการจริงๆคือการแฮรหัสผ่านด้วยPBKDF2ซึ่งเป็นวิธีการพิสูจน์การใช้แฮชที่ปลอดภัยด้วยเกลือและการวนซ้ำ ตรวจสอบการตอบสนอง SOนี้

แก้ไข : ฉันเกือบลืม - ไม่ใช้ MD5 !!!! ใช้แฮชการเข้ารหัสลับที่ทันสมัยเช่นตระกูล SHA-2 (SHA-256, SHA-384 และ SHA-512)


2
@DFTR - เห็นด้วย bcrypt หรือ scrypt เป็นตัวเลือกที่ดีกว่า
orip

อย่าใช้สิ่งเหล่านี้ (ตระกูล SHA-2) ซึ่งสามารถแตกได้ง่ายตรวจสอบcrackstation.netเพื่อพิสูจน์ หากมีสิ่งใดที่ใช้ scrypt หรือ PBKDF2 ซึ่งเป็นฟังก์ชั่นแฮชที่เข้ารหัสด้วยฟังก์ชันการเข้ารหัสแบบคีย์ (KDF)
theodore

3
ในปี 2559 Argon2 และ scrypt เป็นสิ่งที่ทุกคนควรใช้
Silkfire

10

ใช่ - จะลดจำนวนของสตริงที่อาจตรงกับสตริง

ดังที่คุณได้กล่าวไปแล้วกัญชาที่เค็มจะดีกว่ามาก

บทความที่นี่: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/พยายามพิสูจน์ว่าทำไมมันถึงเทียบเท่า แต่ฉันไม่แน่ใจในตรรกะ ส่วนหนึ่งพวกเขาคิดว่าไม่มีซอฟต์แวร์ที่สามารถวิเคราะห์ md5 (md5 (ข้อความ)) แต่เห็นได้ชัดว่ามันค่อนข้างเล็กน้อยในการผลิตตารางรุ้ง

ฉันยังคงยึดติดกับคำตอบของฉันว่ามีแฮ็กชนิด md5 (md5 (ข้อความ)) จำนวนน้อยกว่าแฮช md5 (ข้อความ) เพิ่มโอกาสในการชน (แม้ว่าจะเป็นโอกาสที่ไม่น่าจะเป็น) และลดพื้นที่การค้นหา


5

คำตอบส่วนใหญ่เกิดจากคนที่ไม่มีพื้นฐานด้านการเข้ารหัสหรือความปลอดภัย และพวกเขาคิดผิด ใช้เกลือถ้าเป็นไปได้ที่ไม่ซ้ำกันต่อการบันทึก MD5 / SHA / etc เร็วเกินไปตรงข้ามกับสิ่งที่คุณต้องการ PBKDF2 และ bcrypt ช้าลง (ซึ่งดีมาก) แต่สามารถเอาชนะได้ด้วย ASICs / FPGA / GPUs (ปัจจุบันมีผู้นิยมมาก) ดังนั้นขั้นตอนวิธีการหน่วยความจำที่ยากคือการที่ต้องใช้: ป้อน Scrypt

นี่คือคำอธิบายของคนธรรมดาเกี่ยวกับเกลือและความเร็ว (แต่ไม่เกี่ยวกับอัลกอริธึมที่ยากสำหรับหน่วยความจำ)


4

ฉันแค่มองสิ่งนี้จากมุมมองเชิงปฏิบัติ แฮ็กเกอร์คืออะไรหลังจากนั้น ทำไมการรวมตัวอักขระที่เมื่อใส่ฟังก์ชั่นแฮชจะสร้างแฮชที่ต้องการ

คุณบันทึกแฮชครั้งสุดท้ายเท่านั้นดังนั้นแฮ็กเกอร์จะต้องแฮชหนึ่งแฮช สมมติว่าคุณมีอัตราต่อรองเหมือนกันกับการแฮชที่ต้องการในแต่ละขั้นตอน bruteforce จำนวนแฮชจะไม่เกี่ยวข้อง คุณสามารถทำการแฮชซ้ำอีกหนึ่งล้านครั้งและจะไม่เพิ่มหรือลดความปลอดภัยหนึ่งบิตเนื่องจากในตอนท้ายของบรรทัดยังคงมีแฮชเพียงหนึ่งเดียวเท่านั้นที่จะพังและโอกาสที่จะทำลายมันก็เหมือนกับแฮชใด ๆ

บางทีผู้โพสต์ก่อนหน้านี้คิดว่าอินพุตมีความเกี่ยวข้อง มันไม่ใช่. ตราบใดที่สิ่งที่คุณใส่ลงในฟังก์ชั่นแฮชจะสร้างแฮชที่ต้องการมันจะทำให้คุณผ่านแก้ไขอินพุตหรือป้อนข้อมูลไม่ถูกต้อง

ตอนนี้ตารางสายรุ้งเป็นอีกเรื่องหนึ่ง เนื่องจากตารางรุ้งใช้รหัสผ่านที่ดิบเท่านั้นการ hashing สองครั้งอาจเป็นมาตรการรักษาความปลอดภัยที่ดีเนื่องจากตารางรุ้งที่มีแฮชของแฮชทุกครั้งจะมีขนาดใหญ่เกินไป

แน่นอนฉันแค่พิจารณาตัวอย่างของ OP ที่ให้ซึ่งเป็นเพียงรหัสผ่านแบบธรรมดาที่ถูกแฮช หากคุณรวมชื่อผู้ใช้หรือเกลือลงในแฮชนั่นเป็นอีกเรื่องหนึ่ง การแฮ็ชสองครั้งนั้นไม่จำเป็นเลยเนื่องจากตารางสายรุ้งจะใหญ่เกินไปที่จะใช้งานได้จริงและมีแฮชที่เหมาะสม

ไม่ใช่ผู้เชี่ยวชาญด้านความปลอดภัยที่นี่ แต่นั่นเป็นเพียงสิ่งที่ฉันได้พบจากประสบการณ์ของฉัน


คำตอบนี้ผิดทุกประการ 1. การรู้จักแฮชถัดไปเป็นครั้งสุดท้ายไม่ได้ให้คุณค่าใด ๆ แก่ผู้โจมตีเนื่องจากอินพุตสำหรับแฮชที่ทำซ้ำคือรหัสผ่านซึ่งจะถูกแฮชหลายครั้ง (ไม่ใช่หนึ่งครั้ง) 2. พื้นที่ป้อนข้อมูลเป็นรหัสผ่านพื้นที่ส่งออกเป็นรหัสผ่านที่แฮช พื้นที่ของรหัสผ่านทั่วไปมีขนาดเล็กกว่าพื้นที่ออก 3. ตารางสายรุ้งสำหรับรหัสผ่านแบบแฮชสองครั้งที่ไม่ได้ค่ามีขนาดใหญ่กว่าตารางสายรุ้งสำหรับรหัสผ่านแบบแฮชที่ไม่ได้ใส่ข้อมูล 4. ชื่อผู้ใช้คือเอนโทรปีต่ำเกลือที่ดีเป็นแบบสุ่ม 5. การเติมเกลือไม่ได้แทนที่การวนซ้ำ คุณต้องการทั้งคู่
ผ่อนผัน Cherlin

3

จากสิ่งที่ฉันได้อ่านอาจแนะนำให้ใช้รหัสผ่านซ้ำกันหลายร้อยหรือหลายพันครั้ง

แนวคิดก็คือถ้าคุณสามารถใช้เวลาในการเข้ารหัสรหัสผ่านมากขึ้นมันจะยิ่งทำงานมากขึ้นสำหรับผู้โจมตีที่จะต้องเดารหัสผ่านเพื่อถอดรหัส ดูเหมือนว่าจะเป็นข้อได้เปรียบในการแฮชอีกครั้งไม่ใช่ว่ามันปลอดภัยกว่าการเข้ารหัส แต่มันใช้เวลานานกว่าในการสร้างการโจมตีด้วยพจนานุกรม

แน่นอนว่าคอมพิวเตอร์จะเร็วขึ้นตลอดเวลาดังนั้นข้อได้เปรียบนี้จะลดน้อยลงเมื่อเวลาผ่านไป (หรือคุณต้องเพิ่มการทำซ้ำ)


ฉันพูดถึงเรื่องนี้ในความคิดเห็นอื่นเช่นกัน แต่ en.wikipedia.org/wiki/Key_stretching

2

โดยส่วนตัวแล้วฉันจะไม่ยุ่งกับแฮชหลายตัว แต่ฉันต้องแน่ใจว่าได้แฮชชื่อผู้ใช้ (หรือฟิลด์ ID ผู้ใช้อื่น) เช่นเดียวกับรหัสผ่านดังนั้นผู้ใช้สองคนที่ใช้รหัสผ่านเดียวกันจะไม่ได้แฮชเดียวกัน นอกจากนี้ฉันอาจจะโยนสตริงคงที่อื่น ๆ ลงในสายป้อนข้อมูลด้วยเช่นกันสำหรับการวัดที่ดี

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);

13
ที่จริงแล้วควรเป็นสตริงที่สร้างแบบสุ่มสำหรับผู้ใช้แต่ละรายไม่ใช่ค่าคงที่
Bill the Lizard

7
งานลับคงที่ (และใช้งานได้ง่ายกว่า) หากคุณใส่ชื่อผู้ใช้ตามที่แนะนำ ที่สร้างคีย์เฉพาะผู้ใช้แบบสุ่ม
SquareCog

4
เกลือลับอย่างต่อเนื่องคือความปลอดภัยผ่านความสับสน หาก "ความลับ" แจ้งว่าคุณกำลังใช้ "xxx" + ชื่อผู้ใช้ + รหัสผ่านผู้โจมตีไม่ต้องการแม้แต่ข้อมูลจากตารางของคุณเพื่อเริ่มการโจมตี
Bill the Lizard

8
ฉันไม่คิดว่ามันปลอดภัยผ่านความสับสน เหตุผลในการใช้เกลือคือคุณไม่สามารถคำนวณตารางรุ้งเทียบกับแฮชหลาย md5 ได้พร้อมกัน การสร้างรหัส "xxx" + รหัสผ่าน (เกลือเดียวกัน) จะเกิดขึ้นหนึ่งครั้ง การสร้างตารางสำหรับ "xxx" + ชื่อผู้ใช้ + รหัสผ่านจะแย่กว่าการบังคับให้เดรัจฉาน
FryGuy

5
@Bill the Lizard: "การโจมตีจะลดลงเพื่อสร้างพจนานุกรมหนึ่งเล่มเพื่อโจมตีชื่อผู้ใช้เฉพาะ" เป็นเพียงการโจมตีที่ดุร้าย (ที่เลวร้ายยิ่งกว่านั้นจริงๆเพราะนอกจากการคำนวณแฮชทั้งหมดที่คุณต้องเก็บไว้) ดังนั้นเกลือทำงาน อย่างสมบูรณ์ในกรณีนี้
Kornel

2

ให้เราสมมติว่าคุณใช้อัลกอริทึมการแปลงแป้นพิมพ์: คำนวณ rot13 ใช้อักขระ 10 ตัวแรก หากคุณทำอย่างนั้นสองครั้ง (หรือแม้กระทั่ง 2,000 ครั้ง) ก็เป็นไปได้ที่จะสร้างฟังก์ชั่นที่เร็วกว่า แต่ก็ให้ผลเหมือนกัน

ในทำนองเดียวกันอาจเป็นไปได้ที่จะสร้างฟังก์ชั่นที่เร็วขึ้นซึ่งจะให้ผลลัพธ์เช่นเดียวกับฟังก์ชั่นการแปลงแป้นพิมพ์ซ้ำ ดังนั้นการเลือกฟังก์ชั่นการแฮ็กของคุณจึงมีความสำคัญมากเช่นเดียวกับตัวอย่าง rot13 จะไม่ได้รับการ hashing ซ้ำ ๆ จะช่วยเพิ่มความปลอดภัย หากไม่มีงานวิจัยที่บอกว่าอัลกอริทึมได้รับการออกแบบมาเพื่อการใช้งานแบบเรียกซ้ำดังนั้นจะถือว่าปลอดภัยกว่าที่จะไม่ให้การป้องกันเพิ่มเติมแก่คุณ

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


1

โดยทั่วไปแล้วจะไม่มีการรักษาความปลอดภัยเพิ่มเติมเพื่อแฮชดับเบิลหรือเข้ารหัสสิ่งที่สอง หากคุณสามารถทำลายแฮชหนึ่งครั้งคุณสามารถทำลายมันได้อีกครั้ง แต่โดยทั่วไปแล้วจะไม่ทำร้ายความปลอดภัยในการทำเช่นนี้

ในตัวอย่างการใช้ MD5 อย่างที่คุณอาจทราบว่ามีปัญหาการชนกัน "การแฮ็กสองครั้ง" ไม่ได้ช่วยป้องกันสิ่งนี้จริงๆเนื่องจากการชนกันจะยังคงส่งผลให้แฮชแรกเหมือนกันซึ่งคุณสามารถ MD5 ได้อีกครั้งเพื่อรับแฮชที่สอง

วิธีนี้จะช่วยป้องกันการโจมตีจากพจนานุกรมเช่น "ย้อนกลับฐานข้อมูล MD5" แต่ก็มีการเปลี่ยนแปลง

ในการแทนเจนต์การเข้ารหัสแบบ Double สิ่งที่ไม่มีการรักษาความปลอดภัยเพิ่มเติมใด ๆ เพราะมันจะส่งผลในคีย์ที่แตกต่างกันซึ่งเป็นการรวมกันของสองคีย์ที่ใช้จริง ดังนั้นความพยายามในการค้นหา "คีย์" จึงไม่เพิ่มเป็นสองเท่าเพราะไม่จำเป็นต้องพบสองปุ่ม สิ่งนี้ไม่เป็นความจริงสำหรับการแฮชเนื่องจากผลลัพธ์ของแฮชไม่ได้มีความยาวเท่ากับอินพุตต้นฉบับ


1
ถูกต้องทั้งหมด แต่ฉันแค่ต้องการทราบว่าผลกระทบของการต่อต้านการปะทะกันอย่างรุนแรงต่อ MD5 นั้นมีสัดส่วนเล็กน้อย - สถานการณ์ส่วนใหญ่ที่ใช้ฟังก์ชั่นแฮชของ crypto นั้นไม่ขึ้นอยู่กับความต้านทานการชนที่รุนแรง พวกเขาจะไม่ได้รับผลกระทบจากช่องโหว่นี้
SquareCog

1

การแฮชสองครั้งจะทำให้ฉันรู้สึกเฉพาะเมื่อฉันแฮรหัสผ่านบนไคลเอนต์แล้วบันทึกแฮช (ด้วยเกลือที่แตกต่างกัน) ของแฮชนั้นบนเซิร์ฟเวอร์

ด้วยวิธีนี้แม้ว่าจะมีใครบางคนเจาะเข้าไปในเซิร์ฟเวอร์ (ดังนั้นการเพิกเฉยต่อความปลอดภัยของ SSL) เขาก็ยังไม่สามารถรับรหัสผ่านที่ชัดเจนได้

ใช่เขาจะมีข้อมูลที่จำเป็นในการเจาะเข้าสู่ระบบ แต่เขาจะไม่สามารถใช้ข้อมูลนั้นเพื่อประนีประนอมบัญชีภายนอกที่ผู้ใช้มี และผู้คนรู้จักใช้รหัสผ่านเดียวกันกับสิ่งอื่น

วิธีเดียวที่เขาจะได้รับรหัสผ่านที่ชัดเจนคือการติดตั้ง keygen บนไคลเอนต์ - และนั่นไม่ใช่ปัญหาของคุณอีกต่อไป

ดังนั้นในระยะสั้น:

  1. การแฮชไคลเอ็นต์ครั้งแรกจะปกป้องผู้ใช้ของคุณในสถานการณ์ 'การละเมิดเซิร์ฟเวอร์'
  2. การแฮ็ชครั้งที่สองบนเซิร์ฟเวอร์ทำหน้าที่ปกป้องระบบของคุณหากมีคนสำรองข้อมูลฐานข้อมูลไว้ดังนั้นเขาจึงไม่สามารถใช้รหัสผ่านเหล่านั้นเพื่อเชื่อมต่อกับบริการของคุณได้

1
+1 ฉันรอเพื่อดูคำตอบเช่นนี้เพราะฉันคิดว่าสถานการณ์เดียวกันที่คุณไม่ต้องการเก็บรหัสผ่านข้อความธรรมดาบนไคลเอนต์ แต่ยังไม่ส่งรหัสผ่านที่เข้ารหัสขั้นสุดท้ายผ่านสายที่จะทำ เปรียบเทียบง่าย ๆ กับ DB
ทำเครื่องหมาย

1
ไม่ช่วยเว็บแอป หากเซิร์ฟเวอร์ของคุณถูกบุกรุกรหัสที่เซิร์ฟเวอร์ของคุณส่งไปยังลูกค้าก็ถูกโจมตีเช่นกัน ผู้โจมตีจะปิดใช้งานแฮชฝั่งไคลเอ็นต์ของคุณและเก็บรหัสผ่านแบบดิบ
ผ่อนผัน Cherlin

0

ความกังวลเกี่ยวกับการลดพื้นที่การค้นหานั้นถูกต้องทางคณิตศาสตร์แม้ว่าพื้นที่การค้นหายังคงมีขนาดใหญ่พอสำหรับการใช้งานจริงทั้งหมด (สมมติว่าคุณใช้เกลือ) ที่ 2 ^ 128 อย่างไรก็ตามเนื่องจากเรากำลังพูดถึงรหัสผ่านจำนวนของสตริง 16 ตัวอักษรที่เป็นไปได้ (ตัวอักษรและตัวเลขตัวพิมพ์ใหญ่สัญลักษณ์สองสามตัวที่โยนทิ้ง) มีค่าประมาณ 2 ^ 98 ตามการคำนวณหลังซองจดหมายของฉัน ดังนั้นการลดลงของการรับรู้ในพื้นที่การค้นหาจึงไม่เกี่ยวข้องจริงๆ

นอกเหนือจากนั้นไม่มีความแตกต่างจริง ๆ พูด cryptographically

แม้ว่าจะมีการเข้ารหัสลับแบบดั้งเดิมที่เรียกว่า "hash chain" - เทคนิคที่ช่วยให้คุณทำเทคนิคเจ๋ง ๆ เช่นการเปิดเผยคีย์ลายเซ็นหลังจากถูกใช้งานโดยไม่ต้องเสียสละความสมบูรณ์ของระบบ - ด้วยการประสานเวลาน้อยที่สุด ช่วยให้คุณหลีกเลี่ยงปัญหาการแจกจ่ายกุญแจเริ่มต้นได้อย่างหมดจด โดยพื้นฐานแล้วคุณทำการแฮชชุดใหญ่ของแฮชล่วงหน้า - h (h (h (h (h .... (h)) ... ))))) ใช้ค่า nth เพื่อเซ็นหลังจากช่วงเวลาที่กำหนดคุณส่ง ออกกุญแจแล้วเซ็นชื่อโดยใช้กุญแจ (n-1) ผู้รับสามารถยืนยันว่าคุณได้ส่งข้อความก่อนหน้านี้ทั้งหมดและไม่มีใครสามารถปลอมลายเซ็นของคุณได้ตั้งแต่ช่วงเวลาที่ผ่านไปแล้ว

การทำซ้ำหลายร้อยหลายพันครั้งอย่างที่ Bill แนะนำเป็นเพียงการสูญเสียซีพียูของคุณ .. ใช้คีย์ที่ยาวกว่านี้ถ้าคุณกังวลเกี่ยวกับคนที่ทำลาย 128 บิต


1
การแฮชซ้ำคือการชะลอการแฮชอย่างแม่นยำ นี่คือคุณลักษณะด้านความปลอดภัยที่สำคัญในการเข้ารหัสตามรหัสผ่าน ดูลิงค์สำหรับ PCKS5 และ PBKDF2
orip

0

ตามคำแนะนำหลายข้อในบทความนี้ขอแนะนำว่ามีบางกรณีที่อาจปรับปรุงความปลอดภัยและกรณีอื่น ๆ ที่ทำให้เจ็บปวดอย่างแน่นอน มีทางออกที่ดีกว่าที่จะปรับปรุงความปลอดภัยอย่างแน่นอน แทนที่จะเพิ่มจำนวนเป็นสองเท่าของจำนวนที่คุณคำนวณแฮชให้เพิ่มขนาดเกลือของคุณเป็นสองเท่าหรือเพิ่มจำนวนบิตที่ใช้แฮชแฮ็กสองเท่าหรือทำทั้งสองอย่าง! แทนที่จะเป็น SHA-245 ให้กระโดดขึ้นไปที่ SHA-512


สิ่งนี้ไม่ตอบคำถาม
Bill the Lizard

1
การแฮ็ชสองครั้งไม่คุ้มค่ากับความพยายาม แต่เพิ่มขนาดแฮชของคุณเป็นสองเท่า ฉันคิดว่านี่เป็นจุดที่มีคุณค่ามากขึ้น
Stefan Rusek

-1

การแฮ็ชสองครั้งนั้นน่าเกลียดเพราะมันเป็นไปได้มากกว่าที่ผู้โจมตีสร้างตารางขึ้นมาเพื่อแฮชส่วนใหญ่ ดีกว่าคือเกลือ hash ของคุณและผสม hashes เข้าด้วยกัน นอกจากนี้ยังมี schema ใหม่สำหรับ "sign" hash (โดยทั่วไปคือ salting) แต่ในลักษณะที่ปลอดภัยกว่า


-1

ใช่.

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

ทำใช้รหัสผ่านได้ที่ได้รับการออกแบบมาโดยถอดรหัสอำนาจที่จะเป็นกัญชารหัสผ่านที่มีประสิทธิภาพและทนต่อทั้งแรงเดรัจฉานและการโจมตีเวลาพื้นที่ สิ่งเหล่านี้รวมถึง bcrypt, scrypt และในบางสถานการณ์ PBKDF2 แฮชที่ใช้ glibc SHA-256 เป็นที่ยอมรับเช่นกัน


-1

ฉันกำลังจะออกไปบนกิ่งและบอกว่ามันปลอดภัยมากขึ้นในบางสถานการณ์ ... อย่าลงคะแนนฉันเลย!

จากมุมมองทางคณิตศาสตร์ / การเข้ารหัสมันมีความปลอดภัยน้อยลงด้วยเหตุผลที่ฉันแน่ใจว่ามีคนอื่นจะให้คำอธิบายที่ชัดเจนกว่าที่ฉันสามารถทำได้

อย่างไรก็ตามมีฐานข้อมูลขนาดใหญ่ของ MD5 แฮชซึ่งมีแนวโน้มที่จะมีข้อความ "รหัสผ่าน" มากกว่า MD5 ของมัน ดังนั้นโดยการแฮ็กสองครั้งคุณจะลดประสิทธิภาพของฐานข้อมูลเหล่านั้น

แน่นอนถ้าคุณใช้เกลือความได้เปรียบนี้ (ข้อเสีย) จะหายไป

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