- พบ ข้อผิดพลาดในการตรวจสอบจุดบนเส้นโค้ง Edwards25519 ที่ไม่เหมาะสม ในฟังก์ชันระดับล่าง
crypto_core_ed25519_is_valid_point() ของ libsodium
- ฟังก์ชันนี้ควรตรวจสอบว่าจุดนั้นอยู่ในกลุ่มหลักทางคริปโตกราฟีหรือไม่ แต่กลับปล่อยให้บางจุดที่เป็นส่วนหนึ่งของ mixed order (subgroup) ผ่านการตรวจสอบอย่างผิดพลาด
- สาเหตุมาจากข้อผิดพลาดของโค้ดในการตรวจสอบพิกัดภายใน โดย ตรวจเพียง X=0 แต่ละเลยการตรวจสอบ Y=Z ทำให้จุดที่ไม่ถูกต้องอาจถูกมองว่าใช้งานได้
- เวอร์ชันที่แก้ไขแล้วเปลี่ยนให้ตรวจทั้งสองเงื่อนไข (X=0, Y=Z) และ เวอร์ชัน 1.0.20 หรือต่ำกว่า หรือ รีลีสก่อนวันที่ 30 ธันวาคม 2025 ได้รับผลกระทบ
- API ระดับสูง (
crypto_sign_*) ไม่ได้รับผลกระทบ และแนะนำให้ใช้ กลุ่ม Ristretto255 เพื่อความปลอดภัยและประสิทธิภาพที่ดีกว่า
ภาพรวมของโปรเจกต์ libsodium
- libsodium เป็นโปรเจกต์ที่เริ่มต้นเมื่อ 13 ปีก่อน โดยมีเป้าหมายเพื่อ ทำให้การใช้งานคริปโตกราฟีเป็นเรื่องง่ายผ่าน API ที่เรียบง่าย
- ออกแบบมาให้ผู้ใช้ทำงานระดับสูงได้โดยไม่จำเป็นต้องรู้รายละเอียดของอัลกอริทึมภายใน
- ให้ความสำคัญกับ การคงความเข้ากันได้ของ API และรักษาความสม่ำเสมอมาจนถึงปัจจุบันโดยอิงจาก NaCl API
- ผู้ใช้บางส่วนเริ่มใช้งาน ฟังก์ชันระดับล่าง โดยตรงเกินกว่าข้อจำกัดที่ระบุไว้ในเอกสาร ทำให้มีกรณีที่ไลบรารีถูกใช้งานราวกับเป็น ชุดเครื่องมือคริปโตกราฟี มากขึ้น
สาเหตุของบั๊กที่พบ
- ฟังก์ชันที่มีปัญหา:
crypto_core_ed25519_is_valid_point()
- บนเส้นโค้ง Edwards25519 ฟังก์ชันนี้ควรปฏิเสธจุดที่ไม่ได้อยู่ในกลุ่มหลัก (ลำดับ L)
- แต่จุดบางส่วนที่มี mixed order (2L, 4L, 8L เป็นต้น) กลับผ่านการตรวจสอบ
- ภายในมีการตรวจสอบลำดับของจุดโดยคูณด้วย L แล้วตรวจว่าผลลัพธ์เป็น จุดเอกลักษณ์ (identity) หรือไม่
- จุดเอกลักษณ์แสดงได้ในรูป X=0, Y=Z แต่โค้ดเดิม ตรวจเพียง X=0
- ด้วยเหตุนี้ จุดที่ไม่ถูกต้องซึ่งมี Y≠Z จึงอาจถูกมองว่าถูกต้อง
- ตัวอย่าง: หากนำจุด Q ในกลุ่มหลักไปบวกกับจุดลำดับ 2 คือ (0, -1) จะได้ Q+(0, -1) ซึ่งเป็นจุดที่ไม่ถูกต้อง แต่ก่อนแก้ไขกลับผ่านการตรวจสอบ
รายละเอียดการแก้ไข
- แพตช์คอมมิต มีการเปลี่ยนแปลงดังนี้
- โค้ดเดิม:
return fe25519_iszero(pl.X);
- โค้ดที่แก้ไข:
fe25519_sub(t, pl.Y, pl.Z); return fe25519_iszero(pl.X) & fe25519_iszero(t);
- ตอนนี้มีการตรวจทั้งเงื่อนไข X=0 และ Y=Z จึงตรวจสอบได้อย่างถูกต้อง
ขอบเขตผลกระทบ
- อาจได้รับผลกระทบหากเข้าเงื่อนไขต่อไปนี้
- ใช้งานเวอร์ชัน 1.0.20 หรือต่ำกว่า หรือ รีลีสก่อนวันที่ 30 ธันวาคม 2025
- ใช้
crypto_core_ed25519_is_valid_point() เพื่อ ตรวจสอบจุดอินพุตที่ไม่น่าเชื่อถือ
- เป็นผู้ใช้ที่ ลงมือทำงานกับการคำนวณบนเส้นโค้ง Edwards25519 โดยตรง
- อย่างไรก็ตาม ผู้ใช้ส่วนใหญ่จะไม่ได้รับผลกระทบ
- API ระดับสูง (
crypto_sign_*) ไม่ได้ใช้ฟังก์ชันนี้
crypto_scalarmult_ed25519 ไม่มีการรั่วไหลของข้อมูล แม้จะใช้กุญแจสาธารณะที่ไม่ถูกต้อง
- คีย์ที่สร้างด้วย
crypto_sign_keypair และ crypto_sign_seed_keypair จะอยู่ในกลุ่มที่ถูกต้อง
มาตรการที่แนะนำ
- แนะนำให้ใช้ กลุ่ม Ristretto255
- มีอยู่ใน libsodium ตั้งแต่ปี 2019 และ แก้ปัญหาที่เกี่ยวข้องกับ cofactor
- จุดที่ถอดรหัสแล้วจะปลอดภัยโดยอัตโนมัติ ไม่ต้องตรวจสอบเพิ่มเติม
- ให้ ประสิทธิภาพการคำนวณที่เร็วกว่า Edwards25519
- หากอัปเดตไม่ได้ สามารถตรวจสอบได้ด้วย ฟังก์ชันทดแทนในระดับแอปพลิเคชัน ที่เสนอไว้ (
is_on_main_subgroup)
การแจกจ่ายที่แก้ไขแล้วและการสนับสนุน
- หลังพบปัญหา ได้มีการแก้ไขทันที และถูกรวมอยู่ใน ทุกเวอร์ชันเสถียรที่เผยแพร่หลังวันที่ 30 ธันวาคม 2025
- รวมถึง tarball อย่างเป็นทางการ, ไบนารี Visual Studio/MingW, แพ็กเกจ NuGet, บิลด์สำหรับ Android,
swift-sodium xcframework, Rust libsodium-sys-stable, และ libsodium.js
- มีแผนจะออก point release ใหม่เพิ่มเติม
- โปรเจกต์นี้ดูแลโดย ผู้ดูแลเพียงคนเดียว และสามารถสนับสนุนการปรับปรุงอย่างต่อเนื่องได้ผ่าน OpenCollective
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ไลบรารี PHP sodium_compat ก็ได้รับผลกระทบจากปัญหานี้ด้วย
ดูรายละเอียดที่เกี่ยวข้องได้ใน security-advisories PR #756
เย็นนี้ฉันวางแผนจะตรวจสอบ implementation ของ Ed25519 ตัวอื่น ๆ ในระบบนิเวศโอเพนซอร์สทั้งหมด เพื่อดูว่ามีการข้ามขั้นตอนการตรวจสอบแบบเดียวกันหรือไม่
แต่ไม่พบ กรณีที่ติดตั้งใช้งานผิดวิธี แบบช่องโหว่ที่กล่าวถึงข้างต้น
ถ้าฉันไม่ได้ส่งอีเมลไป ก็เป็นไปได้สูงว่ามันไม่ได้อยู่ใน รายการการใช้งาน Ed25519 ของ ianix, หรือฉันมองข้ามไป, หรือไม่ก็เป็น implementation ที่ปลอดภัย
ในช่วง 4 เดือนที่ผ่านมา ฉันกำลังพัฒนา sodium binding สำหรับ Lean4
ตอนนี้มาถึงขั้นของ Ristretto255 แล้ว และเริ่มเข้าใจว่าทำไมผู้เขียนถึงตื่นเต้นกับเทคโนโลยีนี้มาก
Ristretto เป็น API ที่ซับซ้อนสำหรับสร้างพหุนามตามอำเภอใจบน Curve25519 และการทดลองกับมันสนุกมาก
ถ้าผู้เขียนได้เห็นโพสต์นี้ ก็อยากขอบคุณจากใจจริง
เป้าหมายของ Libsodium คือการมอบ API ระดับสูง ไม่ใช่ฟังก์ชันระดับล่าง
มันถูกออกแบบมาเพื่อให้ผู้ใช้ไม่จำเป็นต้องรู้เรื่องอัลกอริทึมภายใน แต่เมื่อเวลาผ่านไปก็มีผู้ใช้ที่เรียกใช้ฟังก์ชันระดับล่างโดยตรงมากขึ้นเรื่อย ๆ
สุดท้าย Libsodium จึงถูกใช้เหมือน ชุดเครื่องมืออัลกอริทึม
สิ่งสำคัญคือการรับรู้ว่าผู้ใช้ต้องการไปในทิศทางใด และไม่ยัดเยียดให้โปรเจ็กต์ต้องถูกใช้ในรูปแบบเดียวเท่านั้น
บางโปรเจ็กต์ล้มเหลวเพราะกลายเป็นเผด็จการในเรื่องนี้
การให้ผู้ที่ไม่ใช่ผู้เชี่ยวชาญใช้งาน primitive ทางคริปโตโดยตรงเป็นเรื่องอันตราย
Libsodium ถูกออกแบบมาเพื่อไม่ให้ผู้ใช้พาตัวเองไปสู่ความเสี่ยง
อุดมคติคือไลบรารีควร ทำให้การใช้งานผิดเป็นไปไม่ได้
ขอแนะนำบทความนี้: “If You're Typing The Letters A-E-S Into Your Code, You're Doing It Wrong”
เพราะฉะนั้นในหลายกรณีการจำกัดบางฟีเจอร์ให้เป็น private หรือ internal จึงเป็นทางเลือกที่เหมาะสม
ฉันไม่แน่ใจว่า Libsodium วางเส้นแบ่งนี้ได้ดีแค่ไหน แต่ความสมดุลเป็นเรื่องสำคัญ
แต่ผู้ใช้บางคนกลับเอามันไปใช้เหมือน ตัวรันงานแบบแบตช์
ฉันเลยแก้บั๊กบางอย่างเพื่อรองรับความต้องการของพวกเขา
สุดท้ายแล้วแค่มีคนใช้งานก็ทำให้ฉันดีใจมากแล้ว
บั๊กครั้งนี้เป็น ข้อผิดพลาดในการตรวจสอบทางคริปโต ที่ละเอียดอ่อนแต่สำคัญ
การตรวจง่าย ๆ อย่าง “ตรวจว่าถูกต้องหรือไม่” แท้จริงแล้วซับซ้อนมาก
หากยอมรับจุดที่อยู่นอก subgroup ลำดับเฉพาะ แม้จะดูเหมือนไม่มีช่องโหว่ในทันที แต่ก็อาจทำลายสมมติฐานของชั้นบนได้
อีกทั้ง primitive ระดับล่างมักถูกนำกลับไปใช้ซ้ำอย่างกว้างขวางกว่าที่ตั้งใจไว้มาก ดังนั้นการตกหล่นเล็ก ๆ ในการตรวจสอบจึงอาจมี ผลกระทบวงกว้าง
ปัญหา subgroup จะเกิดขึ้นก็ต่อเมื่อสร้างโปรโตคอลที่ซับซ้อนกว่าบน Curve25519 เท่านั้น
เพราะแบบนั้นฉันจึงพยายาม map ทุกจุดกลับเข้าสู่ subgroup ลำดับเฉพาะให้ได้เสมอ
Monocypher มีฟังก์ชันขั้นสูงแบบนี้อยู่
ตัวอย่างเช่น
crypto_x25519_dirty_fast()หรือcrypto_elligator_map()ฟังก์ชัน “dirty” เหล่านี้สร้างกุญแจสาธารณะที่ครอบคลุมทั้งเส้นโค้ง ทำให้แยกไม่ออกจากความสุ่ม
หลังจากนั้นก็ยังได้ shared secret เดิมเมื่อทำ X25519 key exchange
ทั้งหมดนี้เป็น ผลจากการออกแบบของ DJB เพราะแม้ public key จะผิดปกติ shared secret ก็ยังถูก map ไปยัง subgroup ลำดับเฉพาะ
สรุปแล้ว Ristretto จำเป็นเฉพาะในกรณีที่ไม่สามารถทำการ map กลับแบบนี้ได้เท่านั้น
แน่นอนว่า abstraction ของกลุ่มลำดับเฉพาะมีประโยชน์ แต่ถ้าใครออกแบบโปรโตคอลแบบนั้นได้ ก็น่าจะรับมือกับ cofactor ที่ไม่เป็น trivial ได้ด้วย
ถ้าคุณทำงานในบริษัทใหญ่ แนะนำให้พิจารณาสนับสนุน Frank ในนามบริษัท
ต่อให้รู้ ก็ดูเหมือนว่าฉันคงต้องสนับสนุนด้วย เงินส่วนตัว มากกว่าจะใช้บัญชีของบริษัท
สงสัยว่า libnacl ได้รับผลกระทบด้วยหรือไม่
ฉันใช้ซอฟต์แวร์ที่คอมไพล์ด้วย libnacl ทุกวัน แต่ไม่มีอะไรที่คอมไพล์ด้วย “libsodium” เลย
เป็นไลบรารีที่ยอดเยี่ยมจริง ๆ
ขอส่งคำขอบคุณไปยัง Frank Denis