Technisch artikel

XLSX-uitvoer coderen met AES in Delphi: wat HotXLS SaveAsEncrypted schrijft

Excel biedt twee opties die beide "wachtwoord" worden genoemd, maar slechts één daarvan is daadwerkelijk encryptie. Het wachtwoord voor openen activeert een echte versleuteling (cipher): zonder dit wachtwoord kan het bestand helemaal niet worden gelezen. De wachtwoorden voor tabblad- (worksheet) en werkmapbeveiliging (workbook protection) doen dat daarentegen niet. Ze stellen alleen een vlag (flag) in die een meewerkende editor belooft te respecteren. Een werkmap die niets anders dan die vlag bevat, is een gewone leesbare zip waarin de gegevens in platte tekst (cleartext) staan. Kiest u de verkeerde optie, dan verzendt u salarisgegevens die in Excel beveiligd lijken, maar in elke teksteditor te lezen zijn

Het bewijs duurt tien seconden. Hernoem een beveiligde .xlsx naar .zip, open deze in een willekeurig archiefhulpprogramma en bekijk xl/worksheets/sheet1.xml. Als de celwaarden daar in gewone UTF-8 staan, is het bestand niet versleuteld, hoeveel wachtwoordvragen Excel ook stelt wanneer iemand probeert een cel te bewerken. Dit misverstand blijft soms jarenlang bestaan binnen teams die aannemen dat tabbladbeveiliging hetzelfde is als vertrouwelijkheid, en het komt meestal aan het licht op de dag dat een beveiligingscontrole exact deze hernoeming voert

HotXLS is een native Delphi- en C++Builder-spreadsheetbibliotheek en houdt deze twee functies strikt gescheiden. Tabblad- en werkmapbeveiliging zijn bewerkingsbeperkingen die worden ondersteund door een bewust zwakke verouderde hash (legacy hash). SaveAsEncrypted produceert een met AES versleuteld pakket dat door niets anders dan het juiste wachtwoord kan worden geopend. De onderstaande secties behandelen wat die aanroep schrijft, de asymmetrie waarmee u rekening moet houden in uw ontwerp (HotXLS schrijft gecodeerde bestanden maar kan ze niet teruglezen), en waarin het oudere XLS-pad verschilt

Waarom tabbladbeveiliging geen encryptie is

De Protect-methoden op tabbladen en ProtectWorkbook op de werkmap slaan een 4-hex-cijferige hash van het wachtwoord op. Dat is het verouderde algoritme dat OOXML en BIFF beide hebben geërfd van Excel uit de jaren 90, en de formaatdocumentatie claimt nergens dat het meer doet dan onbedoelde bewerkingen voorkomen. Het pakket blijft een gewone leesbare zip: celgegevens, formules en gedeelde strings staan allemaal in platte tekst XML. De standaardinstelling maakt het er niet beter op. Elke cel begint met Locked=True, dus het aanroepen van Protect zonder eerst een invoerbereik te ontgrendelen, bevriest het hele tabblad voor bewerkingen terwijl elke waarde voor iedereen zichtbaar blijft

Dit alles maakt beveiliging niet nutteloos. Gebruikers naar bewerkbare bereiken leiden en een lay-out stabiliseren voor afdrukken zijn reële taken, behandeld in ons artikel over tabbladbeveiliging en pagina-instellingen. Maar dat zijn bruikbaarheidstaken. Zodra vertrouwelijkheid de vereiste is, is de enige API die daaraan voldoet SaveAsEncrypted

Wat SaveAsEncrypted daadwerkelijk schrijft

De implementatie volgt de ECMA-376 Standard Encryption, gespecificeerd in [MS-OFFCRYPTO] sectie 2.3.4. Het wachtwoord doorloopt 50.000 iteraties van SHA-1 to een AES-128 sleutel af te leiden. Een verifieerdersblok (verifier block), gecodeerd met AES-128 in ECB-modus, stelt een consument in staat het wachtwoord te bevestigen voordat er iets wordt gedecodeerd, en het gehele werkmappakket wordt vervolgens gecodeerd met AES-128 in CBC-modus. Wat op de schijf landt is helemaal geen zip-bestand. Het is een OLE compound-bestand dat de streams EncryptionInfo, EncryptedPackage en DataSpaces bevat, zonder een xl/-map die een archiefprogramma kan weergeven. Dit is de reden waarom de hernoemingstest nu niets leesbaurs oplevert. Excel 2007 en nieuwer openen het bestand met enkel het wachtwoord, en het huidige LibreOffice leest Standard Encryption eveneens

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  rc: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Payroll');
    Sheet.Cells[1, 1].Value := 'Employee';
    Sheet.Cells[1, 2].Value := 'Net pay';
    Sheet.Cells[2, 1].Value := 'A. Garcia';
    Sheet.Cells[2, 2].Value := 4815.16;

    rc := Book.SaveAsEncrypted('payroll-2026-06.xlsx', PasswordFromVault);
    if rc <> 1 then
      raise Exception.CreateFmt('Encrypted save failed (rc=%d)', [rc]);
  finally
    Book.Free;
  end;
