Artykuł techniczny

Szyfrowanie plików XLSX algorytmem AES w Delphi z HotXLS

Excel udostępnia dwie rzeczy nazywane tak samo „hasłem”, a tylko jedna z nich jest szyfrowaniem. Hasło otwarcia kluczuje prawdziwy szyfr: bez niego pliku w ogóle nie da się przeczytać. Hasła ochrony arkusza i skoroszytu nie robią nic w tym rodzaju. Ustawiają flagę, którą współpracujący edytor zgadza się honorować, a skoroszyt niosący wyłącznie tę flagę jest zwykłym, czytelnym archiwum zip z danymi leżącymi otwartym tekstem. Wybierz nie to co trzeba, a wyślesz listę płac, która w Excelu wygląda na zamkniętą, a czyta się w dowolnym edytorze tekstu

Dowód zajmuje dziesięć sekund. Zmień nazwę chronionego .xlsx na .zip, otwórz go w dowolnym narzędziu archiwizującym i spójrz na xl/worksheets/sheet1.xml. Jeśli wartości komórek są tam zwykłym UTF-8, plik nie jest zaszyfrowany, niezależnie od tego, ile monitów o hasło podnosi Excel, gdy ktoś próbuje edytować komórkę. Ta luka utrzymuje się latami w zespołach zakładających, że ochrona arkusza to poufność, i zwykle wychodzi na jaw w dniu, w którym przegląd bezpieczeństwa robi dokładnie tę zmianę nazwy

HotXLS to natywna biblioteka arkuszowa dla Delphi i C++Builder, która trzyma obie funkcje po przeciwnych stronach tej linii. Ochrona arkusza i skoroszytu to ograniczenia edycji oparte na celowo słabym, starszym skrócie. SaveAsEncrypted produkuje zaszyfrowany AES pakiet, którego nie otworzy nic poza hasłem. Poniższe sekcje omawiają, co to wywołanie zapisuje, asymetrię, wokół której musisz zaprojektować rozwiązanie (HotXLS zapisuje zaszyfrowane pliki, ale nie potrafi ich odczytać z powrotem), oraz to, czym różni się starsza ścieżka XLS

Diagram zestawiający ochronę arkusza XLSX w Delphi, która przechowuje słaby skrót i zostawia dane komórek czytelne w zwykłym zipie, z HotXLS SaveAsEncrypted, które wyprowadza klucz AES-128 i zapisuje kontener szyfrowania OLE
Ochrona arkusza przechowuje słaby skrót i zostawia pakiet czytelnym zipem. SaveAsEncrypted wyprowadza klucz AES-128 i zapisuje kontener OLE, którego żadne narzędzie archiwizujące nie wypisze

Dlaczego ochrona arkusza to nie szyfrowanie

Metody Protect na arkuszach i ProtectWorkbook na skoroszycie przechowują 4-cyfrowy skrót szesnastkowy hasła. To starszy algorytm, który OOXML i BIFF odziedziczyły po Excelu z lat dziewięćdziesiątych, a dokumentacja formatu nigdy nie twierdzi, że robi on coś więcej niż powstrzymywanie przypadkowych edycji. Pakiet zostaje zwykłym, czytelnym zipem: dane komórek, formuły i ciągi współdzielone, wszystko w XML-u otwartym tekstem. Domyślne ustawienie pogarsza sprawę, a nie poprawia. Każda komórka startuje z Locked=True, więc wywołanie Protect bez wcześniejszego odblokowania zakresu wejściowego zamraża cały arkusz przed edycją, zostawiając każdą wartość na widoku

Nic z tego nie czyni ochrony bezużyteczną. Kierowanie użytkowników do zakresów edytowalnych i stabilizowanie układu pod druk to prawdziwe zadania, omówione w naszym artykule o ochronie arkusza i ustawieniach strony. Ale to zadania z obszaru użyteczności. W chwili, gdy wymaganiem jest poufność, jedynym API, które na nie odpowiada, jest SaveAsEncrypted

Co SaveAsEncrypted naprawdę zapisuje

Implementacja idzie za ECMA-376 Standard Encryption, opisanym w [MS-OFFCRYPTO] w sekcji 2.3.4. Hasło przechodzi przez 50 000 iteracji SHA-1, żeby wyprowadzić klucz AES-128. Blok weryfikatora, zaszyfrowany AES-128 w trybie ECB, pozwala konsumentowi potwierdzić hasło, zanim cokolwiek odszyfruje, a cały pakiet skoroszytu jest następnie szyfrowany AES-128 w trybie CBC. To, co ląduje na dysku, nie jest w ogóle zipem. To plik złożony OLE trzymający strumienie EncryptionInfo, EncryptedPackage i DataSpaces, bez katalogu xl/, który narzędzie archiwizujące mogłoby wypisać, i dlatego test ze zmianą nazwy nie znajduje już nic czytelnego. Excel 2007 i nowsze otwiera go samym hasłem, a obecne LibreOffice też czyta Standard Encryption

