Excel 公开了两个都被称为“密码”的内容,但其中只有一个是加密;打开密码锁定了一个真正的密码器:没有它,文件根本无法被读取;工作表和工作簿保护密码则完全不是这样;它们设置了一个协作编辑器同意遵守的标志,而只携带该标志的工作簿只是一个普通的、可读 of zip 压缩包,数据以明文形式存在;选错了密码,您发出的工资单在 Excel 中看起来是被锁定的,但却可以用任何文本编辑器来读取
证明这一点只需要 10 秒钟:将受保护的 .xlsx 重命名为 .zip,在任何归档工具中打开它,并查看 xl/worksheets/sheet1.xml;如果单元格的值以纯 UTF-8 形式存在,那么该文件就是未加密的,无论当有人尝试编辑单元格时 Excel 弹出了多少次密码提示;在那些认为“工作表保护”就是“机密性”的团队中,这种认知差距存在了多年,并且通常在安全审查运行了上述重命名的那一天暴露出来
HotXLS 是原生的 Delphi 和 C++Builder 电子表格库,它将这两项功能分列在该界线的两侧;工作表和工作簿保护是由故意设计得很弱的传统哈希支持的编辑限制;SaveAsEncrypted 生成一个 AES 加密包,除了输入密码,没有其他办法能打开它;以下各节涵盖了该调用写入的内容、您必须围绕其进行设计的非对称性(HotXLS 可以写入加密文件,但无法读回它们),以及旧的 XLS 路径有何不同
为什么工作表保护不是加密
工作表上的 Protect 方法和工作簿上的 ProtectWorkbook 存储密码的 4 位十六进制哈希值;这是 OOXML 和 BIFF 继承自 1990 年代 Excel 的传统算法,且格式文档从未声称它除了阻止意外编辑之外还能做更多事情;该包依然是一个普通可读的 zip:单元格数据、公式和共享字符串都在明文 XML 中;默认设置使情况变得更糟,而不是更好:每个单元格一开始都是 Locked=True,因此在不先解锁输入范围的情况下调用 Protect 会冻结整个工作表使其无法编辑,同时将每个值都暴露在众目睽睽之下
这并不意味着保护是无用的;引导用户进入可编辑范围以及稳定打印布局是实实在在的工作,这在我们关于工作表保护和页面设置的文章中进行了介绍;但这些是易用性的工作;一旦需求是“机密性”,唯一能响应的 API 就是 SaveAsEncrypted
SaveAsEncrypted 实际写入的内容
该实现遵循 ECMA-376 标准加密(在 [MS-OFFCRYPTO] 第 2.3.4 节中指定);密码经过 50,000 次 SHA-1 迭代以派生出 AES-128 密钥;用 AES-128 在 ECB 模式下加密的验证器块允许使用者在解密任何内容前确认密码,然后整个工作簿包用 AES-128 在 CBC 模式下进行加密;落在磁盘上的根本不是 zip,它是一个包含 EncryptionInfo、EncryptedPackage 和 DataSpaces 流的 OLE 复合文件,没有可供归档工具列出的 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;
应像对待连接字符串一样谨慎对待密码变量;在最后一刻从保险库或生成的秘密服务中获取它,绝不记录它,也绝不将其写入工作簿本身;返回码检查并不是可选的仪式;中途失败的加密保存必须中止交付,因为调用代码能提供的唯一备用方案是未加密的副本,而该副本正是本功能旨在防止发生的事件
还有一个几乎不需要成本的机器可检测的验收测试:在您刚刚写入的文件上调用 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;
这种非对称性有一个明确的架构解释:在交付的最外缘(最后)进行加密;将明文 master 保留在您的信任边界内(例如在数据库、文档存储或受访问控制的共享中),并在文件离开系统的最后一步生成加密副本;仅归档加密输出的管道会使自己无法访问自身的数据,因为该系统的后续阶段无法重新打开这些文件;当下游的 HotXLS 进程再次需要该工作簿时,向其提供明文 master,而绝不是交付的副本
AES-128 标准加密与 AES-256 合规线
Office 文件加密经历了两个世代;Standard Encryption(HotXLS 写入的这一种)使用带有 SHA-1 密钥派生的 AES-128;Agile Encryption 问世较晚,它转向了带有 SHA-512 和不同的 XML 描述密钥容器的 AES-256;两者在 Excel 中都是透明打开的,而且 AES-128 在保护传输给客户的文件时在计算上依然是健全的
当安全调查问卷要求“对静态文件进行 AES-256 加密”的那一天起,这种区别就变得不再是学术性的了;无论密码多么强大,Standard Encryption 都不符合该标准,并且 SaveAsEncrypted 的任何参数都不会改变它所发出的算法;因此,在您的安全文档中准确陈述其特征:AES-128、ECMA-376 标准加密、50,000 次迭代的 SHA-1 密钥派生;能经受住审查的声明比在审计下崩溃的乐观声明更有价值
传统的 XLS 路径:RC4 输出,RC4 与 XOR 读回
BIFF 接口呈现出相反的形状;它的加密更老且更弱,但其往返是完整的:它所写入的也可以被读回;在 SaveAs 之前设置 EncryptionPassword 会通过 BIFF FilePass 机制生成 RC4 加密的 .xls,并且带密码参数的 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 自动化