Password ถูกเก็บในระบบยังไง ? ทำไม ไม่เก็บ Password จริง ๆ ไว้เลยละ

ปัจจุบัน เราใช้งานระบบคอมพิวเตอร์ในการทำอะไรกันเยอะมาก ๆ ตั้งแต่การค้นหาข้อมูล ยันเก็บเรื่องสำคัญ ๆ ที่เป็นความลับไว้ในนั้น ซึ่งการจะเข้าถึงความลับได้นั้น มันจะต้องอาศัยสิ่งที่เรียกว่า Authentication หรือสั้น ๆ ว่า Auth หรือภาษาไทยเราเรียกว่า การยืนยันตัวตน ซึ่งสิ่งที่เราใช้เอามายืนยันตัวตนกันเยอะมาก ๆ คือ การใช้ Username และ Password แต่เราเคยสงสัยกันมั้ยว่า ในโปรแกรมมันเก็บพวก Password ยังไง ให้เจ้าของโปรแกรมก็ยังไม่รู้เลยว่า Password ที่เรากรอกไปคืออะไร ?

ถ้า Password ถูกเก็บตรง ๆ จะเป็นยังไง

ถ้าคิดผ่าน ๆ เราอาจจะคิดว่า Password ที่เรากรอกเข้าไปในระบบมันถูกเก็บตรง ๆ แบบนั้นเลย เช่น เรากรอกไปว่า 1234 ในระบบก็จะเก็บ 1234 ลองคิดต่อว่า ถ้าเกิดนักพัฒนาทำแบบนี้ไปมันจะเกิดอะไรขึ้น

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

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

นอกจากนั้น การที่เก็บ Password ตรง ๆ ยังทำให้เจ้าของโปรแกรมรู้อีกว่า เราใช้ Password อะไร และถ้าเจ้าของโปรแกรมเกิดวันนึงตื่นมาโป๊ะบ๊ะอะไรขึ้นมา อยากจะเอา Password เราไปขาย เขาก็ทำได้ จาก 2 เหตุผลนี้ทำให้การเก็บ Password ตรง ๆ ไม่ใช่เรื่องดีมาก ๆ

Hashing ความลับของการเก็บ Password

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

เราจะเห็นว่า Password อันที่ 2 และ 3 สามารถ Hash เป็นค่าที่ 2 ได้ ทำให้ Hash Function มีความสัมพันธ์แบบ Many-to-one นั่นเอง

โดยที่ Hash เป็น Function ทางคณิตศาสตร์ประเภทนึงที่มีความสัมพันธ์แบบ Many-to-one โดยที่เราจะใส่ Password ของเราลงไป และ ผ่าน Function ออกมาเป็นค่าที่ผ่านการ Hashed แล้ว และนำไปเก็บ

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

เราใส่ Password A และ Password B ไป แต่มันดัน Hash ออกมาได้ Password ตัวเดียวกัน นี่แหละ ปัญหา

เมื่อกี้เราบอกว่า Hash Function มีความสัมพันธ์แบบ Many-to-one ระหว่าง Password และ Hashed Value ทำให้ 1 Hashed Value มาจากได้หลาย Password กล่าวคือ เราอาจจะมี Password บางตัวที่เมื่อ Hash แล้วมันได้ผลลัพธ์เหมือนกับเราใช้ Password อีกตัว Hash เราเรียกอาการนี้ว่า Hash Collision

ถามว่า อาการนี้เราทำให้มันหายไปเลยได้มั้ย คำตอบคือ ไม่ได้ แต่ !!! เราหลีกเลี่ยง หรือทำให้โอกาสที่มันจะเกิดน้อยลงได้ ด้วยการเพิ่มความยาวของค่า Hash เข้าไป เช่น Hashing Function โบราณ ๆ หน่อยอย่าง CRC-32 ใช้ความยาวทั้งหมด 32-bit พอมาเจอกับข้อมูลที่มีจำนวนเยอะ ๆ หน่อยก็ทำให้โอกาสที่จะเจออาการชนกันก็มีเยอะมาก จนตอนนี้ CRC-32 เป็น Hash Function ที่ไม่แนะนำให้ใช้แล้ว

Hash Function ใหม่ ๆ หลายตัวก็ถูกคิดค้นออกมา เพื่อทำให้การชนกันมัน และ การเดาย้อนกลับ ทำได้ยากขึ้นมาก ๆ เช่น bcrypt, scrypt และ Argon2

เกลือเค็มแต่ดี

ลองนึกภาพตามว่า ถ้า User A ใช้รหัสผ่าน 123456 และ User B ใช้รหัสผ่านเดียวกัน ดังนั้นเมื่อเวลาเราจะเก็บรหัสผ่าน ค่าที่ Hash แล้วที่อยู่ในฐานข้อมูลของเราก็จะเหมือนกันเป๊ะเลย เพราะมันใช้วิธีเดียวกัน กับรหัสผ่านเดียวกันในการ Hash ยังไงก็ได้ค่าเดียวกันแน่นอน ซึ่งนั่นไม่ใช่เรื่องดีเลย

