安全なプログラミングのレッスン 1: IV を再利用しないでください
最近の Samsung の脆弱性に関する記事を書きました [こちら] のですが、あるコメントでは次のように書かれていました…「これは古いバグで、IV (初期化ベクトル) の再利用は非常に基本的な問題のようです。」一見すると、このコメントは十分な詳細を述べていないかもしれないので、「バグ」について説明し、それが衝撃的に悪いコーディングであることを示したいと思います…保護の観点からはほとんど怠慢であり、見られる可能性さえあります意図的なバックドアとして。
そして、「非常に基本的な問題」の場合、それはおそらく「非常に悪いコーディング」であるはずであり、この「バグ」は信頼できる環境内では決して見られるべきではありません。これは、初心者向けの脆弱性があり、暗号化がどのように機能するかに関する知識がほぼ完全に欠如していることを示しています。論文はここにあります[1]:
実際、これは WEP をもう一度やり直したようなもので、WEP Wifi 方式には小さな IV (初期化ベクトル) があり、それが展開されたときは、暗号ストリームを XOR するだけで平文を検出することができました。スリープ状態のプログラムは、1 日以内にあらゆる Cisco アクセス ポイントをクラックする可能性があります。幸いなことに、現在は IV を再利用しない WPA-2 を使用しています。
このようなコードがユーザーのデバイスに近づくことがあれば、心配する必要があることを示したいと考えています。実際、携帯電話にバックドアがあるとしたら、それはこれかもしれません。
「バグ」について読みたい場合は、ここをお試しください。
Samsung Galaxy デバイスの暗号化バグ: 信頼された実行環境 (TEE) を破壊する悪い「バグ」
では、この「バグ」がどれほどヤバいのかを説明します。サイバーセキュリティに興味がある人なら、AES GCM がストリーム暗号であることを知っているはずです。これにより、秘密キー値とソルト値 (IV - 初期化ベクトル) を取得し、疑似無限キーストリームを生成します。平文はキーストリームと単純に XOR 演算されて、暗号文が生成されます。
固定のソルト値は常に同じ平文に対して同じキーストリームを生成するため、ソルト値は常にランダムである必要があり、暗号ストリームを XOR することでキーストリームを明らかにし、最終的に平文を明らかにすることができます。キーラッピングの場合、平文は暗号化キーであるため、TEE が使用する暗号化キーが明らかになります。
IV を再利用すると、イブは暗号ストリームを XOR してキーストリーム (K) を明らかにできるようになります。そこから、彼女はすべての暗号ストリームを復号化できますが、単に暗号ストリームと K を XOR 演算するだけです。
コーディング
AES GCM (Galois Counter Mode) は、AES のストリーム暗号モードです。CTR モードに基づいていますが、ストリーム暗号に変換されます。これにより、暗号化/復号化プロセスの待ち時間が短くなり、処理が高速になります。これに加えて、認証用の AEAD モードが統合されています。ただし、GCM はストリーム暗号モードであるため、再利用 IV攻撃に対して無防備です。これにより、暗号の IV (初期化ベクトル) は 2 つの暗号メッセージで同じになります。次に、2 つの暗号ストリームを XOR 演算して、暗号ストリーム キー ( K ) を明らかにします。その後、任意の暗号ストリームとKを XOR 演算することで平文を明らかにすることができます。
それでは、これを行うためのコードを試してみましょう。ここでは、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 はクラウドベースのシステム内でも実行されるため、TEE の作成に関する知識を高める必要があります。
参照
[1] Shakevsky, A.、Ronen, E.、および Wool, A. (2022)。Trust Dies in Darkness: Samsung の TrustZone キーマスター設計に光を当てる。暗号学 ePrint アーカイブ。