用 Delphi 在 Linux 上为 Windows EXE 签名

· 组件
用 Delphi 在 Linux 上为 Windows EXE 签名

为 Windows 程序签名一直意味着需要一台 Windows 机器。signtool 只能在 Windows 上运行,所以即便构建流水线在 Linux 上完成编译,最后要为文件盖上签名时,依然需要在某处准备一台 Windows 主机。

从 sgcSign 2026.10 开始,这台主机已经不是必需的了。sgcSign 中的 Authenticode 格式,也就是对 PE 文件做哈希并构建签名的部分,现在是 Delphi 代码,而不是对 Windows 签名 API 的调用。在 Windows 上,密钥运算仍然通过 CNG 完成;在其他所有平台上,则运行在 sgcSign 自身的加密实现中。因此,在 Windows 上为 EXE 签名的那些组件,同样可以在 Linux 64 位上构建并运行,而 Windows 也会像对待其他任何已签名文件一样,接受它们生成的文件。本文将展示一个完成这项工作的简短控制台程序,介绍如何为 Linux 构建它,以及如何检查签名结果。

这个 Delphi 程序在 Ubuntu 上为 EXE 签名,随后在 Windows 上检查同一个文件。YouTube 上也有

为什么要在 Linux 上签名

这个程序

这是一个控制台程序,接收待签名文件、输出文件、带密码的 PFX,以及可选的时间戳颁发机构。它在 Windows 和 Linux 上是完全相同的代码。

program SignExe;

{$APPTYPE CONSOLE}

uses
  SysUtils, sgcSign_Authenticode, sgcSign_KeyProvider_PFX, sgcSign_TSA;

var
  PFX: TsgcPFXKeyProvider;
  TSA: TsgcTSAClient;
  Signer: TsgcAuthenticodeSigner;
begin
  if ParamCount < 4 then
  begin
    Writeln('usage: SignExe <input.exe> <output.exe> <file.pfx> <password> [tsa-url]');
    Halt(1);
  end;
  PFX := TsgcPFXKeyProvider.Create(nil);
  TSA := TsgcTSAClient.Create(nil);
  Signer := TsgcAuthenticodeSigner.Create(nil);
  try
    try
      PFX.LoadFromFile(ParamStr(3), ParamStr(4));
      Signer.KeyProvider := PFX;
      Signer.Hash := ahSHA256;
      Signer.Level := alBES;
      Signer.Description := 'sgcSign demo';
      if ParamCount >= 5 then
      begin
        TSA.URL := ParamStr(5);
        Signer.TSAClient := TSA;
        Signer.Level := alT;
      end;
      Signer.SignFile(ParamStr(1), ParamStr(2));
      Writeln('Signer: ', PFX.Certificate.Subject);
      Writeln('Signed: ', ParamStr(2));
    except
      on E: Exception do
      begin
        Writeln('Error: ', E.Message);
        ExitCode := 1;
      end;
    end;
  finally
    Signer.Free;
    TSA.Free;
    PFX.Free;
  end;
end.

TsgcPFXKeyProvider 保存证书和私钥。TsgcAuthenticodeSigner 按照 Windows 的方式对 PE 文件做哈希,构建 PKCS#7 签名,并将其写入文件的证书表中。如果连接了 TsgcTSAClient,它还会获取一个 RFC 3161 时间戳,这样即使证书过期,签名依然保持有效。

为 Linux 64 位构建

在 Projects 窗口中,先在 SDK Manager 里配置好 Linux SDK,再添加 Linux 64-bit 作为目标平台,然后构建。sgcSign 的运行时包从 Delphi 10.3 起就已支持为 Linux64 构建。在 Linux 上,由于没有可以导入的 Windows 证书存储,PFX 会以纯 Pascal 代码解析,RSA 或 ECDSA 运算也在 sgcSign 自身的加密实现中完成。围绕它构建的签名格式,用的是与 Windows 上相同的代码。

运行并检查结果

$ file Hello.exe
PE32+ executable (console) x86-64, for MS Windows

