Angular/NodeJS应用对接智能卡/USB令牌实现PDF签名回调咨询
对接智能卡/USB令牌实现PDF数字签名方案
核心前提说明
如果智能卡/USB令牌是用户本地设备,后端(Node.js)无法直接访问用户本地硬件,必须通过前端(Angular)对接;仅当智能卡插在服务器上时,后端方案才适用。以下针对用户场景(本地智能卡)给出可行方案:
前端对接方案(Angular + Web Crypto API + PKCS#11兼容库)
浏览器本身不原生支持PKCS#11,但可以通过WebAssembly封装的PKCS#11库结合Web Crypto API实现本地智能卡访问,流程如下:
1. 依赖准备
安装前端PKCS#11库(以pkcs11js的WASM版本为例):
npm install pkcs11js
2. 前端签名流程代码示例
import * as pkcs11 from 'pkcs11js'; import { HttpClient } from '@angular/common/http'; export class PdfSignComponent { constructor(private http: HttpClient) {} async signPdfBeforeSave(pdfBlob: Blob) { try { // 加载PKCS#11模块(路径需根据用户系统调整:Windows用cryptoki.dll,Linux用libpkcs11.so,macOS用libpkcs11.dylib) const lib = pkcs11.Module.load('/path/to/pkcs11/library'); lib.initialize(); // 获取已插入智能卡的插槽 const slots = lib.getSlots(true); // true仅返回有卡的插槽 if (slots.length === 0) { alert('未检测到智能卡/USB令牌'); return; } const slot = slots[0]; const session = slot.open(pkcs11.SessionFlag.SERIAL_SESSION | pkcs11.SessionFlag.RW_SESSION); // 提示用户输入PIN(本地处理,绝不传给后端) const pin = prompt('请输入智能卡PIN码:'); if (!pin) { session.close(); lib.finalize(); return; } session.login('user', pin); // 查找智能卡中的签名私钥 const privateKeys = session.find({ class: pkcs11.ObjectClass.PRIVATE_KEY, keyType: pkcs11.KeyType.RSA, sign: true }); if (privateKeys.length === 0) { alert('智能卡中未找到可用签名密钥'); session.logout(); session.close(); lib.finalize(); return; } const privateKey = privateKeys[0]; // 计算PDF文件的SHA-256哈希(智能卡仅签哈希,避免大文件传输) const pdfArrayBuffer = await pdfBlob.arrayBuffer(); const pdfHash = await crypto.subtle.digest('SHA-256', pdfArrayBuffer); // 调用智能卡进行签名 const signature = session.sign( { mechanism: pkcs11.Mechanism.SHA256_RSA_PKCS }, privateKey, new Uint8Array(pdfHash) ); // 清理资源 session.logout(); session.close(); lib.finalize(); // 将原始PDF和签名传给后端,由后端嵌入签名并保存 const pdfBase64 = btoa(String.fromCharCode(...new Uint8Array(pdfArrayBuffer))); const signatureBase64 = btoa(String.fromCharCode(...signature)); this.http.post('/api/save-signed-pdf', { pdf: pdfBase64, signature: signatureBase64 }) .subscribe({ next: () => alert('PDF签名并保存成功'), error: (err) => console.error('保存失败:', err) }); } catch (err) { console.error('签名失败:', err); alert('签名过程出错,请检查智能卡驱动和PIN码'); } } }
3. 后端嵌入签名并保存(Node.js)
使用node-signpdf(遵循PAdES规范)和pdf-lib处理PDF签名嵌入:
const express = require('express'); const app = express(); app.use(express.json({ limit: '10mb' })); const { PDFDocument } = require('pdf-lib'); const { sign } = require('node-signpdf'); const fs = require('fs'); app.post('/api/save-signed-pdf', async (req, res) => { try { const { pdf: pdfBase64, signature: signatureBase64 } = req.body; const pdfBytes = Buffer.from(pdfBase64, 'base64'); const signatureBytes = Buffer.from(signatureBase64, 'base64'); // 加载原始PDF,添加签名占位符并嵌入签名(遵循PAdES规范) const signedPdf = await sign(pdfBytes, signatureBytes, { reason: '用户电子签名', contactInfo: 'contact@example.com', location: '线上签署' }); // 保存到服务器指定目录 const savePath = `./signed-documents/${Date.now()}.pdf`; fs.writeFileSync(savePath, signedPdf); res.status(200).send({ message: '签名PDF已保存', filePath: savePath }); } catch (err) { console.error('后端处理失败:', err); res.status(500).send({ error: '保存签名PDF失败' }); } }); app.listen(3000, () => console.log('后端服务启动'));
关键注意事项
- 驱动依赖:用户必须安装智能卡对应的PKCS#11驱动(如银行U盾驱动、通用PC/SC驱动),否则前端无法识别设备。
- 安全性:PIN码必须在前端本地输入和处理,绝对不能传输到后端;仅将PDF哈希传给智能卡签名,避免原始PDF泄露。
- PDF规范:签名必须遵循PKCS#7/PAdES标准,否则PDF阅读器可能不认可签名有效性,推荐使用
node-signpdf等专业库处理。 - 浏览器兼容:Web Crypto API和WASM版PKCS#11库在Chrome、Firefox、Edge中支持良好,Safari可能存在兼容性限制。
内容的提问来源于stack exchange,提问作者jorkata
相关产品推荐
相关产品推荐

