为 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 上签名
- 构建代理。托管的 CI 运行器和构建容器首先是基于 Linux 的。在构建文件的同一个代理上完成签名,省去了跳转到 Windows 机器的那一步,也省去了该步骤所需的凭据。
- 密钥所在的位置。代码签名密钥理应保存在 HSM 或云端密钥库中,而这些地方从 Linux 访问同样很方便。sgcSign 中的 PKCS#11 和云密钥提供程序在每个平台上都能工作。
- 一套工具随处可用。同一个 Delphi 程序既能在开发者的 Windows 机器上签名,也能在发布服务器上签名,使用相同的选项,得到相同的结果。
这个程序
这是一个控制台程序,接收待签名文件、输出文件、带密码的 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 吗?联系我们,回复你的会是真正编写这些代码的人。
