安全最佳实践
本指南提供在正式环境中部署 TCPDF-Next 的可行安全建议。遵循这些实践可确保您的数字签名、加密与文件生成符合企业级安全标准。
证书管理
获取证书
在正式环境中使用数字签名时,请使用受信任证书授权机构(CA)所签发的证书:
| 证书类型 | 使用场景 | 典型有效期 | 费用 |
|---|---|---|---|
| 文档签署(个人) | 个人签名 | 1-3 年 | $50-200/年 |
| 文档签署(组织) | 企业全域签名 | 1-3 年 | $200-500/年 |
| 合格证书(eIDAS) | 欧盟法律效力 | 1-3 年 | $100-400/年 |
| Adobe AATL 证书 | 在 Adobe Acrobat 中默认信任 | 1-3 年 | $300-1000/年 |
TIP
若您的签名需要在 Adobe Acrobat 中无需手动配置信任即可被认可,请使用 Adobe 核准信任列表(AATL) 上的 CA 所签发的证书。
证书轮替
php
use YeeeFang\TcpdfNext\Certificate\CertificateStore;
$store = new CertificateStore();
// 从安全目录加载证书
$store->loadFromDirectory('/etc/tcpdf-next/certs/', '*.pem');
// 自动选取有效且未过期的证书
$activeCert = $store->getActiveCertificate('document-signing');
if ($activeCert->getExpirationDate() < new \DateTimeImmutable('+30 days')) {
// 触发证书续期警报
$logger->warning('签署证书将在 30 天内到期', [
'subject' => $activeCert->getSubject(),
'expires' => $activeCert->getExpirationDate()->format('Y-m-d'),
]);
}建议事项:
- 至少在到期前 30 天进行证书续期
- 通过自动化警报监控证书到期时间
- 立即吊销已遭泄露的证书
- 维护所有证书操作的记录
私钥存储
存储方式层级(从最安全到最不安全)
| 方式 | 安全等级 | 使用场景 |
|---|---|---|
| 硬件安全模块(HSM) | 最高 | 企业级、受监管行业 |
| 云端 KMS(AWS KMS、Azure Key Vault、GCP KMS) | 高 | 云原生部署 |
| 具强密码短语的 PKCS#12 文件 | 中 | 小型部署 |
| PEM 文件(加密) | 中低 | 开发、测试 |
| PEM 文件(未加密) | 最低 | 正式环境中绝不使用 |
使用 HSM(建议方式)
php
use YeeeFang\TcpdfNext\Signature\Pkcs11Signer;
// 通过 PKCS#11 使用硬件安全模块
$signer = new Pkcs11Signer(
modulePath: '/usr/lib/softhsm/libsofthsm2.so',
slotId: 0,
pin: getenv('HSM_PIN'), // PIN 码从环境变量取得
keyLabel: 'signing-key-2026'
);
$pdfSigner = new PdfSigner($pdf);
$pdfSigner->setExternalSigner($signer)
->setLevel(SignatureLevel::PAdES_B_LTA)
->sign();使用云端 KMS
php
use YeeeFang\TcpdfNext\Signature\CloudKmsSigner;
// AWS KMS 示例
$signer = CloudKmsSigner::awsKms(
keyId: 'arn:aws:kms:eu-west-1:123456789:key/abcd-1234',
region: 'eu-west-1',
algorithm: 'RSASSA_PSS_SHA_256'
);
// Azure Key Vault 示例
$signer = CloudKmsSigner::azureKeyVault(
vaultUrl: 'https://my-vault.vault.azure.net',
keyName: 'signing-key',
keyVersion: 'abc123',
algorithm: 'RS256'
);
// Google Cloud KMS 示例
$signer = CloudKmsSigner::gcpKms(
keyName: 'projects/my-project/locations/europe-west1/keyRings/signing/cryptoKeys/doc-signing/cryptoKeyVersions/1',
algorithm: 'RSA_SIGN_PSS_2048_SHA256'
);PKCS#12 文件搭配密码短语
php
// 从文件加载 PKCS#12,密码短语从环境变量取得
$p12Content = file_get_contents('/etc/tcpdf-next/keys/signing.p12');
$passphrase = getenv('SIGNING_KEY_PASSPHRASE');
$certs = [];
openssl_pkcs12_read($p12Content, $certs, $passphrase);
$signer = new PdfSigner($pdf);
$signer->setCertificate($certs['cert'], $certs['pkey'])
->setLevel(SignatureLevel::PAdES_B_LTA)
->sign();
// 从内存清除机密数据
sodium_memzero($passphrase);
sodium_memzero($certs['pkey']);DANGER
切勿将私钥存储于:
- 源代码仓库中
- 在进程清单中可见的环境变量
- 共享文件系统上的未加密文件
- 未加密的数据库字段
- 日志文件或错误消息中
时间戳服务器选择
选择 TSA 的标准
| 标准 | 需求 |
|---|---|
| RFC 3161 合规 | 必要 |
| TLS(HTTPS) | 必要 |
| 响应时间 | < 2 秒 |
| 可用性 | 99.9%+ SLA |
| 哈希算法 | SHA-256、SHA-384、SHA-512 |
| 证书有效期 | 10 年以上 |
| 审计跟踪 | 提供 |
| 地理位置 | 考量数据驻留要求 |
建议的 TSA 供应商
| 供应商 | 网址 | 备注 |
|---|---|---|
| DigiCert | https://timestamp.digicert.com | 广受信任,AATL 成员 |
| Sectigo | https://timestamp.sectigo.com | 全球可用性佳 |
| GlobalSign | https://timestamp.globalsign.com | 欧洲供应商 |
| FreeTSA | https://freetsa.org/tsr | 免费,适合测试用途 |
WARNING
免费 TSA 服务可能无法提供正式环境所需的可靠性、SLA 或审计跟踪。对于具法律效力的签名,请使用商用 TSA 供应商。
TSA 容错备援
配置多个 TSA 服务器以实现高可用性:
php
use YeeeFang\TcpdfNext\Timestamp\TsaPool;
$tsaPool = TsaPool::create()
->addServer('https://timestamp.digicert.com', priority: 1)
->addServer('https://timestamp.sectigo.com', priority: 2)
->addServer('https://timestamp.globalsign.com', priority: 3)
->setFailoverStrategy('priority'); // 或 'round-robin'
$signer->setTimestampClient($tsaPool);签名验证
验证外部来源的 PDF
接受来自外部的已签署 PDF 时,务必验证签名:
php
use YeeeFang\TcpdfNext\Signature\SignatureValidator;
use YeeeFang\TcpdfNext\Certificate\CertificateStore;
$trustStore = new CertificateStore();
$trustStore->loadFromDirectory('/etc/tcpdf-next/trusted-roots/');
$validator = new SignatureValidator($trustStore);
$result = $validator->validate('/path/to/received.pdf');
if (!$result->isValid()) {
foreach ($result->getErrors() as $error) {
$logger->error('签名验证失败', [
'error' => $error->getMessage(),
'code' => $error->getCode(),
]);
}
throw new \RuntimeException('无效的签名');
}验证检查清单
确保您的验证流程检查以下项目:
- [ ] 签名完整性(CMS 签名验证通过)
- [ ] 证书链(签署者到受信任根证书)
- [ ] 证书有效性(未过期、未吊销)
- [ ] 密钥用途(已设置 digitalSignature 位)
- [ ] 字节范围覆盖(整份文件已签署)
- [ ] 无未经授权的签署后修改
- [ ] 时间戳有效性(如有)
- [ ] 算法合规性(无 SHA-1、无弱密钥)
安全部署
环境配置
bash
# .env(Laravel 示例)
# 签署证书(PKCS#12)
TCPDF_SIGNING_CERT_PATH=/etc/tcpdf-next/certs/signing.p12
TCPDF_SIGNING_CERT_PASSPHRASE= # 通过密钥管理器配置,勿写在 .env
# 时间戳服务器
TCPDF_TSA_URL=https://timestamp.digicert.com
TCPDF_TSA_HASH_ALGORITHM=SHA-256
# 安全策略
TCPDF_MIN_RSA_KEY_SIZE=2048
TCPDF_REJECT_SHA1=true
TCPDF_STRICT_PARSING=true
# 资源限制
TCPDF_MAX_MEMORY=256M
TCPDF_MAX_EXECUTION_TIME=120
TCPDF_MAX_PDF_SIZE=104857600文件系统权限
bash
# 证书目录:仅允许 Web 服务器用户读取
chown -R www-data:www-data /etc/tcpdf-next/certs/
chmod 700 /etc/tcpdf-next/certs/
chmod 600 /etc/tcpdf-next/certs/*.p12
chmod 600 /etc/tcpdf-next/certs/*.pem
# 输出目录:仅允许 Web 服务器用户写入
chown -R www-data:www-data /var/lib/tcpdf-next/output/
chmod 700 /var/lib/tcpdf-next/output/
# 临时目录:可写入,非全局可读
chown -R www-data:www-data /tmp/tcpdf-next/
chmod 700 /tmp/tcpdf-next/内容安全
从用户提供的内容(HTML、图片)生成 PDF 时:
php
use YeeeFang\TcpdfNext\Security\ResourcePolicy;
use YeeeFang\TcpdfNext\Security\NetworkPolicy;
// 严格限制资源加载
$resourcePolicy = ResourcePolicy::strict()
->allowLocalDirectory('/app/public/assets/')
->denyAllRemote(); // 禁止所有外部资源获取
// 若确实需要远程资源
$networkPolicy = NetworkPolicy::create()
->denyPrivateNetworks()
->denyLoopback()
->denyLinkLocal()
->allowDomain('cdn.yourcompany.com')
->setMaxRedirects(3)
->setRequestTimeout(10);
$pdf = PdfDocument::create()
->setResourcePolicy($resourcePolicy)
->setNetworkPolicy($networkPolicy)
->build();速率限制
对于公开的 PDF 生成 API,实施速率限制:
php
// Laravel 中间件示例
Route::post('/api/generate-pdf', [PdfController::class, 'generate'])
->middleware('throttle:pdf-generation');
// 在 RouteServiceProvider 中
RateLimiter::for('pdf-generation', function (Request $request) {
return Limit::perMinute(10)->by($request->user()->id);
});合规检查清单
财务文件
- [ ] 使用 PAdES B-LTA 签名确保长期有效性
- [ ] 使用 AATL 列表上的证书取得 Adobe 信任
- [ ] 使用具 SLA 的商用 TSA
- [ ] 嵌入所有验证数据(OCSP + CRL)
- [ ] 保留签署审计日志直至监管要求期限
- [ ] 使用 PDF/A-4 满足归档需求
医疗保健(HIPAA)
- [ ] 以 AES-256 加密含 PHI 的 PDF
- [ ] 使用证书式(公钥)加密实现多收件者访问
- [ ] 记录所有签署密钥的访问行为
- [ ] 将签署密钥存储于 HSM 或云端 KMS
- [ ] 为所有已签署文件实施审计跟踪
欧盟 eIDAS 合规
- [ ] 使用 eIDAS 合格 TSP 签发的合格证书
- [ ] 使用 eIDAS 合格 TSA 的合格时间戳
- [ ] 实现 PAdES B-LTA 以取得合格电子签名
- [ ] 确保 PDF/A-4 合规以维持长期有效性
延伸阅读
- 安全性总览 — 安全架构与设计哲学
- 威胁模型 — 详细威胁分析
- PAdES B-LTA — 签名等级与实现
- ETSI 密码学要求 — 算法要求