Diagram potoku wywołania HotXLS SaveAsEncrypted w Delphi: hasło z sejfu przepuszczone przez 50 000 rund SHA-1 w klucz AES-128, blok weryfikatora ECB i szyfrowanie pakietu w CBC, dające plik złożony OLE sprawdzany przez CanReadEncrypted
Jedno wywołanie SaveAsEncrypted zamienia hasło z sejfu w klucz AES-128 i plik złożony OLE. CanReadEncrypted daje zapisowi bramkę akceptacji sprawdzalną maszynowo
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;

Traktuj zmienną z hasłem z taką samą troską jak ciąg połączenia. Pobieraj je z sejfu albo z usługi generującej sekrety w ostatniej chwili, nigdy nie zapisuj do logu i nigdy nie wpisuj do samego skoroszytu. Sprawdzenie kodu powrotu nie jest opcjonalną ceremonią. Zapis szyfrujący, który zawiedzie w połowie, musi przerwać dostawę, bo jedynym zapasowym wyjściem, jakie może zaproponować kod wywołujący, jest kopia niezaszyfrowana, a właśnie ta kopia to incydent, któremu ta funkcja ma zapobiegać

Istnieje też sprawdzalny maszynowo test akceptacyjny, który nie kosztuje prawie nic: wywołaj CanReadEncrypted na pliku, który właśnie zapisałeś. Zwraca prawdę tylko wtedy, gdy wyjście naprawdę jest kontenerem szyfrowania, więc asercja po każdym zapisie szyfrującym łapie najważniejszą regresję, czyli ścieżkę kodu, która po cichu cofnęła się do zwykłego SaveAs, w chwili jej wystąpienia, a nie tygodnie później w skrzynce klienta. Ostatnie słowo wciąż należy do ręcznego otwarcia w Excelu z prawdziwym hasłem podczas testów wydania

Tylko do zapisu z założenia: obsługa EXlsxEncryptionNotImplemented

Oto asymetria, która powinna ukształtować architekturę twojego potoku: HotXLS szyfruje przy zapisie, ale nie deszyfruje przy otwarciu. OpenEncrypted zgłasza EXlsxEncryptionNotImplemented, gdy wskażesz mu faktycznie zaszyfrowany pakiet; na zwykłym skoroszycie po prostu przechodzi do normalnego Open. Towarzysząca sonda CanReadEncrypted tanio wykrywa kontener szyfrowania OLE, więc kod przyjmujący pliki może kierować takie pliki dalej bez wyzwalania wyjątku:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.CanReadEncrypted(FileName) then
    begin
      // Kontener zaszyfrowany: HotXLS nie potrafi go odszyfrować.
      Writeln(FileName + ': needs manual decryption in Excel first');
      Exit;
    end;
    try
      Book.OpenEncrypted(FileName, '');   // zwykłe pliki przechodzą do 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;

Ta asymetria ma jedno jasne odczytanie architektoniczne: szyfruj na krawędzi dostawy, na samym końcu. Trzymaj wzorzec w postaci jawnego tekstu wewnątrz swojej granicy zaufania, w bazie danych, repozytorium dokumentów albo udziale z kontrolą dostępu, a kopię zaszyfrowaną produkuj jako ostatni krok, zanim plik opuści system. Potok, który archiwizuje wyłącznie zaszyfrowane wyjście, zamknął się przed własnymi danymi, bo żaden późniejszy etap tego samego systemu nie otworzy tych plików ponownie. Gdy proces HotXLS niżej w potoku znów potrzebuje skoroszytu, podaj mu wzorzec jawny, nigdy artefakt dostawy

Diagram architektury dla usług Delphi: wzorzec skoroszytu w postaci jawnego tekstu zostaje wewnątrz granicy zaufania, HotXLS SaveAsEncrypted działa jako ostatni krok na krawędzi dostawy, plus ostrzeżenie przed archiwizowaniem wyłącznie kopii zaszyfrowanej
Szyfruj na krawędzi dostawy, na samym końcu, z jawnego wzorca trzymanego wewnątrz granicy zaufania. Archiwizowanie wyłącznie kopii zaszyfrowanej zamyka każdy późniejszy etap przed jego własnymi danymi

AES-128 Standard Encryption i linia zgodności AES-256