end;

Behandel de wachtwoordvariabele met dezelfde zorg als een verbindingsreeks (connection string). Haal deze op het laatste moment op uit een kluis (vault) of een service voor gegenereerde geheimen, log deze nooit en schrijf deze nooit in de werkmap zelf. De controle van de retourcode is geen optionele formaliteit. Een versleutelde opslag die halverwege mislukt, moet de levering afbreken, omdat de enige fallback die de aanroepende code kan bieden een niet-gecodeerde kopie is, en die kopie is precies het incident dat deze functie moet voorkomen

Er is ook een machine-controleerbare acceptatietest die bijna niets kost: roep CanReadEncrypted aan op het bestand dat u zojuist hebt geschreven. Dit retourneert alleen true als de uitvoer daadwerkelijk een versleutelingscontainer is. Door dit na elke versleutelde opslag te controleren, vangt u de belangrijkste regressie op — een codepad dat stilletjes terugviel op een gewone SaveAs — op het moment dat het gebeurt in plaats van weken later in de inbox van een klant. Het laatste oordeel ligt nog steeds bij het handmatig openen in Excel met het echte wachtwoord tijdens de release-tests

Write-only by design: omgaan met EXlsxEncryptionNotImplemented

Hier is de asymmetrie die uw pijplijnarchitectuur zou moeten vormgeven: HotXLS versleutelt bij het opslaan, maar decodeert niet bij het openen. OpenEncrypted genereert EXlsxEncryptionNotImplemented wanneer deze naar een daadwerkelijk gecodeerd pakket verwijst; bij een gewone werkmap valt deze simpelweg terug naar een normale Open. De bijbehorende controle CanReadEncrypted detecteert de OLE-coderingscontainer op een goedkope manier, zodat innamecode dergelijke bestanden kan routeren zonder de uitzondering te veroorzaken:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.CanReadEncrypted(FileName) then
    begin
      // Encrypted container: HotXLS cannot decrypt it.
      Writeln(FileName + ': needs manual decryption in Excel first');
      Exit;
    end;
    try
      Book.OpenEncrypted(FileName, '');   // plain files fall through to Open
      Writeln(FileName + ': opened, ' + IntToStr(Book.Sheets.Count) + ' sheet(s)');
    except
      on EXlsxEncryptionNotImplemented do
        Writeln(FileName + ': encrypted - routed to manual queue');
    end;
  finally
    Book.Free;
  end;
end;

Die asymmetrie heeft één duidelijke architecturale betekenis: versleutel aan de uiterste grens van de levering, als laatste stap. Houd de platte-tekst master binnen uw vertrouwensgrens — in een database, a documentopslag of een share met toegangscontrole — en produceer de gecodeerde kopie als de allerlaatste stap voordat het bestand het systeem verlaat. Een pijplijn dat alleen de gecachte gecodeerde uitvoer archiveert, heeft zichzelf buitengesloten van zijn eigen gegevens, omdat geen enkele latere fase van hetzelfde systeem die bestanden kan heropenen. Wanneer een stroomafwaarts HotXLS-proces de werkmap opnieuw nodig heeft, geef deze dan de master in platte tekst, nooit het leveringsartefact

AES-128 Standard Encryption en de AES-256 compliance-grens

De versleuteling van Office-bestanden kent twee generaties. Standard Encryption, de versie die HotXLS schrijft, gebruikt AES-128 met SHA-1 sleutelafleiding. Agile Encryption kwam later en stapte over op AES-256 met SHA-512 en een andere, in XML beschreven sleutelcontainer. Beide openen transparant in Excel, en AES-128 is computationeel nog steeds betrouwbaar om een bestand te beschermen tijdens verzending naar een klant

