Technical Article

Шифриране на XLSX изход с AES в Delphi: какво записва функцията SaveAsEncrypted на HotXLS

Excel предлага две неща, наречени „паролаâ€? и само едно от тях е шифриране. Паролата за отваряне задейства истински шифър: без нея файлът изобщо не може да бъде прочетен. Паролите за защита на работния лист и работната книга не правят нищо подобно. Те просто задават флаг, който съвместим редактор се съгласява да спазва, а работна книга, съдържаща само този флаг, е обикновен четлив zip архив с данни в чист текст. Изберете грешния вариант и ще изпратите ведомости за заплати, които изглеждат заключени в Excel, но се четат във всеки текстов редактор.

Доказателството отнема десет секунди. Преименувайте защитен .xlsx файл на .zip, отворете го в който и да е архиватор и погледнете xl/worksheets/sheet1.xml. Ако стойностите на клетките са там в чист UTF-8, файлът не е шифриран, независимо колко подкани за парола показва Excel, когато някой се опита да редактира клетка. Този пропуск оцелява с години в екипи, които приемат защитата на листа за поверителност, и обикновено излиза наяве в деня, в който прегледът на сигурността извърши точно това преименуване.

HotXLS е вградена библиотека за електронни таблици за Delphi и C++Builder, която държи двете функции от двете страни на тази разделителна линия. Защитата на работния лист и работната книга представлява ограничение за редактиране, подкрепено от умишлено слаб остарял хеш. SaveAsEncrypted генерира AES-шифриран пакет, който нищо друго освен паролата не може да отвори. Разделите по-долу описват какво записва това извикване, асиметрията, около която трябва да проектирате архитектурата си (HotXLS пише шифрирани файлове, но не може да ги чете обратно), и по какво се различава по-старият XLS път.

Защо защитата на листа не е шифриране

Методите Protect за листовете и ProtectWorkbook за работната книга съхраняват 4-цифрен шестнадесетичен хеш на паролата. Това е остарелият алгоритъм, който OOXML и BIFF наследиха от Excel от 90-те години на миналия век, и документацията на формата никога не твърди, че той прави нещо повече от предотвратяване на случайни редакции. Пакетът остава обикновен четлив zip: данни за клетки, формули и споделени низове в XML с чист текст. Настройките по подразбиране влошават нещата: всяка клетка започва с Locked=True, така че извикването на Protect без първо отключване на входния диапазон замразява целия лист срещу редактиране, оставяйки всяка стойност напълно видима.

Нищо от това не прави защитата безполезна. Насочването на потребителите към диапазони за редактиране и стабилизирането на оформлението за печат са реални задачи, описани в нашата статия за защита на работни листове и настройки на страници. Но това са задачи за използваемост. В момента, в който изискването е поверителност, единственият API, който отговаря на това, е SaveAsEncrypted.

Какво всъщност записва SaveAsEncrypted

Внедряването следва ECMA-376 Standard Encryption, посочено в раздел 2.3.4 на [MS-OFFCRYPTO]. Паролата преминава през 50 000 итерации на SHA-1 за извеждане на AES-128 ключ. Блок за проверка (verifier block), шифриран с AES-128 в режим ECB, позволява на консуматора да потвърди паролата, преди да дешифрира каквото и да е, а целият пакет на работната книга се шифрира с AES-128 в режим CBC. Това, което попада на диска, изобщо не е zip файл. Това е OLE съставен файл, съдържащ потоци EncryptionInfo, EncryptedPackage и DataSpaces, без директория xl/, която архиватор да може да покаже, поради което тестът с преименуване не разкрива нищо четливо. Excel 2007 и по-нови версии го отварят само с паролата, а съвременният LibreOffice също чете стандартното шифриране.

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;

Третирайте променливата за парола със същата грижа като низ за връзка с база данни (connection string). Вземайте я от трезор или от услуга за генериране на тайни в последния момент, никога не я регистрирайте в лог файлове и никога не я записвайте в самата работна книга. Проверката на кода за връщане не е незадължителна церемония. Запис за шифриране, който се провали по средата, трябва да прекрати доставката, тъй като единствената резервна възможност, която извикващият код може да предложи, е нешифрирано копие â€?а това копие е именно инцидентът, който тази функция съществува да предотврати.

Съществува и тест за приемане, проверим от машина, който не струва почти нищо: извикайте CanReadEncrypted на току-що записания файл. Той връща true само когато изходът наистина е контейнер за шифриране, така че потвърждаването му след всеки шифриран запис улавя най-важния регресионен проблему â€?път на кода, който тихомълком се е върнал към обикновен SaveAs â€?в момента на възникването му, а не седмици по-късно в пощенската кутия на клиента. Последната дума все пак принадлежи на ръчното отваряне в Excel с реалната парола по време на тестовете за пускане.

Само за запис по дизайн: обработка на EXlsxEncryptionNotImplemented

