Teknisk artikel

Kryptera XLSX-utdata med AES i Delphi: vad HotXLS SaveAsEncrypted skriver

Excel exponerar två saker som båda kallas ”lösenord”, och endast en av dem är kryptering. Lösenordet för öppning styr ett riktigt chiffer: utan det kan filen inte läsas alls. Lösenorden för arbetsblads- och arbetsboksskydd gör ingenting av det slaget. De sätter en flagga som en samarbetsvillig redigerare går med på att respektera, och en arbetsbok som bara bär på den flaggan är en vanlig läsbar zip-fil där data ligger i klartext. Välj fel och du skickar ut löneutbetalningar som ser låsta ut i Excel men kan läsas i vilken textredigerare som helst

Beviset tar tio sekunder. Döp om en skyddad .xlsx till .zip, öppna den i valfritt arkivverktyg och titta på xl/worksheets/sheet1.xml. Om cellvärdena finns där i vanlig UTF-8 är filen inte krypterad, oavsett hur många lösenordsprompter Excel visar när någon försöker redigera en cell. Det glappet överlever i åratal inom team som antar att bladskydd innebär sekretess, och det dyker vanligtvis upp den dag en säkerhetsgranskning gör just den här omdöpningen

HotXLS är ett inbyggt kalkylbladsbibliotek för Delphi och C++Builder, och det håller de två funktionerna på motsatta sidor av den linjen. Arbetsblads- och arbetsboksskydd är redigeringsbegränsningar som stöds av en medvetet svag äldre hash. SaveAsEncrypted producerar ett AES-krypterat paket som inget annat än lösenordet kan öppna. Avsnitten nedan beskriver vad det anropet skriver, asymmetrin du måste bygga din design kring (HotXLS skriver krypterade filer men kan inte läsa tillbaka dem) och hur den äldre XLS-sökvägen skiljer sig åt

Varför bladskydd inte är kryptering

Metoderna Protect på blad och ProtectWorkbook på arbetsboken sparar en 4-hexadecimal-siffrig hash av lösenordet. Det är den äldre algoritm som OOXML och BIFF båda ärvde från 1990-talets Excel, och formatdokumentationen påstår aldrig att den gör mer än att förhindra oavsiktliga redigeringar. Paketet förblir en vanlig läsbar zip-fil: celldata, formler och delade strängar i klartext XML. Standardinställningen gör det värre, inte bättre. Varje cell börjar med Locked=True, så att anropa Protect utan att först låsa upp ett inmatningsintervall fryser hela bladet mot redigering medan varje värde lämnas helt synligt

Inget av detta gör skydd värdelöst. Att styra användare till redigerbara intervall och stabilisera en layout för utskrift är verkliga uppgifter, vilka behandlas i vår artikel om arbetsbladsskydd och sidinställningar. Men detta är användbarhetsuppgifter. I samma ögonblick som kravet är konfidentialitet är det enda API som svarar mot det SaveAsEncrypted

Vad SaveAsEncrypted faktiskt skriver

Implementeringen följer ECMA-376 Standard Encryption, specifierad i [MS-OFFCRYPTO] avsnitt 2.3.4. Lösenordet körs genom 50 000 iterationer av SHA-1 för att härleda en AES-128-nyckel. Ett verifierarblock, krypterat med AES-128 i ECB-läge, låter en konsument bekräfta lösenordet innan den dekrypterar något, och hela arbetsbokspaketet krypteras sedan med AES-128 i CBC-läge. Det som hamnar på disken är inte alls en zip-fil. Det är en OLE Compound-fil som innehåller strömmarna EncryptionInfo, EncryptedPackage och DataSpaces, utan någon xl/-katalog som ett arkivverktyg kan lista, vilket är anledningen till att omdöpningstestet nu inte visar något läsbart. Excel 2007 och senare öppnar den med enbart lösenordet, och nuvarande LibreOffice läser också Standard Encryption

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  rc: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Payroll');
    Sheet.Cells[1, 1].Value := 'Employee'; // Anställd
    Sheet.Cells[1, 2].Value := 'Net pay'; // Nettolön
    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;

Behandla lösenordsvariabeln med samma omsorg som en anslutningssträng. Hämta den från ett valv eller en tjänst för genererade hemligheter i sista sekunden, logga den aldrig och skriv den aldrig i själva arbetsboken. Kontrollen av returkoden är inte en valfri ceremoni. En krypterad sparning som misslyckas halvvägs måste avbryta leveransen, eftersom det enda reservalternativ som den anropande koden kan erbjuda är en okrypterad kopia, och den kopian är exakt den incident som denna funktion finns till för att förhindra

Det finns också ett maskinkontrollerbart acceptanstest som nästan inte kostar någonting: anropa CanReadEncrypted på filen du precis skrev. Det returnerar sant endast när utdata verkligen är en krypteringsbehållare, så att verifiera det efter varje krypterad sparning fångar den regression som spelar mest roll — en kodväg som tyst föll tillbaka på en vanlig SaveAs — i samma ögonblick som det inträffar istället för veckor senare i en kunds inkorg. Det som framgår i sista hand avgörs dock vid en manuell öppning i Excel med det riktiga lösenordet under releasetestning

Write-only av design: hantera EXlsxEncryptionNotImplemented