Het verschil is niet langer academisch op de dag dat een beveiligingsvragenlijst vraagt om "AES-256 encryptie van bestanden in rust". Standard Encryption voldoet niet aan die eis, hoe sterk het wachtwoord ook is, en geen enkele parameter van SaveAsEncrypted verandert het algoritme dat het produceert. Vermeld het profiel dus nauwkeurig in uw beveiligingsdocumentatie: AES-128, ECMA-376 Standard Encryption, SHA-1 sleutelafleiding bij 50.000 iteraties. Een claim die een audit overleeft is meer waard dan een optimistische claim die bij controle uiteenvalt

De verouderde XLS-route: RC4 uit, RC4 en XOR weer in

De BIFF-facade heeft de tegenovergestelde vorm. De versleuteling ervan is ouder en zwakker, maar de round-trip is compleet: wat het schrijft, kan het ook teruglezen. Het instellen van EncryptionPassword vóór SaveAs produceert een met RC4 versleutelde .xls via het BIFF FilePass-mechanisme, en Open met een wachtwoordparameter leest alle drie de verouderde schema's: RC4, RC4 CryptoAPI en de oude XOR-obfuscatie:

var
  Writer, Reader: IXLSWorkbook;   // interface refs: no manual Free
begin
  Writer := TXLSWorkbook.Create;
  Writer.Sheets.Add.Cells.Item[1, 1].Value := 'Confidential';
  Writer.EncryptionPassword := 'S3cret!';
  Writer.SaveAs('confidential.xls');

  Reader := TXLSWorkbook.Create;
  if Reader.Open('confidential.xls', 'S3cret!') > 0 then
    Writeln(Reader.Sheets[1].Cells.Item[1, 1].Value);  // Entries are 1-based
end;

RC4 is verouderde cryptografie en mag nooit worden gebruikt om gegevens te beschermen die er vandaag de dag toe doen; de enige overgebleven waarde is interoperabiliteit met systemen die nog steeds .xls uitwisselen. De leeszijde bewijst echter zijn waarde bij migratiewerkzaamheden. Een met een wachtwoord beveiligd legacy-bestand opent met Open(FileName, Password), slaat de brug naar het OOXML model, en wordt opnieuw beveiligd via het AES-pad — een eenrichtingsupgrade die draait zonder dat Excel ergens in de lus nodig is. Voor versleutelde leveringen met een hoog volume zijn de opmerkingen over de doorvoer aan de opslagzijde in ons artikel over streaming-schrijven voor server-batchtaken van toepassing op de fase waarin de inhoud wordt opgebouwd die voorafgaat aan de versleuteling

Encryptie en beveiliging zijn geen rivalen

Nog één punt dat opheldering verdient, omdat het ter sprake komt zodra iemand de waarschuwing bovenaan deze pagina leest als "beveiliging is waardeloos". Dat is het niet. Encryptie en beveiliging beantwoorden verschillende vragen en vullen elkaar aan. Encryptie bepaalt wie het bestand kan openen; beveiliging bepaalt wat een lezer die al binnen is, mag wijzigen. Een salarislevering kan redelijkerwijs beide doen: het pakket versleutelen zodat alleen de houder van het wachtwoord het ziet, en vervolgens de formulecellen vergrendelen zodat de ontvanger kan filteren en sorteren, maar de berekeningen niet stilletjes kan herschrijven. De fout is nooit het toevoegen van beveiliging. De fout is om de aanwezigheid ervan te laten doorgaan voor encryptie wanneer vertrouwelijkheid de vereiste was

Het beheer van de wachtwoorden kent geen vangnet, en dat is zo ontworpen. De sleutelafleiding met 50.000 iteraties is er om gokken duur te maken, en niets in het bestand bewaart het geheim (no escrow). Een verloren wachtwoord is verloren gegevens. Genereer, lever en bewaar deze wachtwoorden met dezelfde discipline die u toepast op database-inloggegevens, en de encryptie doet zijn werk

Echte bestandsversleuteling is slechts één aanroep in HotXLS. De discipline zit in alles om die aanroep heen: wachtwoordbeheer, de grens voor alleen-schrijven die voorkomt dat HotXLS zijn eigen uitvoer heropent, en een claim over het algoritme die u kunt verdedigen tijdens een audit. SaveAsEncrypted en de legacy round-trip worden geleverd met het HotXLS Component, dat native draait in Delphi- en C++Builder-processen zonder Excel-automatisering ergens in het pad