Ето я асиметрията, която трябва да оформи архитектурата на вашия процес: HotXLS шифрира при запис, но не дешифрира при отваряне. OpenEncrypted хвърля EXlsxEncryptionNotImplemented, когато е насочен към действително шифриран пакет; при обикновена работна книга просто преминава към нормално Open извикване. Съпътстващата сонда CanReadEncrypted открива лесно OLE контейнера за шифриране, така че входящият код може да насочва такива файлове, без да задейства изключението:

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;

Тази асиметрия има едно ясно архитектурно тълкуване: шифрирайте последно, в самия край на доставката. Пазете оригиналния документ в чист текст в границата на вашата сигурна зона â€?в база данни, хранилище за документи или споделен ресурс с контролиран достъп â€?и генерирайте шифрираното копие като последна стъпка, преди файлът да напусне системата. Процес, който архивира само шифрирания изход, се е заключил за собствените си данни, тъй като никоя по-късна стъпка на същата система не може да отвори отново тези файлове. Когато последващ процес на HotXLS се нуждае отново от работната книга, подайте му оригинала в чист текст, а не артефакта за доставка.

AES-128 Standard Encryption и съответствието с AES-256

Шифрирането на файлове в Office се дели на две поколения. Standard Encryption, което HotXLS записва, използва AES-128 с деривация на ключове по SHA-1. Agile Encryption се появи по-късно и преминава към AES-256 с SHA-512 и различен, описан с XML контейнер за ключове. И двете се отварят прозрачно в Excel, а AES-128 все още е изчислително стабилен за защита на файл при предаване към клиент.

Разликата спира да бъде академична в деня, в който въпросник за сигурност поиска „AES-256 шифриране на файлове в покойâ€? Standard Encryption не отговаря на това изискване, независимо колко силна е паролата, и никой параметър на SaveAsEncrypted не променя излъчвания алгоритъм. Така че посочете точния профил в документацията си за сигурност: AES-128, ECMA-376 Standard Encryption, деривация на ключове по SHA-1 с 50 000 итерации. Твърдение, което издържа на преглед, струва повече от оптимистично такова, което се проваля при одит.

Наследственият XLS път: RC4 изход, RC4 и XOR обратно вътре

BIFF интерфейсът има противоположна форма. Неговото шифриране е по-старо и по-слабо, но пълният цикъл е завършен: това, което записва, той може да прочете обратно. Настройването на EncryptionPassword преди SaveAs генерира RC4-шифриран .xls файл чрез механизма FilePass на BIFF, а Open с параметър за парола чете и трите наследствени схеми â€?RC4, RC4 CryptoAPI и древната XOR обфускация:

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 е остаряла криптография и никога не трябва да защитава данни, които имат значение днес; единствената му останала стойност е съвместимостта със системи, които все още обменят .xls. Страната на четене обаче е много ценна при миграция на данни. Защитен с парола наследствен файл се отваря с Open(FileName, Password), преминава през моста към модела OOXML и се защитава отново през AES пътя â€?еднопосочно надграждане, което работи без Excel никъде по веригата. За шифрирани доставки в големи обеми бележките за пропускателна способност при запис от нашата статия за потоково писане за пакетни задания важат за фазата на изграждане на съдържанието, която се случва преди шифрирането.

Шифрирането и защитата не са съперници

Още една точка, която си струва да се уточни, тъй като се появява в момента, в който някой прочете предупреждението в началото на тази страница като „защитатÐ?е безполезнаâ€? Тя не е. Шифрирането и защитата отговарят на различни въпроси и се допълват чисто. Шифрирането определя кой може да отвори файла; защитата определя какво може да промени читател, който вече е вътре. При ведомостите за заплати е разумно да се направят и двете: да се шифрира пакетът, така че само притежателят на паролата да го вижда, и да се заключат клетките с формули, така че получателят да може да филтрира и сортира, но не и тихомълком да пренаписва изчисленията. Грешката не е в добавянето на защита. Грешката е да се остави нейното присъствие да замести шифрирането, когато изискването е било поверителност.

Страната на съхранение няма предпазна мрежа и това е по дизайн. Деривацията на ключа с 50 000 итерации съществува, за да направи отгатването скъпо, и нищо в самия файл не архивира тайната. Изгубена парола означава загубени данни. Генерирайте, доставяйте и съхранявайте тези пароли със същата дисциплина, която прилагате към идентификационните данни за бази данни, и шифрирането ще свърши своята работа.

Истинското шифриране на файлове е едно извикване в HotXLS. Дисциплината е във всичко около това извикване: съхранението на паролата, границата само за запис, която пречи на HotXLS да отвори отново собствения си изход, и твърдението за алгоритъма, което можете да защитите при одит. SaveAsEncrypted и наследственият двупосочен път се доставят с HotXLS Component, като работят вградено в процеси на Delphi и C++Builder без никаква автоматизация на Excel по целия път.