2 คะแนน โดย GN⁺ 2024-05-08 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ใน Go 1.22 มีการเปลี่ยนให้ทั้งแพ็กเกจ math/rand เดิมและแพ็กเกจ math/rand/v2 ที่เพิ่งเพิ่มเข้ามา ใช้ตัวสร้างเลขสุ่มที่ปลอดภัยเชิงเข้ารหัส ทำให้ได้ความสุ่มที่ดีกว่าเดิม และลดความเสียหายที่อาจเกิดขึ้นอย่างมากเมื่อผู้พัฒนาเผลอใช้ math/rand แทน crypto/rand

ความต่างระหว่างความสุ่มเชิงสถิติกับความสุ่มเชิงเข้ารหัส

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

ตัวสร้าง math/rand ใน Go 1

  • ใช้วิธี Linear-feedback shift register (LFSR)
  • มีปัญหาที่สถานะภายในถูกเปิดเผยได้ทั้งหมด เพราะประกอบด้วยเวกเตอร์ของ uint64 จำนวน 607 ค่า
  • หากอ่านค่าจากตัวสร้าง 607 ค่า ก็จะเปิดเผยสถานะทั้งหมดและคาดเดาค่าถัดไปได้

ตัวสร้าง PCG ของ math/rand/v2

  • ใช้อัลกอริทึม PCG ของ Melissa O'Neill เป็น LCG แบบ 128 บิตที่มีการ post-processing
  • สถานะทั้งหมดคือเลข 128 บิตเพียงตัวเดียว โดยอัปเดตด้วยการคูณและบวกแบบ 128 บิต
  • ใน Go ใช้ฟังก์ชัน scramble แบบอิงการคูณแทนแบบอิง XOR ตามข้อเสนอของ O'Neill เพื่อผสมบิตให้เข้มข้นขึ้น
  • แม้จะคำนวณมากกว่าตัวสร้างของ Go 1 แต่ใช้หน่วยความจำเก็บสถานะน้อยกว่ามาก ไวต่อค่าเริ่มต้นน้อยกว่า และยังผ่านการทดสอบเชิงสถิติที่ตัวสร้างอื่นไม่ผ่าน
  • แต่ PCG ก็ยังไม่ใช่สิ่งที่คาดเดาไม่ได้อย่างแท้จริง

ความสุ่มเชิงเข้ารหัส

  • ในที่สุดแล้ว ระบบปฏิบัติการต้องรวบรวมความสุ่มจริงจากสัญญาณรบกวนของอุปกรณ์ทางกายภาพ
  • เมื่อรวบรวมความสุ่มได้เพียงพอแล้ว (256 บิตขึ้นไป) ก็สามารถขยายด้วยแฮชเชิงเข้ารหัสหรืออัลกอริทึมเข้ารหัส เพื่อสร้างลำดับเลขสุ่มที่ยาวได้ตามต้องการ
  • แพ็กเกจ crypto/rand ของ Go ทำหน้าที่ abstract ความแตกต่างของอินเทอร์เฟซระบบปฏิบัติการเหล่านี้ และให้ใช้อินเทอร์เฟซเดียวกันคือ rand.Read

ตัวสร้าง ChaCha8Rand

  • เป็นตัวสร้างใหม่ที่ดัดแปลงมาจากสตรีมไซเฟอร์ ChaCha ของ DJB
  • ใช้ ChaCha8 ซึ่งเป็นเวอร์ชัน 8 รอบ ปลอดภัยและเร็วกว่า ChaCha20 ถึง 2.5 เท่า
  • ใช้ seed ขนาด 32 ไบต์เป็นกุญแจของ ChaCha8 และทุก ๆ 16 บล็อก จะนำ 32 ไบต์ท้ายของบล็อกที่สร้างขึ้นมาเป็นกุญแจของ 16 บล็อกถัดไป เพื่อให้มี forward secrecy
  • rand.Float64, rand.N เป็นต้น ใน math/rand/v2 จะใช้ตัวสร้างนี้เสมอ
  • math/rand ก็ใช้ตัวสร้างนี้เช่นกัน ยกเว้นเมื่อมีการเรียก rand.Seed จึงจะกลับไปใช้ตัวสร้างของ Go 1
  • ตัวรันไทม์ก็ใช้ ChaCha8Rand ในการเลือก hash seed ของ map ใหม่ด้วย

การแก้ปัญหาความผิดพลาดด้านความปลอดภัย

  • Go 1.22 ทำให้ math/rand แข็งแกร่งขึ้น ส่งผลให้โปรแกรมปลอดภัยขึ้นได้โดยไม่ต้องแก้โค้ด
  • ตัวอย่างเช่น หากใช้ math/rand Read ผิดวัตถุประสงค์เพื่อนำไปสร้างกุญแจ ใน Go 1.20 นี่คือปัญหาความปลอดภัยร้ายแรง แต่ใน Go 1.22 จะกลายเป็นเพียงความผิดพลาดธรรมดา
  • แม้แต่งานที่ดูไม่เกี่ยวกับ “การเข้ารหัส” อย่างการสร้าง UUID หรือการกระจายโหลดของเซิร์ฟเวอร์ฝั่งฟรอนต์เอนด์ การใช้ ChaCha8Rand ก็ทำให้แข็งแกร่งกว่าตัวสร้างของ Go 1 มาก

