Technical Article

Encrypt XLSX Files with AES in Delphi using HotXLS

Try this experiment on any 'password-protected' workbook produced by your application: rename the .xlsx to .zip, open it in any archive tool, and look at xl/worksheets/sheet1.xml. If the cell values are visible there in plain UTF-8, the file is not encrypted — regardless of how many password prompts Excel shows when a user tries to edit a sheet. Teams can ship payroll exports for years believing sheet protection is encryption, and the gap tends to surface when a security review performs exactly this rename test.

UK teams should align this hotxls xlsx aes protected output delphi workflow with local governance, audit, and data quality requirements before production release

HotXLS, a native Delphi and C++Builder spreadsheet library, separates the two features sharply: worksheet and workbook protection are editing restrictions with a deliberately weak legacy hash, whilst SaveAsEncrypted produces a genuinely AES-encrypted package that only the password will open. This guide covers what that call actually writes, the asymmetry you need to design around — HotXLS can write encrypted files but cannot read them back — and where the legacy XLS path differs.

The rename test: why sheet protection is not encryption

The Protect methods on sheets and ProtectWorkbook on the workbook store a 4-hex-digit hash of the password — the legacy algorithm the OOXML and BIFF specifications both inherited from 1990s Excel. Its purpose is to stop accidental edits, and the file format documentation never claims more. The package remains an ordinary readable zip; the cell data, formulas, and strings are all in cleartext XML. Worse still, the OOXML default is Locked=True on every cell, so calling Protect without unlocking an input area produces a fully frozen sheet that still is not confidential.

Protection has legitimate uses — guiding users to editable ranges, stabilizing layouts for printing, all covered in our article on worksheet protection and page setup — but as soon as the requirement says confidentiality, the only relevant API is SaveAsEncrypted.

What SaveAsEncrypted actually writes

The implementation uses ECMA-376 Standard Encryption as specified in [MS-OFFCRYPTO] section 2.3.4. The password is run through 50,000 iterations of SHA-1 to derive an AES-128 key; a verifier block encrypted with AES-128 ECB allows a consumer to confirm the password before decrypting anything; and the entire workbook package is encrypted with AES-128 in CBC mode. The result is no longer a zip but an OLE compound file containing EncryptionInfo, EncryptedPackage, and DataSpaces streams — which explains why the rename test now fails, as it should: there is no xl/ directory for an archive tool to show. Excel 2007 and later opens the file with only the password, and current LibreOffice handles Standard Encryption as well.

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;

Handle the password variable with the same care as a connection string: fetch it from a vault or generated-secret service at the last moment, never log it, and never write it into the workbook itself. The return-code check is not ceremony either — an encryption save that fails midway must abort the delivery, because the fallback of shipping an unencrypted copy is precisely the incident the feature exists to prevent.

A cheap machine-verifiable acceptance check exists, too: call CanReadEncrypted on the file you just wrote. It answers true only when the output really is an encryption container, so asserting it after every encrypted save catches the worst regression — a code path that silently fell back to a plain SaveAs — at the moment it happens instead of in a customer's inbox. The final arbiter remains a manual open in Excel with the intended password during release testing.

Write-only by design: handling EXlsxEncryptionNotImplemented

Here is the asymmetry that should shape your pipeline architecture: HotXLS encrypts on save but does not decrypt on open. OpenEncrypted raises EXlsxEncryptionNotImplemented when pointed at an actual encrypted package; on a plain workbook it simply falls through to a normal Open. The companion probe CanReadEncrypted detects the OLE encryption container cheaply, so intake code can route such files without triggering the exception:

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;

The architectural consequence: encrypt at the delivery edge, last. Keep the plaintext master within your trust boundary — database, document store, access-controlled share — and produce the encrypted copy as the final step before the file leaves. A pipeline that archives only the encrypted output has locked itself out of its own data, because no later stage of the same system can reopen those files. If a downstream HotXLS process must consume the workbook again, hand it the plaintext master, not the delivery artefact.

AES-128 Standard Encryption and the AES-256 compliance line

Office file encryption comes in two generations. Standard Encryption — what HotXLS writes — uses AES-128 with SHA-1 key derivation. Agile Encryption, introduced later, upgrades to AES-256 with SHA-512 and a different XML-described key container. Both open transparently in Excel, and AES-128 remains computationally sound for protecting files in transit to a customer.

The distinction matters the day a security questionnaire asks for 'AES-256 encryption of files at rest.' Standard Encryption does not satisfy that requirement, regardless of how strong the password is, and no parameter of SaveAsEncrypted changes the algorithm. State the actual profile — AES-128, ECMA-376 Standard Encryption, SHA-1 key derivation at 50,000 spins — in your security documentation up front. A precise claim that passes review beats an optimistic one that fails an audit.

The legacy XLS route: RC4 out, RC4 and XOR back in

The BIFF facade has the opposite shape: its encryption is older and weaker, but the round trip is complete. Setting EncryptionPassword before SaveAs produces an RC4-encrypted .xls (the BIFF FilePass mechanism), and Open with a password parameter reads all three legacy schemes — RC4, RC4 CryptoAPI, and the ancient XOR obfuscation:

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 obsolete cryptography and should never protect data that matters today; its value is interoperability with systems that still exchange .xls. The read capability, however, is genuinely useful in migration pipelines: password-protected legacy files can be opened with Open(FileName, Password), bridged to the OOXML model, and re-secured with the AES path — a one-way upgrade that works entirely without Excel. For high-volume encrypted deliveries, the save-side throughput notes in our article on streaming writes for server batch jobs apply to the content-building phase before encryption.

Frequently asked questions

Can HotXLS reopen an encrypted XLSX it created?

No. The write path is complete and Excel-compatible, but OpenEncrypted raises EXlsxEncryptionNotImplemented for any encrypted package, including HotXLS's own output. Verify deliveries by opening them in Excel with the intended password, and keep a plaintext master for any process that must read the data again.

Is a lost password recoverable from the file?

Not by design. The 50,000-iteration key derivation exists to make password guessing expensive, and there is no escrow within the file. Password custody is your application's responsibility — generate, deliver, and store passwords through the same secret-management discipline you use for credentials.

Should I combine sheet protection with encryption?

They solve different problems and stack cleanly: encryption controls who can open the file, protection controls what they can edit once inside. A payroll delivery might encrypt the package and also lock formula cells so the recipient can filter but not alter calculations. Just never let the presence of protection substitute for encryption in a confidentiality requirement.

Real file encryption is one call in HotXLS, and the discipline lies in everything around the call: password custody, the write-only boundary, and an accurate algorithm claim. SaveAsEncrypted and the legacy round-trip ship with the HotXLS Component, running natively in Delphi and C++Builder processes without Excel automation.