Szyfrowanie plików Office występuje w dwóch generacjach. Standard Encryption, to które zapisuje HotXLS, używa AES-128 z wyprowadzaniem klucza przez SHA-1. Agile Encryption pojawiło się później i przechodzi na AES-256 z SHA-512 oraz na inny, opisany w XML kontener klucza. Oba otwierają się w Excelu przezroczyście, a AES-128 wciąż jest obliczeniowo solidne, gdy chodzi o ochronę pliku w drodze do klienta

Różnica przestaje być akademicka w dniu, w którym ankieta bezpieczeństwa pyta o „szyfrowanie plików w spoczynku algorytmem AES-256”. Standard Encryption nie spełnia tej linii, niezależnie od tego, jak mocne jest hasło, i żaden parametr SaveAsEncrypted nie zmienia emitowanego algorytmu. Podaj więc profil precyzyjnie w swojej dokumentacji bezpieczeństwa: AES-128, ECMA-376 Standard Encryption, wyprowadzanie klucza SHA-1 przy 50 000 iteracji. Twierdzenie, które przetrwa przegląd, jest warte więcej niż optymistyczne, które wali się pod audytem

Starsza trasa XLS: RC4 na wyjściu, RC4 i XOR z powrotem

Fasada BIFF ma odwrotny kształt. Jej szyfrowanie jest starsze i słabsze, ale pełny cykl jest kompletny: to, co zapisuje, potrafi też odczytać z powrotem. Ustawienie EncryptionPassword przed SaveAs produkuje zaszyfrowany RC4 plik .xls przez mechanizm BIFF FilePass, a Open z parametrem hasła czyta wszystkie trzy starsze schematy: RC4, RC4 CryptoAPI oraz prastare zaciemnianie XOR:

var
  Writer, Reader: IXLSWorkbook;   // referencje interfejsowe: bez ręcznego 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);  // Wpisy liczone od jedynki
end;

RC4 to przestarzała kryptografia i nigdy nie powinno chronić danych, na których dziś zależy; jego jedyną pozostałą wartością jest współdziałanie z systemami, które nadal wymieniają .xls. Strona odczytu zarabia jednak na siebie w pracach migracyjnych. Chroniony hasłem starszy plik otwiera się przez Open(FileName, Password), przechodzi mostem do modelu OOXML i zostaje na nowo zabezpieczony ścieżką AES, a to jednokierunkowa aktualizacja biegnąca bez Excela w jakimkolwiek miejscu pętli. Dla wysokowolumenowych dostaw szyfrowanych uwagi o przepustowości po stronie zapisu z naszego artykułu o zapisie strumieniowym dla serwerowych zadań wsadowych stosują się do fazy budowania treści, która dzieje się przed szyfrowaniem

Szyfrowanie i ochrona nie są rywalami

Jeszcze jedna rzecz warta rozstrzygnięcia, bo pojawia się w chwili, gdy ktoś odczyta ostrzeżenie z początku tej strony jako „ochrona jest bezwartościowa”. Nie jest. Szyfrowanie i ochrona odpowiadają na różne pytania i układają się czysto w stos. Szyfrowanie decyduje, kto może plik otworzyć; ochrona decyduje, co może zmienić czytelnik, który już jest w środku. Dostawa listy płac może rozsądnie robić jedno i drugie: zaszyfruj pakiet, żeby widział go tylko posiadacz hasła, a potem zablokuj komórki z formułami, żeby odbiorca mógł filtrować i sortować, ale nie po cichu przepisywać obliczeń. Błędem nigdy nie jest dodanie ochrony. Błędem jest pozwolić, by jej obecność zastąpiła szyfrowanie, gdy wymaganiem była poufność

Strona pieczy nad hasłem nie ma siatki bezpieczeństwa, i to z założenia. Wyprowadzanie klucza w 50 000 iteracji istnieje po to, by zgadywanie było drogie, a nic wewnątrz pliku nie deponuje sekretu. Hasło stracone to dane stracone. Generuj, dostarczaj i przechowuj te hasła z tą samą dyscypliną, którą stosujesz do poświadczeń bazodanowych, a szyfrowanie wywiąże się ze swojej części

Prawdziwe szyfrowanie pliku to w HotXLS jedno wywołanie. Dyscyplina mieszka we wszystkim wokół tego wywołania: w pieczy nad hasłem, w granicy tylko do zapisu, która nie pozwala HotXLS ponownie otworzyć własnego wyjścia, i w twierdzeniu o algorytmie, którego da się bronić w audycie. SaveAsEncrypted oraz pełny cykl dla starszego formatu są częścią HotXLS Delphi Component, działającego natywnie w procesach Delphi i C++Builder, bez automatyzacji Excela w jakimkolwiek punkcie ścieżki