ประสิทธิภาพ

  • ChaCha8Rand มีประสิทธิภาพใกล้เคียงกับตัวสร้างของ Go 1 และ PCG
  • บนโค้ดแบบ 32 บิต ChaCha8Rand เร็วกว่า PCG ที่ต้องใช้การคูณ 128 บิต
  • ด้วยอัลกอริทึมของ math/rand/v2 ที่หลีกเลี่ยงการหาร 64 บิต การทำงาน N(1000) จึงมีบางกรณีที่ ChaCha8Rand หรือ PCG เร็วกว่าตัวสร้างของ Go 1
  • โดยรวมแล้ว ChaCha8Rand ช้ากว่าตัวสร้างของ Go 1 แต่ไม่มีกรณีที่ช้ากว่าเกิน 2 เท่า และบนเซิร์ฟเวอร์ทั่วไปความต่างไม่เกิน 3ns

ความเห็นของ GN⁺

  • การนำ ChaCha8Rand มาใช้ใน Go 1.22 ถือเป็นตัวอย่างการปรับปรุงระดับภาษาที่ดีเยี่ยม ซึ่งยกระดับความปลอดภัยได้มากโดยแทบไม่กระทบประสิทธิภาพ น่าประทับใจที่สามารถปิดช่องพลาดที่ผู้พัฒนามักทำกันบ่อยได้ตั้งแต่ระดับภาษา
  • อย่างที่กล่าวในบทความ ความผิดพลาดลักษณะนี้ไม่ได้เกิดเฉพาะกับ Go แต่พบได้บ่อยในภาษาอื่นเช่นกัน ความปลอดภัยของระบบไม่ควรขึ้นอยู่กับความผิดพลาดของผู้พัฒนาเพียงอย่างเดียว ดังนั้นภาษาอื่นก็ควรเดินหน้าในทิศทางเดียวกับ Go คือใช้ตัวสร้างเลขสุ่มเทียมที่แข็งแกร่งเชิงเข้ารหัส แม้ในงานที่ดูเป็นเลขสุ่มแบบ “คณิตศาสตร์”
  • อย่างไรก็ดี ChaCha8Rand ไม่เหมาะสำหรับใช้ใน primitive การเข้ารหัสอย่าง crypto_box หรือ xchacha20poly1305 หากเป็นงานลักษณะนี้ก็ยังควรใช้ crypto/rand โดยตรง
  • การที่ Go runtime เปลี่ยนมาใช้ ChaCha8Rand แม้กระทั่งกับการเลือก hash seed ของ map ก็ดูน่าประหลาดใจอยู่บ้าง แม้จะยังไม่ชัดว่าจำเป็นต้องใช้เลขสุ่มเชิงเข้ารหัสกับ hash seed หรือไม่ แต่ก็สะท้อนให้เห็นถึงแนวคิดด้านความปลอดภัยของทีมพัฒนาที่ต้องการปิดโอกาสการโจมตีที่น่าปวดหัวตั้งแต่ต้นทาง
  • เมื่อคุณภาพของ math/rand ซึ่งเป็นแพ็กเกจมาตรฐานระดับภาษาดีขึ้นแล้ว ต่อไปก็น่าจะมีการใช้งาน math/rand โดยตรงในแอปพลิเคชันมากขึ้น หากมีโปรเจกต์ที่ก่อนหน้านี้ต้องใช้ไลบรารีเลขสุ่มแยกต่างหากเพราะกังวลเรื่องความสามารถในการคาดเดาของ math/rand การเปลี่ยนแปลงครั้งนี้ก็น่าจะให้ประโยชน์อย่างชัดเจน

1 ความคิดเห็น

 
GN⁺ 2024-05-08
ความคิดเห็นจาก Hacker News

สรุปได้ดังนี้:

  • ใน Go 1.20 ฟังก์ชัน Read ของแพ็กเกจ math/rand ถูก deprecated และพบกรณีที่มีการนำไปใช้ผิดแทน crypto/rand ซึ่งนำไปสู่ความผิดพลาดในการใช้ตัวสร้างเลขสุ่มเชิงกำหนดที่ไม่ปลอดภัยด้านความมั่นคงปลอดภัย
  • การเปลี่ยนตัวสร้างเลขสุ่มเริ่มต้นของ Go ให้เป็น CSPRNG (ตัวสร้างเลขสุ่มเทียมที่ปลอดภัยเชิงเข้ารหัส) เป็นแนวทางที่ดีกว่าสำหรับความปลอดภัย และควรให้เลือกใช้ PRNG แบบชัดเจนเฉพาะเมื่อจำเป็นจริง ๆ
  • เครื่องมือวิเคราะห์แบบสแตติกอย่าง gosec หรือ golangci-lint จะแจ้งเตือนเมื่อมีการใช้ math/rand
  • แพ็กเกจ math/rand/v2 ใช้ไซเฟอร์ ChaCha8 และ seed จาก system entropy ทำให้ดูเหมือนว่า "ปลอดภัย" แต่ก็ยังไม่เหมาะสำหรับงานที่อ่อนไหวด้านความปลอดภัย ซึ่งควรใช้ crypto/rand
  • math/rand ของ Go 1 หากอธิบายให้แม่นยำคือ additive lagged Fibonacci generator
  • math/rand ตัวใหม่ แม้ในกรณีแย่ที่สุดก็ยังมีความเร็วราวครึ่งหนึ่งของตัวสร้างเลขสุ่มแบบไม่ปลอดภัยเดิม และในเบนช์มาร์กส่วนใหญ่แทบไม่ต่างกันเลย Go กำลังสร้างสมดุลที่เหมาะสมระหว่างความปลอดภัยและประสิทธิภาพใน standard library
  • ถูกมองว่าเป็นแนวทางที่เป็นมิตรกับนักพัฒนาและช่วยป้องกันความผิดพลาดแบบเดียวกับ java.util.Random ของ Java
  • มีการตั้งคำถามถึงเหตุผลที่ใช้ ChaCha8 และเหตุใดจึงไม่ใช้ block cipher ที่รองรับ hardware acceleration เช่น AES-GCM