sgcSign 五分钟上手
两个组件即可对文档签名:签名器和密钥提供程序。本页使用 Windows 证书存储区中的证书,以 PAdES 对 PDF 签名,然后展示如何验证您生成的结果。如果您想对 XML 签名,下方链接了一份更详细的 XAdES 演练。
两个组件即可对文档签名:签名器和密钥提供程序。本页使用 Windows 证书存储区中的证书,以 PAdES 对 PDF 签名,然后展示如何验证您生成的结果。如果您想对 XML 签名,下方链接了一份更详细的 XAdES 演练。
签名器了解文档格式。密钥提供程序了解私钥存放在哪里。它们通过一个属性相互关联。
TsgcPAdESSigner,声明在 sgcSign_PAdES.pas 中,注册在 SGC Sign 组件面板页上。SignPDFFile 接受输入路径和输出路径。
Windows 证书存储区使用 TsgcWindowsCertStoreProvider,.pfx 文件使用 TsgcPFXKeyProvider。两者都在同一个组件面板页上。
KeyProvider,其类型是接口 IsgcKeyProvider,而不是组件引用。这一区别关系到生命周期,下面的常见问题部分会解释原因。
Win32、Win64、Linux64、Intel 和 Apple Silicon 上的 macOS、iOS 和 Android。在 Windows 上,哈希和签名通过 Windows CNG API 完成,在其他所有平台上则通过库自己的纯 Pascal 密码学实现,无需部署 OpenSSL。Windows 证书存储区提供程序是唯一仍然仅限 Windows 的组件。
sgcSign 没有功能层级,因此这张表讲的是编译器和平台,而不是版本。
| 项目 | 值 |
|---|---|
| IDE | Delphi 7 到 RAD Studio 13,以及 C++Builder。C++Builder 的做法是把 lib 文件夹放在 System Include 路径上,而不是库路径上。 |
| uses 子句 | 演示中写的是 sgcSign_Types, sgcSign_Interfaces, sgcSign_Classes, sgcSign_PAdES, sgcSign_KeyProvider_WinCertStore, sgcSign_KeyProvider_PEM, sgcSign_KeyProvider_PFX。请去掉您不使用的提供程序。 |
| 版本 | 没有版本之分。产品自己的 sgcVer.inc 中完全没有 SGC_EDT_* 定义,也没有任何功能按层级限制。一个库,每个组件,每种许可证都包含。商业层级按席位数量划分:单个、团队和站点,另外还有免费的 Community Edition。 |
| 平台,已在源代码中核实 | Win32、Win64、Linux64、OSX64、OSXARM64、iOS 和 Android。密码学通过一个衔接单元 sgcSign_Crypto.pas 完成,在 Windows 上是 CNG,在其他所有平台上是纯 Pascal;HTTP 在 Windows 上是 WinHTTP,在其他平台上是 Delphi RTL 客户端;运行时包从 Delphi 10.3 起启用 Linux64 和 Intel 上的 macOS,从 10.4 起启用 Android 和 iOS,从 11 起启用 Apple Silicon 上的 macOS。sgcSign_KeyProvider_WinCertStore.pas 是唯一仅限 Windows 的单元。 |
| 外部依赖 | 无。在 Windows 上,库直接调用 Windows CNG 和 WinHTTP API;在其他平台上,它使用自己的纯 Pascal 密码学和 Delphi RTL HTTP 客户端,因此无需随应用程序部署 OpenSSL DLL。 |
| 默认值 | 全新的 TsgcPAdESSigner 已经带有可用的配置文件:构造函数设置了基本的 PAdES 配置文件、基线 B 签名级别和 SHA-256。您无需修改 Profile 就能生成有效的签名。 |
更喜欢 XML 而不是 PDF?五分钟 XAdES 演练改用 PFX 文件对 XML 文档签名,并涵盖 Delphi 7 的 Unicode 陷阱和 UTC 签名时间。本页则是 PDF 和证书存储区方面的对应内容。
请先编译运行时包,再安装设计时包,因为后者引用前者。
将下载文件解压到一个文件夹,下文称之为 {$DIR}。
依次选择 Tools、Environment Options、Directories。添加 {$DIR}\delphi\source,它适用于每个 RAD Studio 版本。
同时添加特定版本的文件夹,例如 RAD Studio 13 使用 {$DIR}\delphi\libD13\$(Platform),一直到 Delphi 7 的 libD7。对于 C++Builder,这些应改放在 System Include 路径上。
针对您的 IDE 版本打开 Packages\sgcSignD13.groupproj,C++Builder 则打开 sgcSignC13.groupproj。先编译 sgcSign 包,再安装 dclsgcSign 包。
会出现一个名为 SGC Sign 的页面,其中包含签名器、验证器、时间戳和 OCSP 客户端,以及十种密钥提供程序。在 Windows 上,它还包含 Authenticode 签名器和验证器。
选择一个证书,创建签名器,把它指向提供程序,然后调用 SignPDFFile。第一个选项卡使用 Windows 证书存储区,第二个使用 PFX 文件。
uses
SysUtils, Classes,
// sgcSign
sgcSign_Types, sgcSign_Interfaces, sgcSign_Classes,
sgcSign_PAdES, sgcSign_KeyProvider_WinCertStore;
procedure TFormMain.btnSignClick(Sender: TObject);
var
vSigner: TsgcPAdESSigner;
vKeyProvider: TsgcWindowsCertStoreProvider;
vProviderIntf: IsgcKeyProvider;
vOutputFile: string;
begin
vKeyProvider := TsgcWindowsCertStoreProvider.Create(nil);
vSigner := TsgcPAdESSigner.Create(nil);
try
vKeyProvider.SelectCertificateBySubject('My Company');
Log('Certificate found: ' + vKeyProvider.Certificate.Subject);
// Hold the interface in an explicit local: an inline "as" cast
// would leave a compiler-generated interface temporary alive in
// this stack frame until the routine returns, i.e. past
// vKeyProvider.Free, and releasing it would touch freed memory.
vProviderIntf := vKeyProvider as IsgcKeyProvider;
vSigner.KeyProvider := vProviderIntf;
vSigner.Reason := 'Demo signature';
vSigner.Location := 'Spain';
vSigner.SignerName := 'sgcSign Demo';
vOutputFile := ChangeFileExt(edInputFile.Text, '_signed.pdf');
vSigner.SignPDFFile(edInputFile.Text, vOutputFile);
Log('SUCCESS: PDF signed with PAdES profile');
Log('Output file: ' + vOutputFile);
finally
vSigner.Free;
vProviderIntf := nil;
vKeyProvider.Free;
end;
end;
请注意这里没有什么。Profile 从未被修改,因为构造函数已经设置了基本的 PAdES 配置文件、基线 B 签名级别和 SHA-256。关于接口临时变量的注释来自随包附带的演示本身,而 finally 块中的释放顺序正是它重要的原因。
uses
SysUtils, Classes,
// sgcSign
sgcSign_Types, sgcSign_Interfaces, sgcSign_Classes,
sgcSign_PAdES, sgcSign_KeyProvider_PFX;
var
vSigner: TsgcPAdESSigner;
vKeyProvider: TsgcPFXKeyProvider;
vProviderIntf: IsgcKeyProvider;
begin
vKeyProvider := TsgcPFXKeyProvider.Create(nil);
vSigner := TsgcPAdESSigner.Create(nil);
try
vKeyProvider.FileName := 'C:\certs\signer.pfx';
vKeyProvider.Password := GetPfxPassword;
vKeyProvider.LoadFromFile;
Log('Certificate loaded (PFX): ' + vKeyProvider.Certificate.Subject);
vProviderIntf := vKeyProvider as IsgcKeyProvider;
vSigner.KeyProvider := vProviderIntf;
vSigner.SignPDFFile('C:\docs\contract.pdf',
'C:\docs\contract_signed.pdf');
finally
vSigner.Free;
vProviderIntf := nil;
vKeyProvider.Free;
end;
end;
与第一个选项卡唯一的区别是您创建哪个提供程序,以及如何让它指向密钥。从 KeyProvider := 开始,之后的一切都完全相同,这对全部十个提供程序都成立,包括 PKCS#11 硬件和云密钥服务。
uses
SysUtils, Classes,
// sgcSign
sgcSign_Types, sgcSign_Interfaces, sgcSign_Verifier;
var
oVerifier: TsgcSignatureVerifier;
vVerifier: IsgcSignatureVerifier;
oStream: TFileStream;
begin
oVerifier := TsgcSignatureVerifier.Create(nil);
oStream := TFileStream.Create('C:\docs\contract_signed.pdf',
fmOpenRead or fmShareDenyWrite);
try
// VerifyPDF is reached through the interface the component implements
vVerifier := oVerifier as IsgcSignatureVerifier;
if vVerifier.VerifyPDF(oStream) = vsValid then
Writeln('valid')
else
Writeln(oVerifier.GetVerificationDetails);
finally
vVerifier := nil;
oStream.Free;
oVerifier.Free;
end;
end;
与签名器相同的接口规则:把类型转换赋给一个具名的局部变量,并在释放组件之前将其清除。当布尔值提供的证据不够时,GetValidationReportXML 会生成 ETSI 验证报告格式的报告。
前两个选项卡来自随包附带的演示 Demos\Delphi\PAdES\frmMain.pas,并把其分支压缩为每个选项卡一条路径。关于把接口保存在具名局部变量中的注释来自演示本身,值得保留。同类的更丰富的演示 Demos\Delphi\PAdES_Providers 用硬件和云提供程序完成同样的事情。
出现了一个文件,并不等于出现了一个能通过验证的签名。
选择之后,像演示那样读取 Certificate.Subject。这决定了您是用想要的证书签名,还是用最先匹配到的那个证书签名。IsLoaded 以布尔值回答同样的问题。
SignPDFFile 会写入您给它的输出路径。随包附带的演示使用 ChangeFileExt 推导该路径,使已签名的文件落在原文件旁边。
没有可检查的结果,因此请把调用放在 try except 中,并读取异常消息。演示就是这样做的,这也是唯一的失败通道。
一个文件并不是一个有效的签名。TsgcSignatureVerifier.VerifyPDF 返回 TsgcVerificationStatus,您将其与 vsValid 比较,GetVerificationDetails 会解释失败的原因。在 PDF 阅读器中打开该文件,也能让人看到同样的结果。
六个问题几乎涵盖了所有首次签名的情况。
这是演示在自己的注释中提醒过的问题。KeyProvider 接受 IsgcKeyProvider,因此内联的 as 类型转换会让编译器生成的接口引用一直存活到例程返回,而那是在您释放提供程序组件之后。请把接口赋给一个具名的局部变量,并按以下顺序释放:先是签名器,然后把接口设为 nil,最后是提供程序。
它是一个 TsgcSignProfileConfig 对象。您应设置 Profile.Profile 和 Profile.SignatureLevel,而不是 Profile := 'something'。构造函数已经填入了可用的默认值,因此第一个示例根本不需要修改它。
SelectCertificateBySubject 按主题匹配,SelectCertificateByThumbprint 按指纹匹配。选择之后,像演示那样读取 Certificate.Subject,这样您就能看到实际得到的是哪个证书。EnumerateCertificates 会列出可用的证书。
无法编译。签名器和每个提供程序都在接口部分的 uses 子句中无条件地放入了 Windows,因此这是编译错误,而不是空单元。sgcSign 是一个 Windows 库。
基本签名不携带信任锚点,也没有吊销数据。请通过 TSAClient 添加时间戳,并在文档需要在证书过期后仍可验证时,升级到长期配置文件。
这是设计使然。没有可检查的返回值,因此请把调用放在 try except 中并读取异常消息,随包附带的演示就是这样做的。
四个方向,都在同一个库内。
适用于 XML 的 XAdES 和 XMLDSig,适用于分离式 CMS 的 CAdES,ASiC 容器,以及适用于 ClickOnce、NuGet 和 VSIX 包的专用签名器。在 Windows 上还有 Authenticode 签名器。
附带十种密钥提供程序:PFX、PEM、Windows 存储区、PKCS#11 硬件、Azure Trusted Signing、AWS KMS、Google Cloud KMS、Certum SimplySign、HashiCorp Vault 和 CSC 远程签名协议。
演示项目包含在下载包内,位于 Demos\Delphi 下。本页就是基于 PAdES 演示构建的。
| 五分钟 XAdES 演练 详细版快速入门:全新的 VCL 项目、一个 PFX 文件和一个已签名的 XML 封装。 | 打开 | |
| 密钥提供程序 私钥可以存放的全部十个位置,以及各自的需求。 | 打开 | |
| 签名配置文件 二十一个国家和行业配置文件,以及各自的要求。 | 打开 | |
| PDF 签名教程 关于 PAdES 的更详细演练,包括可见签名。 | 打开 | |
| sgcSign Server 自托管的签名守护进程,适用于密钥不得外传的场景。 | 打开 | |
| 下载试用版 与正式版相同的安装程序,有时间限制,另有免费的 Community Edition。 | 打开 |
相关阅读:sgcSign 简介和代码签名服务器。每个产品都有自己的快速入门,列在入门页面上。
TsgcPAdESSigner(声明在 sgcSign_PAdES.pas 中)和一个密钥提供程序。对于 Windows 证书存储区,是来自 sgcSign_KeyProvider_WinCertStore.pas 的 TsgcWindowsCertStoreProvider。对于 .pfx 文件,是来自 sgcSign_KeyProvider_PFX.pas 的 TsgcPFXKeyProvider。两者都位于 SGC Sign 组件面板页上。把提供程序赋给签名器的 KeyProvider 属性,然后调用 SignPDFFile(aInputFile, aOutputFile)。
KeyProvider 的类型是接口 IsgcKeyProvider,而不是组件。内联的 as 类型转换会创建一个由编译器生成的接口临时变量,它会一直存活在栈帧中直到例程返回,而那是在提供程序组件被释放之后,此时再释放它就会访问已经不存在的内存。演示把类型转换赋给一个具名的局部变量,然后按以下顺序释放:签名器、把接口设为 nil、提供程序。请照搬这个顺序。
sgcVer.inc 中完全没有 SGC_EDT_* 定义,也没有任何组件或格式按层级限制。每种许可证都包含每个签名器、每个密钥提供程序和每个国家配置文件。商业层级按席位数量划分,即单个、团队和站点,试用版之外还有免费的 Community Edition。
Profile 是一个 TsgcSignProfileConfig 对象,因此您设置的是 Profile.Profile 和 Profile.SignatureLevel,而不是赋一个字符串。对于长期签名,请切换到 LTV 配置文件和基线 LT 级别,并附加一个 TSAClient。
TsgcSignatureVerifier。VerifyPDF 接受一个流并返回 TsgcVerificationStatus,您将其与 vsValid 比较。GetVerificationDetails 会解释失败的原因,当您需要证据而不是一个布尔值时,GetValidationReportXML 会生成 ETSI 验证报告格式的报告。