บทที่ 1 ในการเขียนโปรแกรมที่ปลอดภัย: อย่าใช้ IV ของคุณซ้ำ
ฉันเขียนบทความเกี่ยวกับช่องโหว่ล่าสุดของ Samsung [ ที่นี่ ] และความคิดเห็นหนึ่งกล่าวว่า … “มันเป็นบั๊กเก่า การใช้ IV (Initialisation Vectors) ซ้ำดูเหมือนเป็นปัญหาพื้นฐานมาก” จากความคิดเห็นนี้ ความคิดเห็นอาจไม่ลงรายละเอียดเพียงพอ ดังนั้นฉันจะพยายามอธิบาย "จุดบกพร่อง" และหวังว่าจะแสดงให้เห็นว่าเป็นการเขียนโค้ดที่แย่อย่างน่าตกใจ…เกือบจะประมาทเลินเล่อในแง่ของการป้องกัน และอาจมองเห็นได้ด้วยซ้ำ เป็นประตูลับโดยเจตนา
และสำหรับ "ปัญหาพื้นฐานมาก" นั้นควรเป็น "การเข้ารหัสที่แย่มาก" และไม่ควรพบเห็น "ข้อบกพร่อง" นี้ ในสภาพแวดล้อมที่เชื่อถือได้ แสดงให้เห็นถึงการขาดความรู้เกือบทั้งหมดเกี่ยวกับวิธีการทำงานของการเข้ารหัสด้วยช่องโหว่สำหรับผู้เริ่มต้น กระดาษอยู่ที่นี่ [1]:
ในความเป็นจริง มันเหมือนกับ WEP อีกครั้ง และที่วิธี WEP Wifi มี IV ขนาดเล็ก (Initialisation Vector) และเมื่อเปิดตัว เป็นไปได้ที่เพียงแค่ XOR cipher streams และค้นพบข้อความธรรมดา โปรแกรมสลีปสามารถแคร็กจุดเชื่อมต่อของ Cisco ได้ในเวลาน้อยกว่าหนึ่งวัน โชคดีที่ตอนนี้เราใช้ WPA-2 และไม่มี IV ที่ใช้ซ้ำ
ฉันหวังว่าจะแสดงให้เห็นว่าเราควรกังวลหากรหัสเช่นนี้เข้าใกล้อุปกรณ์ของผู้ใช้ อันที่จริง ถ้าเคยมีประตูหลังในโทรศัพท์มือถือ อาจเป็นประตูหลังก็ได้
หากคุณต้องการอ่านเกี่ยวกับ "จุดบกพร่อง" ลองที่นี่:
Crypto Bug ในอุปกรณ์ Samsung Galaxy: การทำลายสภาพแวดล้อมการดำเนินการที่เชื่อถือได้ (TEE)"ข้อผิดพลาด" ที่ไม่ดี
ตอนนี้ฉันจะอธิบายว่า "จุดบกพร่อง" นี้เลวร้ายเพียงใด หากคุณสนใจเรื่องความปลอดภัยในโลกไซเบอร์ หวังว่าคุณจะรู้ว่า AES GCM เป็นรหัสสตรีม ด้วยวิธีนี้ เราจะใช้ค่าคีย์ลับและค่าเกลือ (IV — Initialisation Vector) และสร้างคีย์สตรีมหลอกที่ไม่มีที่สิ้นสุด ข้อความธรรมดาของเราเป็นเพียง XOR-ed พร้อมคีย์สตรีมเพื่อสร้างข้อความรหัสของเรา:
ค่าเกลือควรเป็นค่าสุ่มเสมอ เนื่องจากค่าเกลือคงที่จะสร้างคีย์สตรีมเดียวกันสำหรับข้อความธรรมดาเดียวกันเสมอ และตำแหน่งที่เราสามารถเปิดเผยคีย์สตรีมโดยสตรีมรหัส XOR-ing และในที่สุดก็เปิดเผยข้อความธรรมดา ในกรณีของการรวมคีย์ ข้อความธรรมดาเป็นคีย์เข้ารหัส ดังนั้นคีย์เข้ารหัสที่ใช้โดย TEE จะถูกเปิดเผย
หากเราใช้ IV ซ้ำ อีฟจะสามารถสตรีมรหัส XOR ร่วมกันและเปิดเผยคีย์สตรีม (K) จากที่นั่นเธอสามารถถอดรหัสสตรีมรหัสทุกรายการ แต่เพียงแค่ XOR-ing สตรีมรหัสด้วย K
การเข้ารหัส
AES GCM (โหมดเคาน์เตอร์ Galois) เป็นโหมดเข้ารหัสสตรีมสำหรับ AES มันขึ้นอยู่กับโหมด CTR แต่แปลงเป็นรหัสสตรีม ซึ่งให้เวลาแฝงต่ำในกระบวนการเข้ารหัส/ถอดรหัสและประมวลผลได้รวดเร็ว นอกจากนี้ยังผสานรวมโหมด AEAD สำหรับการรับรองความถูกต้อง แต่เนื่องจาก GCM เป็นโหมดการเข้ารหัสแบบสตรีม จึงเปิดให้ ใช้ การโจมตีIV ซ้ำได้ ด้วยเหตุนี้ IV (Initialization Vector) ของรหัสจะเหมือนกันสำหรับข้อความเข้ารหัสสองข้อความ จากนั้นเราสามารถ XOR ไปยังสตรีมรหัสทั้งสองพร้อมกันเพื่อเปิดเผยรหัสสตรีมรหัส ( K ) จากนั้นเราสามารถเปิดเผยข้อความธรรมดาโดย XOR-ing สตรีมรหัสใด ๆด้วยK
มาลองโค้ดเพื่อทำสิ่งนี้กัน ในกรณีนี้ ฉันจะใช้ Golang เพื่อแสดงหลักการพื้นฐานของวิธีการ ฉันจะใช้คีย์แบบคงที่ในกรณีนี้ (เนื่องจากจะไม่เปลี่ยนแปลงภายใน TEE) ของ “0123456789ABCDEF” (16 ไบต์ — คีย์ 128 บิต) และไม่มีค่าคงที่ของ “0123456789AB” (12 ไบต์ — 96 บิต) [ ที่นี่ ]:
package main
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"os"
)
func xor(a, b []byte, length int) []byte {
c := make([]byte, len(a))
for i := 0; i < length; i++ {
c[i] = a[i] ^ b[i]
}
return (c)
}
func main() {
nonce := []byte("0123456789AB")
key := []byte("0123456789ABCDEF")
block, err := aes.NewCipher(key)
if err != nil {
panic(err.Error())
}
msg1 := "hello"
msg2 := "Hello"
argCount := len(os.Args[1:])
if argCount > 0 {
msg1 = (os.Args[1])
}
if argCount > 1 {
msg2 = (os.Args[2])
}
plaintext1 := []byte(msg1)
plaintext2 := []byte(msg2)
aesgcm, err := cipher.NewGCM(block)
if err != nil {
panic(err.Error())
}
ciphertext1 := aesgcm.Seal(nil, nonce, plaintext1, nil)
ciphertext2 := aesgcm.Seal(nil, nonce, plaintext2, nil)
xor_length := len(ciphertext1)
if len(ciphertext1) > len(ciphertext2) {
xor_length = len(ciphertext2)
}
ciphertext_res := xor(ciphertext1, ciphertext2, xor_length)
fmt.Printf("Message 1:\t%s\n", msg1)
fmt.Printf("Message 2:\t%s\n", msg2)
fmt.Printf("Cipher 1:\t%x\n", ciphertext1)
fmt.Printf("Cipher 2:\t%x\n", ciphertext2)
fmt.Printf("Key:\t\t%x\n", key)
fmt.Printf("Nonce:\t\t%x\n", nonce)
fmt.Printf("XOR:\t\t%x\n", ciphertext_res)
plain1, _ := aesgcm.Open(nil, nonce, ciphertext1, nil)
plain2, _ := aesgcm.Open(nil, nonce, ciphertext2, nil)
fmt.Printf("Decrypted:\t%s\n", plain1)
fmt.Printf("Decrypted:\t%s\n", plain2)
}
Message 1: hello
Message 2: Hello
Cipher 1: 7fcbe7378c2b87a5dfb2803d4fcaca8d5cde86dbfa
Cipher 2: 5fcbe7378cf8c68b82a2b8d705354e8d6c0502cef2
Key: 30313233343536373839414243444546
Nonce: 303132333435363738394142
XOR: 2000000000d3412e5d1038ea4aff840030db841508
Decrypted: hello
Decrypted: Hello
Message 1: hello
Message 2: Cello
Cipher 1: 7fcbe7378c2b87a5dfb2803d4fcaca8d5cde86dbfa
Cipher 2: 54cbe7378c5638db82df34a46172abed62b887aa48
Key: 30313233343536373839414243444546
Nonce: 303132333435363738394142
XOR: 2b000000007dbf7e5d6db4992eb861603e660171b2
Decrypted: hello
Decrypted: Cello
บทสรุป
นี่เป็นการเขียนโค้ดที่แย่มากและฉันไม่คิดว่าเด็กจบใหม่จะใช้งานในระดับนี้ หากทีมพัฒนาที่สร้างโค้ดภายใน TEE ไม่เข้าใจการโจมตีแบบใช้ซ้ำ IV พวกเขาจำเป็นต้องเข้าคอร์สอบรมการเขียนโค้ดที่ปลอดภัยก่อนที่จะสัมผัสกับโค้ดที่เชื่อถือได้อีก หากนี่เป็นการลับหลังโดยเจตนา นั่นก็เป็นอีกเรื่องหนึ่ง ฉันหวังว่ามันเป็นแค่ข้อบกพร่อง แต่เราจำเป็นต้องปรับปรุงความรู้ของเราในการสร้าง TEE เนื่องจากสิ่งเหล่านี้ทำงานภายในระบบบนคลาวด์ด้วย
อ้างอิง
[1] Shakevsky, A., Ronen, E., & Wool, A. (2022) Trust Dies in Darkness: ฉายแสงให้กับการออกแบบ TrustZone Keymaster ของ Samsung เอกสารลับ ePrint วิทยาการเข้ารหัสลับ