XAES-256-GCM: AEAD แบบ nonce ขยาย
(words.filippo.io)- XAES-256-GCM คือสเปก AEAD ใหม่ที่ใช้คีย์ 256 บิตและ nonce 192 บิต เพื่อทำให้การจัดการ nonce ใน API การเข้ารหัสระดับสูงมีความเสี่ยงน้อยลง
- nonce ที่ใหญ่ขึ้นทำให้สามารถใช้วิธีสร้างค่าใหม่อัตโนมัติจาก CSPRNG ของระบบปฏิบัติการสำหรับทุกข้อความได้ โดยตั้งเป้าความเสี่ยงการชนกันไว้ที่ระดับ 2⁻³² สำหรับข้อความ 2⁸⁰ รายการ
- ภายในเป็นโครงสร้าง nonce ขยายที่สร้างคีย์อนุพันธ์และ nonce 96 บิตจากคีย์อินพุตและ nonce ขนาดใหญ่ แล้วใช้ AES-256-GCM มาตรฐานตามเดิม
- อิมพลีเมนต์อ้างอิงของ Go ใช้เพียง
crypto/cipherและcrypto/aesและมีขนาดไม่ถึง 100 บรรทัด โดยการเรียกใช้ AES-256 ต่อข้อความ 3 ครั้งนั้นบางส่วนสามารถคำนวณล่วงหน้าได้ - ด้วยการให้ความสำคัญกับการปฏิบัติตาม FIPS 140 และความเข้ากันได้กับไลบรารี จึงสามารถใช้เป็นตัวเลือกสำหรับ API AEAD แบบไม่ต้องจัดการ nonce ร่วมกับ XChaCha20Poly1305 และ AES-GCM-SIV ได้
AEAD ที่มี nonce ขนาดใหญ่สำหรับ API ระดับสูง
- XAES-256-GCM เป็นอัลกอริทึม authenticated encryption with associated data (AEAD) ที่ใช้คีย์ 256 บิตและ nonce 192 บิต
- เป้าหมายของการออกแบบสรุปได้เป็น 3 ข้อ
- รองรับ nonce ขนาดใหญ่ ที่การสุ่มสร้างยังปลอดภัย แม้จำนวนข้อความจะมากจนแทบไม่จำกัด
- ปฏิบัติตาม FIPS 140 ได้อย่างสมบูรณ์และตรงไปตรงมา
- ติดตั้งใช้งานได้ง่ายบนไลบรารีเข้ารหัสทั่วไป
- ด้วย nonce ขนาดใหญ่ จึงสามารถสร้าง API ที่อ่าน nonce ใหม่จาก CSPRNG ของระบบปฏิบัติการสำหรับทุกข้อความได้ โดยไม่ต้องให้ผู้ใช้คำนวณ birthday bound เอง
- โดยให้ความสำคัญกับการปฏิบัติตามข้อกำหนดและความเข้ากันได้ก่อน จึงนำไปใช้กับงานที่ต้องการ AEAD ได้แม้ในสภาพแวดล้อมที่ใช้ AEAD แบบ nonce ใหญ่ชนิดอื่นได้ยาก
โครงสร้าง nonce ขยายที่ใช้ AES-256-GCM เดิมโดยตรง
- XAES-256-GCM เป็น โครงสร้าง nonce ขยาย ที่วางอยู่บน AEAD เดิม เช่นเดียวกับ XChaCha20Poly1305
- คำนวณคีย์อนุพันธ์และ nonce อนุพันธ์สำหรับ AES-256-GCM ภายในจากคีย์อินพุตและ nonce 192 บิต
- คีย์อินพุตและ nonce คือ
K,N - คีย์และ nonce อนุพันธ์สำหรับ AES-256-GCM คือ
Kₓ,Nₓ
- คีย์อินพุตและ nonce คือ
- สร้าง
Kₓด้วยการเรียกAES-256ₖและส่วนหน้าของ nonce และใช้ 96 บิตท้ายของ nonce อินพุตเป็นNₓ - ต้องเรียก
AES-256ₖ3 ครั้งต่อข้อความ- หนึ่งในนั้นสามารถ คำนวณล่วงหน้า ได้สำหรับคีย์ที่กำหนด
- อีกสองครั้งสามารถใช้ key schedule เดียวกันซ้ำได้
การติดตั้งใช้งานสั้นและอธิบายได้ด้วยองค์ประกอบมาตรฐาน
- อิมพลีเมนต์อ้างอิงของ Go มีทั้งการปรับให้เหมาะสมด้วยการคำนวณล่วงหน้าและ boilerplate ส่วนใหญ่ โดยมีขนาดไม่ถึง 100 บรรทัด
- อิมพลีเมนต์ของ Go ใช้เพียง
crypto/cipherและcrypto/aesจากไลบรารีมาตรฐาน - XAES-256-GCM ยังสามารถอธิบายได้ด้วย KDF มาตรฐาน NIST SP 800-108r1 และ NIST AES-256-GCM AEAD มาตรฐาน
- KDF เป็น counter-based KDF
- PRF คือ CMAC-AES256
- คีย์อินพุตคือ
Kin - label คืออักขระ ASCII
Xหรือ0x58 - context คือ 96 บิตแรกของ nonce อินพุต
- ขนาด counter คือ 16 บิต
- ละเว้นฟิลด์
Lแบบเลือกได้ - เอาต์พุตคือคีย์อนุพันธ์ 256 บิต
- คีย์อนุพันธ์และ 96 บิตสุดท้ายของ nonce อินพุตจะถูกป้อนเข้า AES-256-GCM
- จากการเลือกพารามิเตอร์นี้ เมื่อตัดชั้นนามธรรมของ KDF และ CMAC ออก ก็จะช้ากว่าและซับซ้อนกว่าวิธีเรียก AES-256 กับ counter โดยตรงเพียงเล็กน้อย
- พารามิเตอร์ชุดเดียวกันนี้ยังรองรับใน API ระดับสูงของ OpenSSL ด้วย
อิมพลีเมนต์ของบุคคลที่สามและ test vectors
- ณ การแก้ไขวันที่ 2024-06-29 มีการเพิ่มอิมพลีเมนต์ของบุคคลที่สามแล้ว
- อิมพลีเมนต์ของ Web Cryptography API ใช้
CryptoKeyแบบ AES-CBC 256 บิต - ในสเปกมี test vectors สำหรับสอง code path หลัก
MSB₁(L) = 0MSB₁(L) = 1
- ยังมี accumulated randomized tests ที่สรุปการสุ่มทำซ้ำ 10,000 ครั้งหรือ 1,000,000 ครั้งด้วย
การยกเลิก /11 และทางเลือกอื่น
- แนวคิดก่อนหน้านี้เคยใช้ชื่อ
XAES-256-GCM/11แต่ในสเปกสุดท้ายได้ยกเลิก/11 /11เป็นการปรับประสิทธิภาพ และเนื่องจากหนึ่งในเหตุผลที่ใช้ AES-GCM คือ การปฏิบัติตาม FIPS 140 การเปลี่ยนจำนวนรอบจึงทำให้สูญเสียความสอดคล้องตามข้อกำหนด- หากไม่ได้มีเป้าหมายเรื่องการปฏิบัติตาม FIPS 140 ก็มีทางเลือกหลายแบบ
- AES-GCM-SIV
- โครงสร้าง AEAD สมัยใหม่ที่อิงกับ AES core
- ส่วน Alternatives ในสเปกเปรียบเทียบแต่ละทางเลือกกับ XAES-256-GCM
ตำแหน่งใน Go และ API AEAD แบบไม่ต้องจัดการ nonce
- XAES-256-GCM มุ่งเป็น AEAD ที่ปลอดภัย เรียบง่าย ปฏิบัติตามข้อกำหนดได้ และทำงานร่วมกันได้
- กรณีใช้งานหลักคือ API ระดับสูง แบบที่ต้องการเพิ่มเข้าไปใน Go
- XAES-256-GCM ถูกออกแบบมาให้เสริม XChaCha20Poly1305 และ AES-GCM-SIV และเป็นผู้สมัครสำหรับการติดตั้งใช้งาน API AEAD แบบไม่ต้องจัดการ nonce ตามสมมุติฐาน
- เนื่องจากไม่ต้องการเพิ่มโครงสร้างเฉพาะของ Go เข้าไปในไลบรารีมาตรฐานของ Go จึงต้องการความคิดเห็นจากผู้ดูแลไลบรารีเข้ารหัสรายอื่น
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
การออกแบบฉลาดมาก: เพราะเป็นแบบ อิง CMAC จึงสามารถ derive คีย์ด้วย AES-CBC ได้แม้ไม่มี primitive ระดับล่าง
หากมองจากมุมของ AES-CBC จะเริ่มด้วย
L = AES-CBC-256ₖ(iv = 0¹²⁸, plaintext = 0¹²⁸)[:16]เพื่อสร้างK1จากนั้นเข้ารหัสM1,M2เพื่อได้Kₓแล้วใช้Nₓ = N[12:]ประมาณนี้มองได้ว่า AES-CBC-256 คืนเฉพาะบล็อก 128 บิตแรกของ ciphertext แล้วทิ้งบล็อก padding ไป และแม้จะปิด padding ไม่ได้ ก็แค่ต้องเรียก AES ด้วยคีย์เดียวกันเพิ่มอีก 3 ครั้งเมื่อเทียบกับการใช้งานระดับล่าง ซึ่งก็ไม่ได้แย่นัก
มี implementation บน JS ที่ใช้ WebCrypto API โดยอาศัยคุณสมบัตินี้อยู่ที่ https://github.com/dchest/xaes และยังรองรับคุณสมบัติของ
CryptoKeyเช่น รับCryptoKeyสำหรับ AES-CBC มาใช้ได้ตรง ๆ และบันทึกลง IndexedDB ด้วยextractable=falseใน pseudocode นั้น ดูเหมือนว่าครึ่งหนึ่งของตัวเลขนับเป็นจำนวนไบต์ ส่วนอีกครึ่งนับเป็นจำนวนบิต และถ้าไม่รู้ algorithm อยู่แล้วแทบจะบอกไม่ได้ว่าอันไหนเป็นอันไหน
เช่น
N[:12]ดูเหมือน 12 ไบต์ แต่0¹²⁸คือ 16 ไบต์ และXคืออักขระจริง ๆ'X'หรือบิตสตริง01011000แต่Lเป็นตัวแปร ไม่ใช่บิตสตริง01001100ดูชัดเจนว่านักคณิตศาสตร์ไม่ได้ชอบ notation ที่ไม่กำกวม เท่าคนสายวิทยาการคอมพิวเตอร์
0¹²⁰10000111คือบิตสตริงที่แทนสัมประสิทธิ์ของพหุนามตัวแรกตามลำดับพจนานุกรม ในบรรดาพหุนามลดทอนไม่ได้ดีกรีbที่มีจำนวนเทอมที่ไม่เป็นศูนย์น้อยที่สุด สำหรับ ขนาดบล็อกของ block cipher ระดับล่างbที่ CMAC ใช้ไม่มีเจตนาแอบแฝงอะไร
งานนี้ช่วยหลีกเลี่ยงปัญหานั้นได้ง่าย ๆ ด้วยการเปลี่ยนทั้งคีย์และ nonce ในการเรียก AES-GCM แต่ละครั้ง
อีกทั้งถ้ามี AES-GCM ก็ใช้เพียง AES “ธรรมดา” ที่โดยทั่วไปหาได้อยู่แล้ว และเลี่ยงโครงสร้างซับซ้อนใหม่ ๆ ที่อาจยังมีจุดอ่อน
overhead ต่อข้อความคือบัฟเฟอร์เล็ก ๆ 2 ชุดที่ต้องเข้ารหัส/ถอดรหัสด้วย AES “ธรรมดา” และ nonce ที่ยาวขึ้นเป็น 192 บิต
[1]: https://frereit.de/aes_gcm/
ดูเหมือนจะขจัดกับดักของ AES-GCM แบบดั้งเดิมที่เมื่อใช้ nonce แบบสุ่มแล้วต้อง เปลี่ยนคีย์ทุก ๆ ประมาณ 2^32 ข้อความ
ใน AES-GCM การชนกันของ nonce เป็นเรื่องร้ายแรง และอย่างน้อยก็ทำให้ผู้โจมตีสามารถเซ็นข้อความใด ๆ ได้
ไม่ได้จำเป็นต้องใช้ nonce แบบสุ่มเสมอไป แต่โดยทั่วไปมักแนะนำ และการทำให้ สอดคล้องกับ FIPS ด้วย primitive สองอย่างคือ counter-based key derivation function กับ GCM ดั้งเดิม ถือว่าค่อนข้างฉลาด
ใน AES-GCM มาตรฐาน 96 บิตไม่เพียงพอสำหรับหลีกเลี่ยงการชนกันแบบสุ่ม จึงต้องใช้การสร้าง nonce แบบกำหนดแน่นอน
อีกทั้งไม่ว่าจะสร้าง nonce อย่างไร เมื่อ counter rollover บล็อกถัดไปก็จะใช้ nonce+counter เหมือนกับบล็อกแรก ดังนั้นหลังจาก 2^32 บล็อกจึงต้องเปลี่ยน nonce หรือคีย์
ยอดเยี่ยมจริง ๆ ถ้ามีสิ่งนี้ตอนที่ผมทำ encrypted filesystem ครั้งสุดท้ายเมื่อไม่กี่ปีก่อนก็คงดี
ในการ deploy filesystem ขนาดใหญ่ การชนกันของ nonce เป็นเรื่องน่ากังวลมาก
2^32 ดูเหมือนเยอะ แต่ถ้าเป็น array ระดับ PB ที่เขียนด้วย 100k IOPS ต่อวินาทีและพึ่งพาความสุ่มของ pseudo-random generator โอกาสชนกันแทบจะรับประกันได้เลย
[1] https://en.m.wikipedia.org/wiki/CAESAR_Competition
มันก็แค่หมายความว่าสองบล็อกใช้คีย์เข้ารหัสเดียวกันร่วมกันไม่ใช่หรือ?
ถ้าไม่รู้ plaintext ของบล็อกใดเลย ก็ไม่เห็นว่ามันจะทำให้ความปลอดภัยของระบบอ่อนลงอย่างไร
อยากให้สิ่งนี้ถูกใช้กับ variant ของ age ที่สอดคล้องกับ FIPS สำหรับการเข้ารหัสไฟล์เพื่อเก็บถาวร
ในการ audit ของภาคธนาคาร age ถูกปฏิเสธสำหรับ use case นี้เพราะใช้ ChaCha และพวกเขาเห็นว่าส่วน public key X25519 ของ age ใช้ได้ ผมเข้าใจว่า X25519 เพิ่งได้รับการอนุมัติจาก NIST เมื่อไม่นานมานี้
ไม่มีประสบการณ์กับ Go แต่ดูจากสเปกของ age แล้วน่าจะเสียบเข้าไปได้ทันที และถ้ามีเวลาอาจลองทำดู
ชื่อน่าจะเรียกว่า “cage” ในความหมายของ “compliant actually good encryption”
1: https://github.com/FiloSottile/age
ในฐานะคนที่ไม่ใช่นักคริปโต ผมสงสัยว่าทำไมใช้ nonce 192 บิต ไม่ใช้ 256 บิต
ในแอปพลิเคชันจริง ไม่น่าจะมองว่าบิตเพิ่มเติมเหล่านั้นเป็นต้นทุน
อาจทำให้อินพุต CMAC ยาวกว่านี้ได้ แต่จะต้องรันฟังก์ชันบล็อก AES-256 หลายครั้งขึ้น และจะเจอปัญหายุ่งยากเรื่องการควบคุมคีย์ใน CMAC key derivation function ด้วย
เหตุผลก็คล้ายกับที่ XChaCha20Poly1305 ใช้ nonce 192 บิต และการสอดคล้องกับ extended-nonce AEAD หลัก ๆ ตัวอื่นก็เป็นข้อดีเล็กน้อยด้วย
บอกว่า “ความเสี่ยงชนกัน 2⁻³² เมื่อมีข้อความ 2⁸⁰ ข้อความ” แต่จะไม่เกิดปัญหาก่อนหน้านั้นหรือ จากข้อเท็จจริงที่ว่า ขนาดบล็อกของ AES มีแค่ 128 บิต?
XAES derive คีย์ขนาดใหญ่ต่อข้อความ จึงให้สิ่งที่มักเรียกว่า การรับประกันที่ดีกว่า birthday bound
ถ้าไม่มีบริบทละเอียดกว่านี้ว่าทำไมถึงคิดว่าจะเป็นปัญหา ก็คงตอบได้ละเอียดกว่านี้ไม่ได้
“ปลอดภัย น่าเบื่อ สอดคล้องข้อกำหนดได้ และทำงานร่วมกันได้”
นี่คือเทคโนโลยีประเภทที่ผมชอบที่สุด
ดีเลย ดีใจที่มี โครงสร้างที่อิง NIST จริง ๆ
แต่ก็น่าเสียดายที่ต้องยอมเสียฟีเจอร์หลายอย่างที่เป็นข้อดีของ NIST key derivation function เช่น label และ context
เข้าใจว่าทำไปเพื่อลดจำนวนการเรียก AES ให้น้อยที่สุด แต่โดยเฉพาะถ้าข้อความยาวกว่าสองสามร้อยไบต์ ผมน่าจะให้ความสำคัญกับการแยกทางคริปโตที่แข็งแรง มากกว่าการประหยัดการเรียก AES ไม่กี่ครั้ง
สุดท้าย nonce GCM แบบสุ่ม ที่ยาวกว่า 96 บิตยังถูกเข้าใจผิดกันมาก และให้การรับประกันที่ดีกว่า nonce 96 บิตจริง ๆ[1]
แน่นอนว่าถ้า derive คีย์ใหม่ได้ทุกข้อความ ทางนั้นย่อมดีกว่าแน่นอน
[1] https://neilmadden.blog/2024/05/23/galois-counter-mode-and-r...