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
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
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
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