จากข้อมูลตรงนี้ ทำให้เจ้าของโปรแกรม หรือ คนที่เจาะเข้ามารู้เลยว่า คนกลุ่มนี้ใช้ Password เดียวกัน อาจจะเอาไป Brute Force ด้วยรหัสที่ง่าย ๆ ถ้าเข้าคนนี้ได้ คนที่เหลือที่มี Hashed Password เดียวกันก็ควรจะเข้าได้เช่นกัน นั่นก็ยังไม่ปลอดภัยในการใช้งานเท่าไหร่ ทำให้เราจำเป็นที่จะต้องเพิ่มอีกสิ่งนึงเข้าไป เป็นข้อความที่อาจจะเอามาต่อ หรือใส่ก่อนหน้า Password เพื่อให้ Hash ออกมาแล้วมันไม่เหมือนกัน เราเรียกข้อมูลที่มาเติมว่า Salt

เราอาจจะสุ่ม Salt แล้วเก็บไว้ได้ ทำให้ แม้ว่า 2 User ใช้ Password เดียวกัน แต่เมื่อเรา Hash ออกมา มันจะได้ไม่เหมือนกัน เพราะตอนเราจะ Hash เราเติม Salt ที่ไม่เหมือนกันลงไปด้วย

ตัวอย่างถ้าเราเอาไปใช้งานกับระบบ Authentication ก็คือ เราอาจจะ Random Salt และเก็บไว้คู่กับแต่ละ User ไปเลย และพอเราจะเก็บรหัสผ่าน เราก็เอา Salt และ รหัสผ่านนั้นแหละ มาต่อกัน และค่อยเข้า Hash Function ที่เราเลือกเท่านี้ถึง User A จะมี Password ตัวเดียวกับ User B แต่ Hashed Value ก็จะไม่เหมือนกันนั่นเอง

Hash ต่างจาก Encrypt อย่างไร ?

เป็นคำถามที่ดีเลย สิ่งที่เหมือนกันคือ ทั้งคู่เป็น Function ที่ใช้ในการแปลงค่าเหมือนกัน แต่สิ่งที่ต่างกันคือ ความสัมพันธ์ของฉันและเธอ อิ้ววว ความสัมพันธ์ของ Function สิ อะถูกแล้ว ที่ Hash เป็น Function แบบ Many-to-one ที่ 1 ค่า Hash สามารถมาจากได้หลาย Password ทำให้ การที่เราจะแปลงค่า Hash กลับไปเป็น Password เดิม ทำได้ยาก หรือแทบทำไม่ได้เลย ทำให้ Password ของเรายังคงเป็นความลับอยู่

Encryption เมื่อเรา เข้ารหัสแล้ว เราสามารถใช้ กุญแจ (Key) เพื่อ ถอดรหัสกลับมาเป็นข้อความเดิมได้ ทำให้ไม่ปลอดภัยเท่ากับการทำ Hash

แต่กลับกัน Encrypt หรือการเข้ารหัส พวกนี้เราจะใช้ Function แบบ One-to-One หรือก็คือ 1 Password แปลงไปเป็นได้แค่ 1 Encrypted เท่านั้น ทั้งไปและกลับ นั่นทำให้เมื่อเรา เข้ารหัส ข้อมูล เรายังสามารถที่จะใช้ Key ในการ ถอดรหัส กลับมาเป็นข้อมูลเดิมได้

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

สรุป

การเก็บ Password ในปัจจุบัน เรานิยมการทำ Hash กับ Password เพื่อทำให้ Password ที่ถูก Hash แล้ว ไม่สามารถย้อนกลับมาเป็น Password อันเดิมได้ (หรือ ทำได้ยากมาก กอไก่ ล้านตัว บน Hashing Function ใหม่ ๆ) ทำให้ไม่ว่าค่า Hash ที่เป็น Password จะหลุดออกไป คนที่แอบเอาไปก็ยังไม่รู้อยู่ดีว่า Password จริง ๆ มันคืออะไร แต่ Hash ก็ยังมีโอกาสที่จะชนกันได้ ทำให้เราจำเป็นที่จะต้องเติม Salt ซึ่งจะมาจากการสุ่มให้แต่ละ User ไม่เหมือนกัน ทำให้ เมื่อ User มีการใช้ Password ซำ้กันในระบบ ก็จะทำให้รู้ได้ยากขึ้นนั่นเอง ทำให้ปลอดภัยต่อผู้ใช้มากขึ้น และเราในฐานะผู้ใช้ก็วางใจได้มากขึ้นว่า Password ของเราไม่ได้เก็บกันโจ้ง ๆ

สำหรับคนที่เรียนคอมพิวเตอร์ มันยังมีลงลึกอีกนะว่า Hashing Function มันมีกี่ประเภทอะอะไรบ้าง และ เมื่อเกิดการชนกันจริง ๆ เราจะทำยังไงได้บ้าง เช่น Chaining, Probing และ Double Hashing หรือคำถามที่ว่า ทำไม bcrypt ถึงค่อนข้างทนต่อการทำ Brute Force (เพราะมันใช้หลาย CPU Cycle เยอะอยู่ ในการ Hash)