HotXLS liest Agile-verschlüsselte Excel-Dateien — den Passwortschutz, den Excel 2010 und alle neueren Versionen standardmäßig anwenden — über einen einzigen Aufruf: TXLSXWorkbook.OpenEncrypted. Die Komponente parst den XML-Verschlüsselungsdeskriptor, leitet die Schlüssel über eine SHA-512-Hashkette (mit Iterationszähler) vom Passwort ab, verifiziert das Passwort gegen den verschlüsselten Prüfwert (Verifier) und entschlüsselt das Paket anschließend in AES-CBC-Segmenten von 4096 Byte. Dabei wird keine Excel-Installation, kein COM und keine externe Krypto-DLL benötigt
Dieser Artikel befasst sich speziell mit der Leseseite der Agile-Verschlüsselung. Zwei verwandte Themen haben eigene Artikel: Das Zusammenspiel mit den veralteten RC4- und XOR-Schemata in alten BIFF-.xls-Dateien wird im Artikel über ECB- und RC4-Interoperabilität behandelt, und die Erstellung passwortgeschützter Arbeitsmappen mit der ECMA-376 Standard-Verschlüsselung wird im Artikel über AES-geschützte XLSX-Ausgaben beschrieben. Hier gehen wir davon aus, dass die Datei bereits existiert, von jemand anderem verschlüsselt wurde und Ihre Aufgabe darin besteht, sie zu öffnen
Das Szenario, das diese Anforderung erzwingt, ist jedem bekannt, der eine Dokumenten-Pipeline betreibt. Ein serverseitiger Importdienst akzeptiert Uploads von Arbeitsmappen. Auf dem System ist kein Excel installiert und wird es auch nie sein. Eines Morgens lädt ein Kunde eine ganz normale .xlsx-Datei hoch, die der ZIP-Reader ablehnt, weil es sich überhaupt nicht um ein ZIP-Archiv handelt — der Kunde hat die Datei mit einem Passwort gespeichert. Ab diesem Moment muss Ihr Loader entweder [MS-OFFCRYPTO] verstehen oder er weist die Datei zurück. Der Kunde hat aus seiner Sicht jedoch nichts Ungewöhnliches getan
Was ist die Agile-Verschlüsselung in einer Excel-Datei?
Die Agile-Verschlüsselung ist das in [MS-OFFCRYPTO] §2.3.4.10 bis §2.3.4.15 definierte Passwortschutzverfahren, das von Excel 2010 und neuer geschrieben wird, sobald eine Arbeitsmappe mit einem Passwort gespeichert wird. Die verschlüsselte Datei ist kein ZIP-Paket mehr, sondern ein OLE Compound File Binary (CFB) Container. Dieser enthält zwei Streams: EncryptionInfo, das beschreibt, wie die Verschlüsselung durchgeführt wurde, und EncryptedPackage, welches das eigentliche, als undurchsichtiger Blob verschlüsselte .xlsx-ZIP-Archiv darstellt. Die CFB-Signatur (D0 CF 11 E0 A1 B1 1A E1) entspricht der Kennung alter BIFF-.xls-Dateien. Deshalb kann eine Datei nicht allein anhand ihrer Dateiendung klassifiziert werden
Was die Agile-Verschlüsselung von ihren Vorgängern unterscheidet, ist, dass EncryptionInfo selbstbeschreibend ist. Nach einem 8 Byte langen Versionspräfix (Major- und Minor-Version sind 4) folgt der Stream als UTF-8 XML-Deskriptor. Ein Element keyData deklariert die Verschlüsselungsmethode (AES), den Verkettungsmodus (ChainingModeCBC), den Hashwert (SHA512), die Schlüssellänge in Bit, die Blockgröße und einen Base64-Salt. Ein Passwort-Element keyEncryptor trägt seinen eigenen Salt, den spinCount (Iterationszähler) sowie drei Base64-Nutzdaten: encryptedVerifierHashInput, encryptedVerifierHashValue und encryptedKeyValue. Excel verwendet standardmäßig AES-256 mit einem Iterationszähler von 100.000, aber der Deskriptor darf auch AES-128 oder AES-192 deklarieren, und HotXLS beachtet den Wert von keyBits, anstatt blind von 256 auszugehen
Ein einziger Einstiegspunkt für Klartext-, Standard- und Agile-Arbeitsmappen
TXLSXWorkbook.OpenEncrypted verarbeitet alle drei Zustände, auf die ein Aufrufer stoßen kann: einfaches ZIP, standardverschlüsselt und agile-verschlüsselt. So müssen Upload-Handler die Dateien vor dem Laden nicht selbst klassifizieren. Die Methode prüft zuerst die Datei: Gibt es keine CFB-Signatur, wird an den normalen Pfad Open übergeben und das Passwort ignoriert. Handelt es sich um einen CFB-Container, versucht die Methode zuerst die ECMA-376 Standard-Verschlüsselung. Entspricht die Signatur in EncryptionInfo der Agile-Version 4.4, wird die Agile-Pipeline gestartet. Der Rückgabewert ist bei Erfolg 1, genau wie bei der Methode Open
var
Wb: TXLSXWorkbook;
begin
Wb := TXLSXWorkbook.Create;
try
// Funktioniert gleichermaßen für Klartext-, standardverschlüsselte
// und agile-verschlüsselte Dateien
if Wb.OpenEncrypted('upload.xlsx', 'customer-password') = 1 then
Writeln(VarToWideStr(Wb.Sheets[1].Cells[1, 1].Value));
finally
Wb.Free;
end;
end;
Der Fallback für unverschlüsselte Eingaben ist wichtiger als es scheint. Ein Batch-Importer, der immer OpenEncrypted aufruft, benötigt keine Verzweigung im Code: Ungeschützte Dateien werden genau wie zuvor geladen, und verschlüsselte Dateien werden direkt entschlüsselt und als In-Memory-Stream an den normalen ZIP-Loader übergeben. Es gibt nur einen einzigen Pfad zu testen, nicht drei
Wie wird aus einem Passwort ein AES-Schlüssel?
Die Agile-Verschlüsselung verwendet das Passwort niemals direkt. HotXLS berechnet zuerst einen iterierten Hashwert: Der initiale Digest ist SHA-512 über den Passwort-Salt verkettet mit den UTF-16LE-Bytes des Passworts. Anschließend wird dieser Digest spinCount-mal neu gehasht, wobei in jeder Runde der 32-Bit Little-Endian-Iterationszähler vor dem vorherigen Digest angefügt wird. Bei Excels Standard-Iterationszähler von 100.000 sind das einhunderttausend serielle SHA-512-Aufrufe pro Passwortversuch — und genau das ist der Sinn der Sache. Der Iterationszähler verlangsamt Brute-Force-Angriffe: Er kostet einen berechtigten Aufrufer einmalig wenige Millisekunden, zwingt einen Angreifer bei einer Wörterbuch-Attacke jedoch bei jedem einzelnen Versuch zu derselben Verzögerung
// [MS-OFFCRYPTO] iterierter Passworthash:
// H(0) = SHA-512(Salt + UTF-16LE(Passwort))
// H(n) = SHA-512(LE32(n - 1) + H(n - 1)), spinCount-mal wiederholt
function AgilePasswordHash(const Password: WideString;
const Salt: TBytes; SpinCount: Integer): TBytes;
var
buf: TBytes;
i: Integer;
begin
Result := XlsSHA512(Concat(Salt, Utf16LEBytes(Password)));
SetLength(buf, 4 + 64);
for i := 0 to SpinCount - 1 do
begin
PutLE32(buf, 0, i); // Iterationszähler, Little-Endian
Move(Result[0], buf[4], 64); // vorheriger Digest
Result := XlsSHA512(buf);
end;
end;
Der iterierte Hashwert ist jedoch immer noch kein Schlüssel. Daraus werden durch erneutes Hashen mit einem angehängten festen 8 Byte langen Blockschlüssel drei verschiedene Schlüssel abgeleitet, einer pro Verwendungszweck: FE A7 D2 76 3B 4B 9E 79 zum Entschlüsseln der Prüfwerteingabe, D7 AA 0F 6D 30 61 34 4E für den Prüfwert-Hash und 14 6E 0B E7 AB AC D0 D6 zum Entpacken des eigentlichen Paketschlüssels. Jedes SHA-512-Ergebnis wird auf die deklarierte Schlüssellänge gekürzt und gemäß [MS-OFFCRYPTO] mit 0x36-Bytes aufgefüllt, falls der Hash kürzer als der Schlüssel sein sollte. Dieselbe 0x36-Auffüllregel gilt, wenn der Passwort-Salt zur Verwendung als CBC-Initialisierungsvektor auf Blockgröße erweitert wird
Passwort-Verifizierung und die saltSize-Kürzungsfalle
HotXLS verifiziert das Passwort mithilfe der Prüfwerte aus dem Deskriptor, bevor das eigentliche Paket angefasst wird. Es entschlüsselt encryptedVerifierHashInput mit dem ersten abgeleiteten Schlüssel, hasht das Ergebnis mit SHA-512, entschlüsselt encryptedVerifierHashValue mit dem zweiten abgeleiteten Schlüssel und vergleicht beide Hashes Byte für Byte. Eine Abweichung bedeutet, dass das Passwort falsch ist. Dies wird als separates Ergebnis gemeldet, anstatt eine beschädigte Arbeitsmappe zu erzeugen. Wichtig ist, dass der Paketinhalt bei falschem Passwort niemals entschlüsselt wird. Es gibt also kein Szenario, in dem ein falsches Passwort plausible, aber korrupte Daten liefert
Hierbei gibt es ein Spezifikationsdetail, das leicht übersehen wird. [MS-OFFCRYPTO] §2.3.4.13 definiert den Prüfwert als saltSize Byte an Zufallsdaten, wobei saltSize der Länge des Salts des Schlüssel-Verschlüsselers (Key Encryptor) entspricht und nicht der Cipher-Blockgröße. Da die Ausgabe von AES-CBC an Blockgrenzen ausgerichtet ist, wird die entschlüsselte Prüfwerteingabe auf ein Vielfaches von 16 Byte aufgefüllt zurückgegeben und muss vor dem Hashen wieder auf saltSize gekürzt werden. Excel schreibt die saltSize immer gleich der Blockgröße (beide 16), sodass eine fehlerhafte Implementierung, die das Kürzen überspringt, zwar alle Tests mit echten Excel-Dateien besteht, aber bei der ersten Datei eines Drittanbieters scheitert, der eine andere Salt-Länge gewählt hat. HotXLS kürzt auf die Salt-Länge, da dies die Spezifikation so vorschreibt; dass beide Werte in der Praxis oft übereinstimmen, ist ein Zufall und keine Regel
Wie wird das EncryptedPackage entschlüsselt?
Der Stream EncryptedPackage beginnt mit einer 8 Byte langen Little-Endian-Klartextgröße, gefolgt vom Chiffretext in 4096-Byte-Segmenten. HotXLS entschlüsselt diesen Segment für Segment mit einem jeweils neuen IV pro Segment. Der eigentliche Paketschlüssel ist nicht passwortbasiert: Er ist ein zufälliger Zwischenschlüssel, den das Erstellungsprogramm in encryptedKeyValue verschlüsselt hat. HotXLS entpackt ihn mit dem dritten abgeleiteten Schlüssel und kürzt ihn auf die in keyData angegebene Schlüssellänge. Der IV jedes Segments ist SHA-512 über dem keyData-Salt verkettet mit dem 32-Bit Little-Endian-Segmentindex, gekürzt auf Blockgröße. Durch diesen Aufbau kann jedes 4096-Byte-Segment unabhängig entschlüsselt werden, was das Format prinzipiell für wahlfreien Zugriff (Random Access) geeignet macht, auch wenn HotXLS das gesamte Paket im Speicher entschlüsselt und die ZIP-Bytes an den normalen XLSX-Loader übergibt
Die deklarierte Klartextgröße übernimmt die abschließende Aufgabe. Die AES-CBC-Ausgabe ist an Blöcken ausgerichtet, weshalb das letzte Segment bis zu 15 Byte an Padding-Daten enthalten kann, die nicht zum Dokument gehören. Der entschlüsselte Puffer wird auf die angegebene Größe gekürzt. Das Ergebnis ist exakt das .xlsx-ZIP-Archiv, das Excel verschlüsselt hat. HotXLS validiert das Größenpräfix vor dem Entschlüsseln gegen die tatsächliche Stream-Länge, sodass ein unvollständiger Upload oder ein manipulierte Größenfeld kontrolliert fehlschlägt, anstatt einen Überlauf zu verursachen
Fehlerbehandlung und dokumentierte Systemgrenzen
Fehlerszenarien werden sauber getrennt. Ein falsches Passwort löst eine Exception mit einer eindeutigen Fehlermeldung aus, die auf der Prüfung des Verifiers basiert. So kann eine Benutzeroberfläche den Benutzer zur erneuten Eingabe auffordern. Ein CFB-Container, dessen Deskriptor nicht unterstützte Algorithmen deklariert — also alles andere als AES im CBC-Modus mit SHA-512-Hashing in einem Agile-Deskriptor — oder ein Container, der weder dem Standard- noch dem Agile-Format entspricht, löst eine andere Exception aus, die das Verfahren als nicht unterstützt kennzeichnet. Diese beiden Fälle dürfen niemals verwechselt werden: Die wiederholte Passworteingabe bei einem nicht unterstützten Format verschwendet die Zeit des Benutzers, und das Melden eines falschen Passworts bei einem Formatfehler führt das Support-Team in die Irre
function LoadUploadedWorkbook(const FileName: WideString;
const Password: WideString; Wb: TXLSXWorkbook): Boolean;
begin
Result := False;
try
Result := Wb.OpenEncrypted(FileName, Password) = 1;
except
on E: EXlsxEncryptionNotImplemented do
// Wird sowohl bei falschem Passwort als auch bei nicht unterstützten
// Verfahren ausgelöst. E.Message enthält Details. Protokollieren Sie dies
// und bieten Sie eine Passwort-Wiederholung nur bei falschem Passwort an.
RejectUpload(FileName, E.Message);
end;
end;
Die Grenzen der Komponente seien klar benannt: HotXLS liest Agile-Deskriptoren, die AES im CBC-Modus mit SHA-512 deklarieren. Dies deckt das ab, was Excel 2010 bis Excel 365 tatsächlich in allen drei Schlüsselgrößen schreiben. Deskriptoren, die andere Chiffren oder Hash-Algorithmen angeben, werden abgelehnt. Zertifikatsbasierte Schlüssel-Verschlüsseler (Key Encryptor) werden nicht verarbeitet, sondern nur passwortbasierte. Auf der Schreibseite erzeugt HotXLS derzeit die Standard-Verschlüsselung anstelle von Agile. Dieser Unterschied ist wichtig, falls nachgelagerte Tools das Verfahren genau prüfen. Details dazu finden Sie im Artikel über das Schreiben von AES-geschützten XLSX-Ausgaben
Passwortgeschützte Uploads verlieren ihren Status als Sonderfall, sobald der Loader die Verschlüsselung als Teil des Dateiformats und nicht als Ausnahme begreift. Der Einstiegspunkt OpenEncrypted, die SHA-512-Schlüsselerzeugung mit Iterationszähler und die segmentierte AES-CBC-Entschlüsselung werden als Teil von HotXLS Delphi Excel Component ausgeliefert, neben der restlichen nativen XLS- und XLSX-Engine für Delphi und C++Builder