AES-GCM-Implementierungsguide: authentifizierte Verschlüsselung richtig
Implementieren Sie AES-GCM korrekt. Nonce-Verwaltung, Schlüsselhandling und häufige Fallstricke bei authentifizierter Verschlüsselung.
AES-GCM (Galois/Counter Mode) kombiniert AES-CTR-Verschlüsselung mit GHASH-Authentifizierung, um Authenticated Encryption with Associated Data (AEAD) zu erzeugen. Eine korrekte Implementierung verwendet einen 256-Bit-Schlüssel, einen 96-Bit-(12-Byte-)Nonce, der pro Schlüssel eindeutig ist (niemals wiederverwendet), ein 128-Bit-Authentifizierungs-Tag und optional authentifizierte assoziierte Daten (AAD), die verifiziert, aber nicht verschlüsselt werden. NIST SP 800-38D spezifiziert die genaue Konstruktion. Auch nur einen dieser Punkte falsch zu machen — besonders Nonce-Wiederverwendung — und GCMs Sicherheit bricht zusammen: Ein einziges wiederholtes (Schlüssel, Nonce)-Paar ermöglicht Angreifern, den Authentifizierungsschlüssel wiederherzustellen und beliebige Chiffretexte zu fälschen. Dieser Guide behandelt die korrekte Verwendung von AES-GCM in Browser-, Node- und Server-Kontexten.
Was GCM tatsächlich garantiert
Zwei Eigenschaften:
Vertraulichkeit: Der Klartext kann ohne den Schlüssel nicht wiederhergestellt werden. AES-GCMs CTR-Mode-Verschlüsselungsschicht bietet das.
Integrität und Authentizität: Jede Veränderung an Chiffretext, Nonce oder assoziierten Daten führt zum Fehlschlag der Entschlüsselung. GHASH erzeugt ein 128-Bit-Tag, das bei der Entschlüsselung konstant-zeitig verifiziert wird.
Was GCM nicht garantiert: Nichtabstreitbarkeit (es ist symmetrisch, also kann jeder mit dem Schlüssel gültige Chiffretexte erzeugen), Replay-Schutz (das ist eine höhere Schicht) oder Reihenfolge (für Streams muss irgendwie verkettet werden).
Der Kernpunkt: GCM bleibt nur sicher, wenn Nonces pro Schlüssel eindeutig sind. Nicht meistens eindeutig, nicht üblicherweise eindeutig — tatsächlich eindeutig. Der Sicherheitsbeweis bricht bei Wiederverwendung zusammen.
Nonce-Verwaltung: Der wichtigste Aspekt
Ein 96-Bit-Nonce kann auf zwei Wegen erzeugt werden:
Zufällig: crypto.getRandomValues(new Uint8Array(12)). Mit zufälligen 96-Bit-Nonces unter einem einzigen Schlüssel erscheinen Geburtstags-Kollisionen nach etwa 2^48 Verschlüsselungen. NIST empfiehlt eine Sicherheitsmarge, also Verwendungen pro Schlüssel bei 2^32 deckeln.
Zähler: Einen 96-Bit-Integer inkrementieren. Garantiert Eindeutigkeit bis zu 2^96 Nachrichten. Benötigt zuverlässig monotonen State — schwierig in verteilten Systemen.
Für Dateiübertragungen mit einem frischen Schlüssel pro Datei sind zufällige Nonces vollkommen sicher — 2^32 Verschlüsselungen mit einem Schlüssel werden nie erreicht. Für ge-chunkte Verschlüsselung unter einem einzigen Dateischlüssel einen Zähler verwenden, bei dem der Nonce den Chunk-Index kodiert:
const nonce = new Uint8Array(12);
new DataView(nonce.buffer).setUint32(0, messageId);
new DataView(nonce.buffer).setBigUint64(4, BigInt(chunkIndex));
Der Katastrophenfall: Mehrere Prozesse verschlüsseln unter demselben gemeinsamen Schlüssel mit zufälligen Nonces und skalieren auf Millionen Verschlüsselungen pro Sekunde. Geburtstags-Kollisionen werden wahrscheinlich. Wenn Schlüssel über Prozesse geteilt werden müssen, einen koordinierten Zähler mit Prozess-ID-Präfix verwenden.
64-Bit-Nonces nicht verwenden
AES-GCM unterstützt variable Nonce-Längen, aber nur 96-Bit-Nonces verwenden die optimierte Konstruktion gemäß NIST 800-38D. Andere Längen (typischerweise 64 oder 128 Bit) lösen einen GHASH-Vorverarbeitungsschritt aus, der Leistung reduziert und Komplexität erhöht. Die Web Crypto API akzeptiert Nicht-96-Bit-IVs, aber die Spec empfiehlt 96. Einfach 96 Bit verwenden.
Tag-Länge: Nicht kürzen
GCMs Tag ist bis zu 128 Bit. Manche Spezifikationen erlauben Kürzung auf 96, 64 oder sogar 32 Bit. Nicht tun. Gekürzte Tags erleichtern Fälschungsangriffe, und die Einsparungen (4–12 Bytes pro Nachricht) sind für Dateiübertragungen irrelevant. Web Cryptos AES-GCM ist standardmäßig auf 128-Bit-Tags über den tagLength-Parameter eingestellt. Nicht anfassen.
Assoziierte Daten (AAD)
AAD sind Daten, die authentifiziert, aber nicht verschlüsselt werden. Für Metadaten verwenden, die an den Chiffretext gebunden werden sollen: Dateiname, Content-Typ, Ablauf-Zeitstempel, Uploader-ID.
await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: nonce,
additionalData: new TextEncoder().encode(JSON.stringify({
filename: "bericht.pdf",
contentType: "application/pdf",
expires: 1712345678,
})),
},
key,
plaintext
);
Wenn ein Angreifer die AAD modifiziert, schlägt die Entschlüsselung fehl. Das verhindert Swap-Angriffe, bei denen jemand den Dateinamen eines gespeicherten Chiffretexts unentdeckt ersetzt. Der Empfänger muss die genaue AAD kennen, um zu entschlüsseln, also sie zusammen mit dem Chiffretext speichern.
Schlüsselgenerierung und -ableitung
Für Per-File-Schlüssel:
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
256 Bit ist der Standard 2026. 128-Bit-AES ist noch sicher, hat aber weniger Post-Quanten-Marge (Grovers Algorithmus halbiert die effektive Stärke).
Für passwortabgeleitete Schlüssel:
const aesKey = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
hash: "SHA-256",
},
passwordKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
Das Salt zusammen mit dem Chiffretext speichern. Es ist kein Geheimnis; es muss nur pro Passwort eindeutig sein.
Der kritische Codepfad
Eine minimale Verschlüsselungsfunktion:
async function encrypt(key, plaintext, aad = new Uint8Array()) {
const nonce = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = new Uint8Array(
await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: nonce, additionalData: aad },
key,
plaintext
)
);
return { nonce, ciphertext, aad };
}
Entschlüsseln mit korrekter Fehlerbehandlung:
async function decrypt(key, { nonce, ciphertext, aad }) {
try {
return await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: nonce, additionalData: aad },
key,
ciphertext
);
} catch (e) {
// Authentifizierungsfehler
throw new Error("Entschlüsselung fehlgeschlagen: Chiffretext manipuliert oder falscher Schlüssel");
}
}
Der decrypt-Aufruf wirft OperationError bei Tag-Nichtübereinstimmung, zu kurzem Chiffretext oder falschem Schlüssel. Jede Ausnahme als Integritätsfehler behandeln; nicht versuchen zu unterscheiden.
Ge-chunkte große Dateien
Für Dateien über einige hundert Megabyte chunken, um Speicherdruck zu vermeiden:
async function encryptChunks(key, file, chunkSize = 1024 * 1024) {
const chunks = [];
let chunkIndex = 0;
for (let offset = 0; offset < file.size; offset += chunkSize) {
const chunk = await file.slice(offset, offset + chunkSize).arrayBuffer();
const nonce = new Uint8Array(12);
new DataView(nonce.buffer).setBigUint64(4, BigInt(chunkIndex++));
const ct = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: nonce }, key, chunk
);
chunks.push(new Uint8Array(ct));
}
return chunks;
}
Vorbehalt: Ge-chunktes AES-GCM erkennt keine Abschneidung. Ein Angreifer könnte abschließende Chunks fallen lassen, und jeder überlebende Chunk entschlüsselt korrekt. Zur Abwehr die Gesamtzahl der Chunks in die AAD jedes Chunks einbeziehen oder libsodiums crypto_secretstream verwenden, das damit umgeht.
Serverseitige Entschlüsselung (Node.js)
Nodes Crypto-Modul kann Daten entschlüsseln, die im Browser verschlüsselt wurden:
const { createDecipheriv } = require('crypto');
function decrypt(key, nonce, ciphertextWithTag) {
const tag = ciphertextWithTag.slice(-16);
const ct = ciphertextWithTag.slice(0, -16);
const decipher = createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ct), decipher.final()]);
}
Web Crypto hängt das 128-Bit-Tag an den Chiffretext; Nodes API erwartet Tag und Chiffretext separat. Entsprechend aufteilen.
Leistungszahlen
Auf typischer Hardware 2024–2026 mit AES-NI:
- Nativ (OpenSSL, AES-NI): 3–5 GB/s pro Core
- Web Crypto (Browser mit Hardware-Beschleunigung): 1–2 GB/s
- libsodium.js WASM AES-GCM: 400–800 MB/s
- Reines JS (@noble/ciphers): 50–150 MB/s
Für eine 1-GB-Datei läuft Web-Crypto-Verschlüsselung in 0,5–1 Sekunde. Reines JS braucht 7–20 Sekunden. Implementierungen auf Basis dieser Realität wählen; für große Dateiübertragungen ist Web Crypto die praktische Wahl.
Häufige Fallstricke zusammengefasst
- Nonce-Wiederverwendung: katastrophal. Größter Einzelfehler.
Math.random()stattcrypto.getRandomValues()verwenden.- Assoziierte Metadaten nicht mit AAD authentifizieren.
- CBC-Modus „aus Gewohnheit" verwenden. CBC erfordert eine separate MAC, um GCMs Integrität zu erreichen; eine HMAC-CBC-Konstruktion ist korrekt, aber komplex — GCM vermeidet die Fehlerquelle.
- Entschlüsselungsfehler stillschweigend abfangen und Kauderwelsch zurückgeben. Immer laut scheitern.
- Eigenes GCM implementieren. Web Crypto, libsodium oder node:crypto verwenden. Die GHASH-Implementierung hat Seitenkanalprobleme, die Experten Jahre brauchten, um richtig zu lösen.
HexaTransfer verwendet Web Cryptos AES-256-GCM mit 96-Bit-Zufalls-Nonces, 128-Bit-Tags und keiner AAD, weil der Schlüssel Pro-Datei ist und der Dateiname separat in AEAD-geschützten Metadaten gespeichert wird. Einfach, korrekt, schnell.
Wann etwas anderes wählen
AES-GCM ist optimal für Dateiübertragungen, aber in spezifischen Fällen Alternativen erwägen:
- XChaCha20-Poly1305: 192-Bit-Nonces machen zufällige Nonce-Sicherheit in jeder Größenordnung trivial. Etwas langsamer auf Hardware mit AES-NI, schneller auf älterem ARM ohne AES-NI. libsodium stellt es bereit.
- AES-GCM-SIV: Missbrauchs-resistent; Nonce-Wiederverwendung leckt nicht den Schlüssel, enthüllt nur, ob Klartexte gleich waren. Nützlich, wenn Nonce-Eindeutigkeit nicht garantiert werden kann.
Für die meisten Dateiübertragungsworkloads in einem Standard-Webstack ist AES-256-GCM mit einem frischen Schlüssel pro Datei und zufälligen 96-Bit-Nonces die richtige und einfachste korrekt zu implementierende Wahl.
Auf hexatransfer.com testen — kostenlos, ohne Konto, bis 10 GB.
Große Dateien sicher mit Ende-zu-Ende-Verschlüsselung senden
Übertragen Sie Dateien bis zu 10 GB kostenlos mit Ende-zu-Ende-Verschlüsselung. Kein Konto erforderlich. Ihre Dateien werden in Ihrem Browser verschlüsselt, bevor sie hochgeladen werden — niemand sonst kann sie lesen.
Datei senden