PAdES B-LTA 數位簽章¶
PAdES(PDF Advanced Electronic Signatures)是 ETSI EN 319 142-1 標準定義的 PDF 電子簽章格式,為歐盟 eIDAS 法規認可的合法電子簽章形式。NextPDF Pro 提供從 B-B 到 B-LTA 的完整四級支援。
四級簽章等級¶
graph LR
BB["B-B\n基礎簽章\n+ 簽署憑證"] --> BT["B-T\n+ RFC 3161\n時間戳記"]
BT --> BLT["B-LT\n+ 憑證鏈\n+ CRL/OCSP"]
BLT --> BLTA["B-LTA\n+ 追加時間戳記\n(可週期更新)"]
style BLTA fill:#D97706,color:#fff | 等級 | 關鍵新增內容 | 驗證有效期 |
|---|---|---|
| B-B | 基礎簽章值、簽署憑證引用 | 至憑證到期 |
| B-T | RFC 3161 可信時間戳記 | 至 TSA 憑證到期 |
| B-LT | 完整憑證鏈 + 撤銷狀態資料(CRL/OCSP) | 至最舊撤銷資料到期 |
| B-LTA | 追加時間戳記封存整個驗證資料 | 無限(依 LTA 輪換週期) |
快速開始:B-LTA 簽章¶
前置準備¶
use NextPDF\Pro\Signatures\PAdES\PadesSignatureAppender;
use NextPDF\Pro\Signatures\PAdES\PadesSignatureLevel;
use NextPDF\Pro\Signatures\PAdES\PadesSignatureOptions;
use NextPDF\Pro\Signatures\Timestamp\TimestampAuthorityClient;
use NextPDF\Pro\Signatures\Certificate\CertificateChain;
// 載入簽署憑證與私鑰(PEM 或 PKCS#12 格式)
$certificate = CertificateChain::fromPkcs12(
pkcs12Path: '/secure/signing.p12',
passphrase: (string) getenv('SIGNING_KEY_PASSPHRASE'),
);
// 設定 RFC 3161 時間戳記服務
$tsa = new TimestampAuthorityClient(
url: 'https://tsa.example.com/timestamp',
username: (string) getenv('TSA_USERNAME'),
password: (string) getenv('TSA_PASSWORD'),
);
執行 B-LTA 簽章¶
use NextPDF\Pro\Document\ProDocument;
use NextPDF\Pro\Signatures\PAdES\PadesSignatureAppender;
use NextPDF\Pro\Signatures\PAdES\PadesSignatureLevel;
use NextPDF\Pro\Signatures\PAdES\PadesSignatureOptions;
// 生成或載入 PDF 文件
$doc = ProDocument::createStandalone();
$doc->addPage();
$doc->text('Quarterly Financial Report — Q4 2025', x: 20, y: 30);
$pdfBytes = $doc->render();
// 建立簽章選項
$options = PadesSignatureOptions::create(
level: PadesSignatureLevel::BLta,
certificate: $certificate,
timestampAuthority: $tsa,
reason: 'Authorized signatory — CFO approval',
location: 'Taipei, Taiwan',
contactInfo: '[email protected]',
includeOcsp: true,
includeCrl: true,
);
// 附加 B-LTA 簽章
$appender = new PadesSignatureAppender($options);
$signedPdf = $appender->sign($pdfBytes);
// 儲存已簽署文件
file_put_contents('/output/signed-report.pdf', $signedPdf);
多重簽名(Sequential Signing)¶
多方連署場景下,後續簽署者使用 PadesSignatureAppender 在現有簽章基礎上附加新簽章,不會使前一個簽章失效:
use NextPDF\Pro\Signatures\PAdES\PadesSignatureAppender;
use NextPDF\Pro\Signatures\CAdES\CadesSignatureParser;
// 解析現有文件中的簽章資訊
$parser = new CadesSignatureParser();
$existingSignatures = $parser->parse($signedPdf);
foreach ($existingSignatures as $sig) {
echo sprintf(
"Signer: %s | Level: %s | Valid: %s\n",
$sig->getSubjectName(),
$sig->getLevel()->value,
$sig->isValid() ? 'Yes' : 'No',
);
}
// 附加第二個簽章(不修改第一個)
$secondSignerOptions = PadesSignatureOptions::create(
level: PadesSignatureLevel::BLta,
certificate: $secondCertificate,
timestampAuthority: $tsa,
reason: 'Legal counsel review',
signatureFieldName: 'Signature2', // 使用獨立欄位
);
$appender = new PadesSignatureAppender($secondSignerOptions);
$doubleSignedPdf = $appender->sign($signedPdf); // 傳入已有第一個簽章的 PDF
CadesSignatureParser 解析器¶
CadesSignatureParser 負責讀取 PDF 文件中嵌入的 CMS(Cryptographic Message Syntax)簽章結構:
use NextPDF\Pro\Signatures\CAdES\CadesSignatureParser;
use NextPDF\Pro\Signatures\CAdES\ParsedSignature;
$parser = new CadesSignatureParser();
/** @var list<ParsedSignature> $signatures */
$signatures = $parser->parse($pdfBytes);
foreach ($signatures as $signature) {
// 簽章基本資訊
$signature->getSubjectName(); // string: 簽署者 DN
$signature->getSigningTime(); // DateTimeImmutable
$signature->getLevel(); // PadesSignatureLevel enum
$signature->isValid(); // bool: 密碼學驗證結果
// 時間戳記資訊(B-T 以上才有)
$signature->getTimestamp()?->getTime(); // ?DateTimeImmutable
$signature->getTimestamp()?->getTsaName(); // ?string
// 長期驗證資料(B-LT 以上才有)
$signature->getLtvData()?->hasCrl(); // ?bool
$signature->getLtvData()?->hasOcsp(); // ?bool
$signature->getLtvData()?->getCertificateChain(); // ?list<X509Certificate>
}
LTA 時間戳記輪換¶
B-LTA 的長期效力依賴週期性的時間戳記更新。當最外層時間戳記的 TSA 憑證即將到期前,必須在現有時間戳記上再追加一個新的封存時間戳記:
use NextPDF\Pro\Signatures\PAdES\LtaTimestampRenewer;
$renewer = new LtaTimestampRenewer(
timestampAuthority: $tsa,
renewBeforeExpiryDays: 180, // 到期前 180 天觸發
);
// 檢查是否需要輪換
if ($renewer->needsRenewal($signedPdfBytes)) {
$renewedPdf = $renewer->renew($signedPdfBytes);
// 儲存更新後的文件,原有所有簽章不受影響
file_put_contents('/archive/renewed-report.pdf', $renewedPdf);
}
簽章外觀設計¶
use NextPDF\Pro\Signatures\PAdES\PadesSignatureAppearance;
use NextPDF\Pro\Signatures\PAdES\SignatureAppearanceRect;
$appearance = PadesSignatureAppearance::create(
rect: SignatureAppearanceRect::fromPoints(x: 400, y: 700, width: 180, height: 60),
backgroundImage: '/assets/company-seal.png',
textTemplate: "Signed by: {{subjectName}}\nDate: {{signingTime:Y-m-d}}\nReason: {{reason}}",
fontSize: 8.0,
fontColor: '#1E3A8A',
);
$options = PadesSignatureOptions::create(
level: PadesSignatureLevel::BLta,
certificate: $certificate,
timestampAuthority: $tsa,
appearance: $appearance,
);
驗證簽章¶
簽署完成後應立即驗證,確認簽章結構正確:
use NextPDF\Pro\Signatures\PAdES\PadesSignatureValidator;
use NextPDF\Pro\Signatures\PAdES\ValidationResult;
$validator = new PadesSignatureValidator(
trustedRoots: ['/certs/root-ca.pem', '/certs/intermediate-ca.pem'],
checkRevocation: true,
verifyTimestamps: true,
);
/** @var ValidationResult $result */
$result = $validator->validate($signedPdfBytes);
if (!$result->isValid()) {
foreach ($result->getErrors() as $error) {
throw new \RuntimeException(sprintf('Signature validation failed: %s', $error->getMessage()));
}
}
echo 'Signature valid. Level: ' . $result->getHighestLevel()->value;
錯誤處理¶
| 例外類別 | 觸發條件 | 建議處理 |
|---|---|---|
CertificateExpiredException | 簽署憑證已過期 | 替換憑證或改用 HSM |
TimestampAuthorityException | TSA 服務無回應 | 重試邏輯 + 備援 TSA |
OcspRevocationException | 憑證已被撤銷 | 拒絕簽章,通知管理員 |
PdfCorruptedException | 輸入 PDF 結構損毀 | 驗證輸入來源 |
SignatureConflictException | 嘗試修改已簽署區域 | 改用 append-only 模式 |
相關資源¶
- HSM 硬體安全模組整合 — 使用 AWS KMS 進行簽章
- CadesSignatureParser API 參考
- PadesSignatureAppender API 參考
- 為什麼選擇 Pro — 功能矩陣與標準對照