$ ./SignExe Hello.exe Hello-signed-linux.exe ../certs/demo.pfx demo http://timestamp.digicert.com
Signer: O=eSeGeCe Demo, CN=sgcSign Demo Code Signing
Signed: Hello-signed-linux.exe

要在没有 Windows 的情况下检查结果,osslsigncode verify 会重新计算文件的摘要并核对其上的签名:Signature verification: ok

接着把文件复制到 Windows 上。PowerShell 和 signtool verify /pa /v 读取的是同一个签名:Windows 计算出的文件哈希与被签名时的哈希一致,签名者是这个演示证书,DigiCert 的时间戳也通过了验证。文件依然可以正常运行。

PS> Get-AuthenticodeSignature .\Hello-signed-linux.exe | Format-List SignatureType,
      @{n='Signer';e={$_.SignerCertificate.Subject}},
      @{n='TimeStamper';e={$_.TimeStamperCertificate.Subject}}

SignatureType : Authenticode
Signer        : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TimeStamper   : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US

Windows 唯一有异议的是这个演示证书,而不是签名本身。演示证书链的根是一个自签名的测试根证书,并未安装在这台机器上,所以 Windows 无法将它关联到自己信任的根证书。摘要、签名和时间戳全部通过验证。

Linux 与 Windows 上完全相同的字节

用同一个 PFX、不加时间戳,分别用上面程序的 Linux 版本和 Windows 版本为同一个 Hello.exe 签名,然后比较这两个文件。

Linux    82344 bytes  SHA-256 6FD63BB01A8E911AB099431BC76F3BFA4EF557082BFB8BC18CCBFA97A9E8079D
Windows  82344 bytes  SHA-256 6FD63BB01A8E911AB099431BC76F3BFA4EF557082BFB8BC18CCBFA97A9E8079D

两个文件逐字节完全相同。这种 RSA 签名是确定性的,而签名者除非被要求,否则不会写入签名时间,所以文件只取决于密钥和输入本身,而与执行签名的机器无关。时间戳是唯一会在每次运行中变化的部分,因为时间戳颁发机构盖上的是它被请求那一刻的时间。

密钥的来源

在 Linux 上,密钥可以来自 PFX 或 PEM 文件、PKCS#11 令牌或 HSM,也可以来自云签名服务:Azure Trusted Signing、AWS KMS、Google Cloud KMS、HashiCorp Vault,或某个 CSC 远程签名服务。出于显而易见的原因,Windows 证书存储提供程序是唯一仍然只能在 Windows 上使用的一个。

不只是 EXE

这一点对 sgcSign 中的每一个代码签名组件都成立:用于 EXE 和 DLL 的 TsgcAuthenticodeSigner,用于 .cat 目录文件的 TsgcCatalogSigner,用于 Windows Installer 的 .msi.msp 文件的 TsgcMSISigner,用于 .msix.appx 包的 TsgcAppxSigner,用于 PowerShell 脚本的 TsgcPowerShellSigner,以及用于校验以上所有内容的 TsgcAuthenticodeVerifier。它们全部可以在 Linux、macOS、iOS 和 Android 上构建并运行。

在服务器和命令行上也是如此

同样的情况也适用于库之外。为 Linux 构建的 sgcSign Server 会像在 Windows 上一样对 Authenticode、PowerShell、目录文件、MSI 和 MSIX 请求进行签名和校验,sgcsign 命令行工具可以从任意平台发送这些请求。在 Delphi 命令行工具中,仍然只有一条路径专属于 Windows:在本地计算 EXE 或 DLL 的哈希值,只发送哈希值。.NET 命令行工具在所有平台上都会走这条路径。

可用性

在 Linux、macOS、iOS 和 Android 上进行代码签名的能力,已经包含在面向 Delphi 的 sgcSign 2026.10 中。对于 Windows 程序而言,一切都没有变化:同样的组件、同样的属性、同样的输出结果。每一个属性都记录在 sgcSign 在线帮助中。

有疑问,或者想把某条流水线迁出 Windows 吗?联系我们,回复你的会是真正编写这些代码的人。