Keine Verwendung eines unvorhersagbaren IV mit CBC-Modus
Beschreibung
Keine Verwendung eines unvorhersagbaren IV mit CBC-Modus tritt auf, wenn Software den Cipher Block Chaining (CBC)-Modus für Verschlüsselung verwendet, aber keinen zufälligen, unvorhersagbaren Initialisierungsvektor (IV) für jede Verschlüsselungsoperation verwendet. CBC-Modus erfordert einen einzigartigen IV, um sicherzustellen, dass identische Klartexte unterschiedliche Geheimtexte erzeugen. Die Verwendung eines statischen, vorhersagbaren oder wiederverwendeten IV offenbart Muster in verschlüsselten Daten und ermöglicht Chosen-Plaintext-Angriffe. Der BEAST-Angriff gegen TLS 1.0 nutzte vorhersagbare IVs im CBC-Modus aus.
Risiko
Vorhersagbare oder wiederverwendete IVs kompromittieren die Sicherheitsgarantien der CBC-Modus-Verschlüsselung. Angreifer können erkennen, wenn dieselbe Nachricht wiederholt gesendet wird. Chosen-Plaintext-Angriffe ermöglichen die Entschlüsselung von Nachrichten. Der BEAST-Angriff demonstrierte praktische Ausnutzung gegen HTTPS-Verbindungen. Während CBC mit ordnungsgemäßen IVs sicher ist, macht die Anforderung für korrekte IV-Handhabung es fehleranfällig. Moderne Empfehlungen favorisieren authentifizierte Verschlüsselungsmodi wie GCM.
Lösung
Generieren Sie einen neuen zufälligen IV für jede Verschlüsselungsoperation mit einem kryptografisch sicheren Zufallszahlengenerator. Verwenden Sie niemals IVs mit demselben Schlüssel wieder. Der IV muss nicht geheim sein, aber muss unvorhersagbar sein. Erwägen Sie die Verwendung authentifizierter Verschlüsselungsmodi (GCM, CCM) anstelle von CBC, da sie weniger fehleranfällig sind. Wenn Sie CBC verwenden, stellen Sie den IV immer dem Geheimtext für die Entschlüsselung voran. Verwenden Sie TLS 1.2 oder höher, das CBC-IV-Vorhersageangriffe abschwächt.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Informationsoffenlegung Musteranalyse und Chosen-Plaintext-Angriffe können verschlüsselte Inhalte offenbaren. |
| Integrität | Umfang: Nachrichtenfälschung Padding-Oracle-Angriffe gegen CBC können verschlüsselte Nachrichten modifizieren. |
| Authentifizierung | Umfang: Angriffsfläche Vorhersagbare IVs ermöglichen verschiedene kryptografische Angriffe wie BEAST. |
Beispielcode + Korrigierter Code
Anfälliger Code
# ANFÄLLIG: Statischer IV
from Crypto.Cipher import AES
STATIC_IV = b'0000000000000000' # Derselbe IV jedes Mal!
def encrypt_weak(plaintext, key):
cipher = AES.new(key, AES.MODE_CBC, iv=STATIC_IV)
return cipher.encrypt(pad(plaintext))
# ANFÄLLIG: Null-IV
def encrypt_zero_iv(plaintext, key):
cipher = AES.new(key, AES.MODE_CBC, iv=bytes(16)) # Alles Nullen!
return cipher.encrypt(pad(plaintext))
# ANFÄLLIG: Vorhersagbarer IV (Zähler)
counter = 0
def encrypt_predictable(plaintext, key):
global counter
counter += 1
iv = counter.to_bytes(16, 'big') # Vorhersagbare Sequenz!
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
return cipher.encrypt(pad(plaintext))
# ANFÄLLIG: IV wiederverwenden
def encrypt_messages(messages, key, iv):
# Derselbe IV für alle Nachrichten!
for message in messages:
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
yield cipher.encrypt(pad(message))
// ANFÄLLIG: Statischer IV
public class WeakCBCEncryption {
private static final byte[] STATIC_IV = new byte[16]; // Alles Nullen!
public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
IvParameterSpec ivSpec = new IvParameterSpec(STATIC_IV);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return cipher.doFinal(plaintext);
}
}
// ANFÄLLIG: IV vom Schlüssel abgeleitet
public class PredictableIVEncryption {
public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
// IV vom Schlüssel abgeleitet - gleicher Schlüssel = gleicher IV!
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] iv = md.digest(key.getEncoded());
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return cipher.doFinal(plaintext);
}
}
// ANFÄLLIG: Zeitstempel als IV verwenden
public byte[] encryptWithTimeIV(byte[] plaintext, SecretKey key) throws Exception {
// Zeitstempel ist vorhersagbar!
long timestamp = System.currentTimeMillis();
byte[] iv = ByteBuffer.allocate(16).putLong(timestamp).array();
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return cipher.doFinal(plaintext);
}
// ANFÄLLIG: Statischer IV in Node.js
const crypto = require('crypto');
const STATIC_IV = Buffer.alloc(16, 0); // Alles Nullen!
function encryptWeak(plaintext, key) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, STATIC_IV);
return Buffer.concat([cipher.update(plaintext), cipher.final()]);
}
// ANFÄLLIG: IV aus Klartext
function encryptBadIV(plaintext, key) {
// IV aus zu verschlüsselnden Daten abgeleitet - sehr schlecht!
const hash = crypto.createHash('md5').update(plaintext).digest();
const cipher = crypto.createCipheriv('aes-256-cbc', key, hash);
return Buffer.concat([cipher.update(plaintext), cipher.final()]);
}
Korrigierter Code
# SICHER: Zufälliger IV für jede Verschlüsselung
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
def encrypt_safe(plaintext, key):
# Zufälligen IV für jede Verschlüsselung generieren
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
# IV dem Geheimtext voranstellen
return iv + ciphertext
def decrypt_safe(ciphertext, key):
# IV vom Anfang extrahieren
iv = ciphertext[:16]
actual_ciphertext = ciphertext[16:]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
plaintext = unpad(cipher.decrypt(actual_ciphertext), AES.block_size)
return plaintext
# BESSER: GCM-Modus anstelle von CBC verwenden
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
def encrypt_gcm(plaintext, key):
# GCM bietet authentifizierte Verschlüsselung
nonce = get_random_bytes(12) # 96-Bit Nonce für GCM
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Nonce + Tag + Geheimtext zurückgeben
return nonce + tag + ciphertext
def decrypt_gcm(data, key):
nonce = data[:12]
tag = data[12:28]
ciphertext = data[28:]
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext
// SICHER: Zufälliger IV mit SecureRandom
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;
public class SecureCBCEncryption {
private static final SecureRandom secureRandom = new SecureRandom();
public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
// Zufälligen IV generieren
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(plaintext);
// IV und Geheimtext kombinieren
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
return result;
}
public byte[] decrypt(byte[] data, SecretKey key) throws Exception {
// IV extrahieren
byte[] iv = Arrays.copyOfRange(data, 0, 16);
byte[] ciphertext = Arrays.copyOfRange(data, 16, data.length);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
return cipher.doFinal(ciphertext);
}
}
// BESSER: GCM-Modus verwenden
public class SecureGCMEncryption {
private static final int GCM_NONCE_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 128;
private static final SecureRandom secureRandom = new SecureRandom();
public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
byte[] nonce = new byte[GCM_NONCE_LENGTH];
secureRandom.nextBytes(nonce);
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
byte[] ciphertext = cipher.doFinal(plaintext);
// Nonce voranstellen
byte[] result = new byte[nonce.length + ciphertext.length];
System.arraycopy(nonce, 0, result, 0, nonce.length);
System.arraycopy(ciphertext, 0, result, nonce.length, ciphertext.length);
return result;
}
public byte[] decrypt(byte[] data, SecretKey key) throws Exception {
byte[] nonce = Arrays.copyOfRange(data, 0, GCM_NONCE_LENGTH);
byte[] ciphertext = Arrays.copyOfRange(data, GCM_NONCE_LENGTH, data.length);
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
return cipher.doFinal(ciphertext);
}
}
// SICHER: Zufälliger IV in Node.js
const crypto = require('crypto');
function encryptCBCSafe(plaintext, key) {
// Zufälligen IV generieren
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext),
cipher.final()
]);
// IV dem Geheimtext voranstellen
return Buffer.concat([iv, encrypted]);
}
function decryptCBCSafe(data, key) {
// IV extrahieren
const iv = data.slice(0, 16);
const ciphertext = data.slice(16);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
}
// BESSER: GCM-Modus verwenden
function encryptGCM(plaintext, key) {
const nonce = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
const encrypted = Buffer.concat([
cipher.update(plaintext),
cipher.final()
]);
const authTag = cipher.getAuthTag();
// nonce + authTag + Geheimtext
return Buffer.concat([nonce, authTag, encrypted]);
}
function decryptGCM(data, key) {
const nonce = data.slice(0, 12);
const authTag = data.slice(12, 28);
const ciphertext = data.slice(28);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(authTag);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
}
Ausgenutzt in der Praxis
BEAST-Angriff (2011)
Der Browser Exploit Against SSL/TLS (BEAST)-Angriff nutzte vorhersagbare IVs in der CBC-Modus-Implementierung von TLS 1.0 aus, wodurch Angreifer HTTPS-Verkehr durch einen Chosen-Plaintext-Angriff entschlüsseln könnten.
ASP.NET Padding Oracle (2010)
Schwachstellen in ASP.NETs Verschlüsselungsimplementierung, kombiniert mit CBC-Modus-Problemen, ermöglichten Angreifern, verschlüsselte Daten durch Padding-Oracle-Angriffe zu entschlüsseln und zu fälschen.
SSH CBC-Modus-Schwachstelle (2008)
CVE-2008-5161 beschrieb Schwächen in SSHs CBC-Modus-Implementierung, die die Wiederherstellung von bis zu 32 Bits Klartext ermöglichen könnten.
Tools zum Testen/Ausnutzen
-
Padding Oracle Attack Tools — PadBuster für Padding-Oracle-Angriffe.
-
TestSSL — Prüfung auf CBC-Modus-Schwachstellen in TLS.
-
BEAST Attack PoC — Demonstration des BEAST-Angriffs.
CVE-Beispiele
-
CVE-2011-3389 — BEAST-Angriff auf TLS 1.0 CBC.
-
CVE-2008-5161 — SSH CBC-Modus-Schwachstelle.
-
CVE-2014-3566 — POODLE-Angriff (verwandte CBC-Probleme).
Referenzen
-
MITRE. "CWE-329: Not Using an Unpredictable IV with CBC Mode." https://cwe.mitre.org/data/definitions/329.html
-
NIST. "Recommendation for Block Cipher Modes of Operation." https://csrc.nist.gov/publications/detail/sp/800-38a/final