Här är den asymmetri som bör forma din pipeline-arkitektur: HotXLS krypterar vid sparning men dekrypterar inte vid öppning. OpenEncrypted utlöser EXlsxEncryptionNotImplemented när den pekar på ett faktiskt krypterat paket; på en vanlig kalkylbok faller den helt enkelt igenom till en normal Open. Den kompletterande sökningen CanReadEncrypted upptäcker OLE-krypteringsbehållaren billigt, så att mottagningskoden kan dirigera sådana filer utan att utlösa undantaget:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.CanReadEncrypted(FileName) then
    begin
      // Krypterad behållare: HotXLS kan inte dekryptera den.
      Writeln(FileName + ': needs manual decryption in Excel first');
      Exit;
    end;
    try
      Book.OpenEncrypted(FileName, '');   // vanliga filer faller igenom till Open
      Writeln(FileName + ': opened, ' + IntToStr(Book.Sheets.Count) + ' sheet(s)');
    except
      on EXlsxEncryptionNotImplemented do
        Writeln(FileName + ': krypterad - dirigerad till manuell kö');
    end;
  finally
    Book.Free;
  end;
end;

Den asymmetrin har en tydlig arkitektonisk tolkning: kryptera vid leveransgränsen, sist. Behåll plaintext-originalet inom din förtroendegräns, i en databas, ett dokumentarkiv eller en åtkomstkontrollerad delad resurs, och producera den krypterade kopian som det sista steget innan filen lämnar systemet. En pipeline som endast arkiverar de krypterade utdata har låst sig ute från sina egna data, eftersom inget senare skede i samma system kan öppna dessa filer igen. När en efterföljande HotXLS-process behöver arbetsboken igen, ge den plaintext-originalet, aldrig leveransartefakten

AES-128 Standard Encryption och överensstämmelsegränsen för AES-256

Kryptering av Office-filer finns i två generationer. Standard Encryption, den som HotXLS skriver, använder AES-128 med SHA-1-nyckelgenerering. Agile Encryption kom senare och går över till AES-256 med SHA-512 och en annan, XML-beskriven nyckelbehållare. Båda öppnas transparent i Excel, och AES-128 är fortfarande beräkningsmässigt säkert för att skydda en fil under överföring till en kund

Skillnaden upphör att vara akademisk den dag ett säkerhetsformulär frågar efter ”AES-256-kryptering av vilande filer (files at rest)”. Standard Encryption uppfyller inte den gränsen, oavsett hur starkt lösenordet är, och ingen parameter i SaveAsEncrypted ändrar algoritmen som den skickar ut. Ange därför profilen exakt i din säkerhetsdokumentation: AES-128, ECMA-376 Standard Encryption, SHA-1-nyckelgenerering med 50 000 iterationer. Ett påstående som överlever en granskning är mer värt än ett optimistiskt som faller samman under en revision

Den äldre XLS-sökvägen: RC4 ut, RC4 och XOR tillbaka in

BIFF-fasaden har motsatt form. Dess kryptering är äldre och svagare, men round-trip är komplett: det den skriver kan den också läsa tillbaka. Att ställa in EncryptionPassword före SaveAs producerar en RC4-krypterad .xls via BIFF FilePass-mekanismen, och Open med en lösenordsparameter läser alla tre äldre scheman: RC4, RC4 CryptoAPI och den urgamla XOR-obfuskeringen:

var
  Writer, Reader: IXLSWorkbook;   // gränssnittsreferenser: ingen manuell Free
begin
  Writer := TXLSWorkbook.Create;
  Writer.Sheets.Add.Cells.Item[1, 1].Value := 'Confidential'; // Konfidentiellt
  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);  // Poster är 1-baserade
end;

RC4 är föråldrad kryptografi och bör aldrig skydda data som har betydelse idag; dess endast kvarvarande värde är samverkan med system som fortfarande utbyter .xls. Lässidan gör dock nytta i migreringsarbete. En lösenordsskyddad äldre fil öppnas med Open(FileName, Password), bryggas över till OOXML-modellen och säkras på nytt via AES-sökvägen — en enkelriktad uppgradering som körs utan att Excel behövs någonstans i loopen. För krypterade leveranser med hög volym gäller anmärkningarna om genomströmning på sparningssidan i vår artikel om strömmande skrivningar för serverseriejobb för den fas där innehållet byggs upp, vilket sker före krypteringen

Kryptering och skydd är inte rivaler

Ytterligare en punkt är värd att reda ut, eftersom den dyker upp i samma ögonblick som någon läser varningen högst upp på denna sida som ”skydd är värdelöst”. Det är det inte. Kryptering och skydd besvarar olika frågor, och de kan kombineras rent. Kryptering avgör vem som kan öppna filen; skydd avgör vad en läsare som redan är inne i filen får ändra. En löneleverans kan rimligen göra båda: kryptera paketet så att endast innehavaren av lösenordet ser det, och sedan låsa formelcellerna så att mottagaren kan filtrera och sortera men inte i hemlighet skriva om beräkningarna. Felet är aldrig att lägga till skydd. Felet är att låta dess närvaro ersätta kryptering när kravet var konfidentialitet

Lösenordshanteringen har inget skyddsnät, och det är avsiktligt. Nyckelgenereringen med 50 000 iterationer finns till för att göra gissningar dyra, och ingenting inuti filen deponerar hemligheten. Ett förlorat lösenord är förlorade data. Generera, leverera och lagra dessa lösenord med samma disciplin som du tillämpar på databasautentiseringsuppgifter, så sköter krypteringen sin del

Riktig filkryptering är ett anrop i HotXLS. Disciplinen lever i allt runt anropet: lösenordshantering, gränsen för write-only som förhindrar HotXLS från att öppna sina egna utdata igen, och ett påstående om algoritm som du kan försvara i en granskning. SaveAsEncrypted och den äldre round-trip levereras med HotXLS Component, och körs inbyggt i Delphi- och C++Builder-processer utan någon Excel-automation någonstans i kedjan