ทำไมต้องใช้ bcrypt ในการ Hash Password
หลังจากบนความที่แล้ว เราได้อธิบายถึงการใช้ Salt เพื่อเพิ่มความปลอดภัยให้กับการจัดเก็บรหัสผ่านของผู้ใช้ในระบบไปแล้ว ในครั้งนี้ เราจะมาอธิบายว่าทำไม bcrypt คืออะไร และทำไมเราถึงควรจะใช้มัน
Disclaimer: ในบทความนี้ผมจะไม่ได้พูดถึงการทำงานของ bcrypt และตัว algorithm โดยลึก เนื่องจากผมก็ไม่ได้มีความรู้ด้าน Cryptography ที่มากพอ
What is it anyway?
bcrypt เป็น password hashing function ที่สร้างขึ้นจากพื้นฐานของ Blowfish cipher โดยการทำงานของ Blowfish cipher ที่การสร้าง key ใหม่ขึ้นมาจะต้องทำการ pre-processโดยใช้เวลาเทียบเก่ากับการเข้ารหัสตัวอักษรขนาด 4KB ซึ่งถือว่าช้ากว่า block cipher รูปแบบอื่นๆ
โดยผลลัพธ์ที่ได้จาก bcrypt จะมีสักษณะดังนี้
- ชุดแรกจะบอกว่าใช้ bcrypt version อะไรในการ hash
- เลขถัดไปคือค่า cost ที่กำหนดความช้าในการทำงาน
- 16 bytes ถัดไปเป็นค่า salt ในรูปแบบ Radix-64 ความยาว 22 ตัวอักษร
- และ 24 bytes สุดท้ายเป็นค่า hash ที่อยู่ในรูปแบบ Radix-64 ความยาว 31 ตัวอักษร
ช้าแล้วใช้ทำไม
ในขณะที่ Hash function อย่างเช่น SHA family ถูกพัฒนาเพื่อความเร็วในการทำงาน แต่ bcrypt ถูกพัฒนาเพื่อให้ทำงานช้า นอกจากนั้นยังไม่พอ มันยังสามารถกำหนดได้ด้วยว่า ต้องการให้ช้าแค่ไหน!
ซึ่งความช้าในการทำงานนี่แหละ ที่เป็นจุดแข็งที่ทำให้คนเลือกใช้ bcrypt ในการ hash รหัสผ่าน บทความที่แล้วผมได้อธิบายไปถึงการใช้ Salt ที่แตกต่างกันให้กับผู้ใช้แต่ล่ะคน ซึ่งจะทำให้การทำ hacker สามารถทำสร้างตารางที่ pre-compute รหัสผ่านที่ใช้บ่อยได้ยากขึ้น แต่การใช้ bcrypt จะทำให้การโจมตีข้างต้นนั้น ทำได้ยากขึ้นไปอีก เนื่องจากว่ามันช้ามาก
ลองนึกภาพนะครับว่า ถ้า Database ของเราหลุดออกไป และมีคนต้องการโจมตีด้วยวิธีการที่บอกไปข้างต้น เขาจะต้องใช้เวลาขนาดไหน ถ้าเรามีข้อมูลผู้ใช้ 1,000 คน โดยผู้ใช้แต่ล่ะคนใช้ salt ที่ไม่เหมือนกัน และรหัสผ่านคนที่ใช้บ่อย(รวมไปถึงคำศัพท์)มีจำนวน 10,000 รหัสผ่าน ดังนั้น ในการ compute เพื่อสร้างตารางค่า hash ตัว hash function จะถูกรันทั้งหมด 1000 * 10000 = 10,000,000 รอบ แล้วถ้าเราเปรียบเทียบความเร็วของ hash function ต่อการ hash 1 ครั้ง จะได้ว่า
- ถ้าใช้เวลา 1ms ต่อ 1 ครั้ง จะต้องใช้เวลาทั้งหมด 10,000,000 * 0.001 = 10,000 วินาที หรือประมาณ 2.78 ชั่วโมง
- ถ้าใช้เวลา 10ms ต่อ 1 ครั้ง จะต้องใช้เวลาทั้งหมด 10,000,000 * 0.01 = 100,000 วินาที หรือประมาณ 27.78 ชั่วโมง
- ถ้าใช้เวลา 1s ต่อ 1 ครั้ง จะต้องใช้เวลาทั้งหมด 10,000,000 * 1 = 10,000,000 วินาที หรือประมาณ 2778 ชั่วโมง หรือเกือบ 4 เดือนเลยทีเดียว
จะเห็นได้ว่ายิ่ง hash function ของเราทำงานช้าลงมากแค่ไหน ระยะเวลาที่ hacker ต้องใช้ในการ compute hash table ก็นานมากเท่านั้น ดังนั้น bcrypt ที่สามารถปรับความช้าการทำงานได้ จึงเป็นที่นิยมเนื่องจากสามารถทนต่อการโจมตีแบบ pre-computation ได้ และอย่างน้อยก็เป็นการถ่วงเวลาหากมีข้อมูลหลุดออกไปจริงๆ hacker ก็ต้องใช้เวลาในการโจมตีนานขึ้น ทำให้ผู้ใช้สามารถมีเวลาไปเปลี่ยนรหัสผ่านเดียวกันที่ใช้กับอย่างอื่นได้ทัน ก่อนที่ hacker จะได้รหัสผ่านไป
bcrypt's performance
อย่างที่เราบอกไปแล้วว่าความช้าของ hash function สามารถเพิ่มความปลอดภัยให้กับข้อมูลของเราได้ ต่อไปเขาจะมาดูว่า bcrypt ที่ว่าทำงานช้าเนี่ย มันช้าสักแค่ไหน โดยการทดสอบนี้ทำขึ้นโดยทีมงานของ auth0 โดยใช้ bcrypt ที่ implement ใน NodeJS ซึ่งรันบน MacBook Pro 2017
- Processor: 2.8 GHz Intel Core i7
- Memory: 16 GB 2133 MHz LPDDR3
- Graphics: Radeon Pro 555 2048 MB, Intel HD Graphics 630 1536 MB
ผลที่ได้ก็คือ
bcrypt | cost: 10, time to hash: 65.683ms
bcrypt | cost: 11, time to hash: 129.227ms
bcrypt | cost: 12, time to hash: 254.624ms
bcrypt | cost: 13, time to hash: 511.969ms
bcrypt | cost: 14, time to hash: 1015.073ms
bcrypt | cost: 15, time to hash: 2043.034ms
bcrypt | cost: 16, time to hash: 4088.721ms
bcrypt | cost: 17, time to hash: 8162.788ms
bcrypt | cost: 18, time to hash: 16315.459ms
bcrypt | cost: 19, time to hash: 32682.622ms
bcrypt | cost: 20, time to hash: 66779.182ms
โดยเมื่อเราเพิ่ม cost ซึ่งเป็นตัวกำหนดความช้าในการทำงานของ bcrypt เวลาที่ใช้ในการทำงานก็จะเยอะขึ้น โดยเมื่อนำไปพล๊อตลงในกราฟจะได้ลักษณะนี้
ดังนั้นการนำ bcrypt ไปใช้จึงมี best practice ว่าเราต้องทำ UX research ก่อนว่าผู้ใช้ส่วนใหญ่จะรอการทำงานของระบบได้นานขนาดไหน และทำการเลือก cost ที่จะทำให้เวลาในการทำงานไม่มากไปกว่าเวลาที่ผู้ใช้จะรอได้