Bill Buchanan - Lesson 1 in Secure Programming: Don't Reuse Your IVs
ASecuritySite Podcast - A podcast by Professor Bill Buchanan OBE
Categories:
Blog: https://medium.com/asecuritysite-when-bob-met-alice/lesson-1-in-secure-programming-dont-reuse-your-ivs-5666ddfa9a1c I wrote up an article on a recent Samsung vulnerability [here], and one comment said … “it’s an old bug, reuse of IV (Initialisation Vectors) seem a very basic problem”. On the face of it, the comment perhaps doesn’t go into enough detail, so I’ll try and explain the “bug” and hopefully show that it is shockingly bad coding … almost negligent in terms of protection, and could even be seen as an intentional backdoor. And for a “very basic problem”, it should perhaps be “extremely bad coding”, and this “bug” should never, ever be seen within trusted environments. It shows an almost complete lack of knowledge in how cryptography works, with a novice vulnerability. The paper is here [1]: In fact, it’s like WEP all over again, and where the WEP Wifi method had a small IV (Initialisation Vector), and when it rolled out, it was possible to just XOR cipher streams, and discover the plaintext. The asleep program could crack any Cisco access point in less than a day. Luckily we now use WPA-2, and which does not have the reuse of the IV. I hope to show that we should be worried if code such as this ever gets near a user’s device. In fact, if there was ever a back door in a mobile phone, it could be this one. If you want to read about the “bug”, try here: Crypto Bug in Samsung Galaxy Devices: Breaking Trusted Execution Environments (TEEs) If you use an Apple Macbook, it’s likely that you have a secret enclave for important secrets — such as your encryption… medium.com A bad “bug” Now, I will explain how bad this “bug” is. If you are into cybersecurity, you should hopefully know that AES GCM is a stream cipher. With this, we take a secret key value and a salt value (an IV — Initialisation Vector) and generate a pseudo infinite keystream. Our plaintext is then simply XOR-ed with the keystream to produce our ciphertext: The salt value should then always be random, as a fixed salt value will always produce the same keystream for the same plaintext, and where we can reveal the keystream by XOR-ing cipher streams, and eventually revealing the plaintext. In the case of the key wrapping, the plaintext is an encryption key, and thus the encryption key used by the TEE will be revealed. If we reuse IVs, Eve will be able to XOR cipher streams together and reveal the keystream (K). From there she can decrypt every cipher stream, but simply XOR-ing the cipher stream with K. Coding AES GCM (Galois Counter Mode) is a stream cipher mode for AES. It is based on the CTR mode but converts to a stream cipher. This provides low latency in the encryption/decryption process and is fast to process. Along with this, it integrates AEAD mode for authentication. But as GCM is a stream cipher mode, it is open to a reuse IV attack. With this, the IV (Initialization Vector) of the cipher is the same for two cipher messages. We can then XOR to the two cipher streams together to reveal the cipher stream key (K). We can then reveal the plaintext by XOR-ing any cipher stream with K. So, let’s try some code to do this. In this case, I will use Golang to show the basic principles of the method. I will use a static key in this case (as this would not change within the TEE) of “0123456789ABCDEF” (16 bytes — 128-bit key), and a static nonce of “0123456789AB” (12 bytes — 96 bits) [here]: package mainimport ( "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)} When we run with “hello” and “Hello” we get [here]: Message 1: helloMessage 2: HelloCipher 1: 7fcbe7378c2b87a5dfb2803d4fcaca8d5cde86dbfaCipher 2: 5fcbe7378cf8c68b82a2b8d705354e8d6c0502cef2Key: 30313233343536373839414243444546Nonce: 303132333435363738394142XOR: 2000000000d3412e5d1038ea4aff840030db841508Decrypted: helloDecrypted: Hello If we try “hello” and “Cello”, we can see that there’s a variation in the first byte of the cipher stream: Message 1: helloMessage 2: CelloCipher 1: 7fcbe7378c2b87a5dfb2803d4fcaca8d5cde86dbfaCipher 2: 54cbe7378c5638db82df34a46172abed62b887aa48Key: 30313233343536373839414243444546Nonce: 303132333435363738394142XOR: 2b000000007dbf7e5d6db4992eb861603e660171b2Decrypted: helloDecrypted: Cello The “bug” allowed a user program to request their own IV, which meant that a cracker would continually request the same IV, and then break the cipher, and reveal the encryption key used. This is because the TEE (Trusted Execution Environment) uses key wrapping to encrypt the encryption key with AES GCM, so a cracker can request these wrapped keys, but with their own IV. This then reveals the key that the TEE is using. This is extremely bad coding! Conclusion This is extremely bad coding, and I would not expect this level of implementation from a new graduate. If a development team creating code within a TEE doesn’t understand a reuse IV attack, they need to go on a secure coding training course before they ever touch any more trusted code. If this was an intentional backdoor, that’s a whole other story. I hope, it was just a bug, but we really need to improve our knowledge in the creation of TEEs, as these also run within Cloud-based systems. Reference [1] Shakevsky, A., Ronen, E., & Wool, A. (2022). Trust Dies in Darkness: Shedding Light on Samsung’s TrustZone Keymaster Design. Cryptology ePrint Archive.