You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js中通过智能卡实现PAdES格式PDF签名

问题描述

我正尝试在Node.js中编写一个函数,通过智能卡对PDF文档进行PAdES格式的数字签名。我使用pkcs11js库获取了证书和私钥,并编写了如下签名生成代码:

pkcs11.C_FindObjectsInit(session, [
     { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_CERTIFICATE }
 ]);
 var hObject = pkcs11.C_FindObjects(session);

 var i = 0;
 while (hObject) {
     var attrs = pkcs11.C_GetAttributeValue(session, hObject, [
         { type: pkcs11js.CKA_VALUE }
     ]);
     certificates[i] = attrs[0].value;
     //console.log(attrs[0].value.toString()); prints the certificate data
     hObject = pkcs11.C_FindObjects(session);
     i++;
 }
 pkcs11.C_FindObjectsFinal(session);
 var keys = { privateKey: null, publicKey: null };
 pkcs11.C_FindObjectsInit(session, [
     { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PRIVATE_KEY },
 ]);
 var hObject = pkcs11.C_FindObjects(session);
 while (hObject) {
     var attrs = pkcs11.C_GetAttributeValue(session, hObject, [
         { type: pkcs11js.CKA_CLASS },
         { type: pkcs11js.CKA_TOKEN },
         { type: pkcs11js.CKA_LABEL }
     ]);
     keys.privateKey = hObject;
     hObject = pkcs11.C_FindObjects(session);
 }
 pkcs11.C_FindObjectsFinal(session);

 pkcs11.C_SignInit(session, { mechanism: pkcs11js.CKM_SHA256_RSA_PKCS }, keys.privateKey);
 var signature = pkcs11.C_Sign(session, pdf, Buffer.alloc(2048)); 

请问现在如何将PDF签名为PAdES格式?我考虑使用node-signpdf库,但它要求PKCS#12格式,不知能否将BER编码的证书转换为PKCS#12格式?这是否是正确的做法?您有什么推荐方案?

解决方案

一、BER证书转PKCS#12的可行性与问题

BER编码的X.509证书可以转换为PKCS#12格式,但完全不推荐这种做法:PKCS#12容器需要绑定私钥与证书,而智能卡的私钥是无法导出的(这是智能卡的核心安全设计),强行生成的PKCS#12会缺失有效的私钥签名能力,根本无法完成真实的PDF签名操作。

二、适配node-signpdf的替代方案

node-signpdf默认依赖PKCS#12,但可以通过自定义签名流程绕开这个限制,直接对接智能卡的签名能力,步骤如下:

  1. 生成PDF签名占位符:用SignPdf类创建带有签名占位符的PDF,指定签名算法(如SHA256-RSA)和从智能卡获取的BER证书。
  2. 提取待签名摘要:从占位符PDF中获取需要签名的原文数据,计算其哈希值。
  3. 调用智能卡签名:用已有的pkcs11js代码对该哈希值完成签名,确保签名机制与PDF要求的哈希算法匹配。
  4. 填充签名到PDF:将智能卡返回的签名值替换占位符,生成最终的PAdES签名PDF。

核心代码片段:

const { SignPdf } = require('node-signpdf');
const fs = require('fs');
const crypto = require('crypto');

// 读取原始PDF
const pdfBuffer = fs.readFileSync('./original.pdf');
// 从智能卡获取的BER证书Buffer
const certBuffer = certificates[0];

// 1. 创建带签名占位符的PDF
const signPdf = new SignPdf();
const pdfWithPlaceholder = signPdf.sign(
    pdfBuffer,
    certBuffer,
    {
        reason: 'Document verification',
        signatureLength: 2048, // 匹配RSA密钥长度
        hash: 'sha256',
    },
    () => Buffer.alloc(0) // 临时空签名函数,后续替换
);

// 2. 计算待签名的哈希值
const toSign = signPdf.getSignatureContent(pdfWithPlaceholder);
const hash = crypto.createHash('sha256').update(toSign).digest();

// 3. 调用智能卡签名
pkcs11.C_SignInit(session, { mechanism: pkcs11js.CKM_SHA256_RSA_PKCS }, keys.privateKey);
const signature = pkcs11.C_Sign(session, hash, Buffer.alloc(2048));

// 4. 填充签名生成最终PDF
const signedPdf = signPdf.replaceSignature(pdfWithPlaceholder, signature);
fs.writeFileSync('./signed.pdf', signedPdf);

三、更灵活的PAdES实现方案

如果node-signpdf的适配成本较高,推荐用node-forge结合pkcs11js实现:

  • 用node-forge构建符合PAdES标准的PKCS#7签名容器。
  • 调用pkcs11js完成哈希的签名操作,将签名嵌入PKCS#7容器。
  • 将PKCS#7容器写入PDF的签名域。

这种方案能更灵活地控制签名流程,完全适配智能卡私钥不可导出的场景。

四、关键注意事项

  • 确保智能卡的签名机制与PDF签名要求的哈希算法一致(PAdES推荐SHA256及以上)。
  • 处理PDF字节流时要严格遵循编码规范,避免格式错误。
  • 智能卡会话要正确管理,签名完成后及时释放资源。

内容的提问来源于stack exchange,提问作者Carmine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 22:30:32