如何构建API,使用Hypersecure USB令牌上的eMudhra DSC签署PDF?
基于Python/Node.js/.NET的PDF数字签名API实现指南
通用前置准备
- 安装eMudhra Hypersecure USB令牌官方驱动:Windows需安装对应CSP驱动,Linux/macOS需安装PKCS#11适配库(如
libemudhra-pkcs11.so) - 验证令牌访问:
- Windows:打开
certmgr.msc,确认“个人”证书库中存在eMudhra DSC - Linux/macOS:执行
openssl pkcs11 -engine pkcs11 -keyform engine -listkeys验证能读取令牌内私钥
- Windows:打开
- 确保API服务有权限访问USB设备(Linux需添加udev规则,Windows需以管理员权限运行服务)
Python实现方案
依赖安装
pip install pypdf2 cryptography python-pkcs11 fastapi uvicorn
核心代码示例
import os from pkcs11 import Session, ObjectClass, KeyType from PyPDF2 import PdfReader, PdfWriter from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from fastapi import FastAPI, File, UploadFile from fastapi.responses import Response app = FastAPI() # 从环境变量读取PIN,禁止硬编码 TOKEN_PIN = os.getenv("DSC_PIN") # 替换为你的令牌PKCS#11库路径(Windows用对应DLL路径,Linux/macOS用.so路径) PKCS11_LIB_PATH = "/usr/lib/libemudhra-pkcs11.so" def sign_pdf(input_pdf_bytes: bytes) -> bytes: with Session(PKCS11_LIB_PATH) as session: session.login(TOKEN_PIN) # 获取DSC证书和RSA私钥 cert = session.get_objects({ObjectClass.CERTIFICATE})[0] private_key = session.get_objects({ ObjectClass.PRIVATE_KEY, KeyType.RSA })[0] # 读取PDF并准备签名 reader = PdfReader(input_pdf_bytes) writer = PdfWriter() for page in reader.pages: writer.add_page(page) # 生成文档哈希并签名 pdf_hash = writer.get_document_hash() signature = private_key.sign( pdf_hash, mechanism=hashes.SHA256(), padding=padding.PKCS1v15() ) # 嵌入签名到PDF writer.add_signature( certificate=cert.to_cryptography(), signature=signature, hash_algorithm=hashes.SHA256() ) # 输出签名后的PDF字节流 output_bytes = bytearray() writer.write(output_bytes) session.logout() return output_bytes # API接口:接收PDF文件,返回签名后的PDF @app.post("/sign-pdf") async def sign_pdf_endpoint(file: UploadFile = File(...)): input_bytes = await file.read() signed_bytes = sign_pdf(input_bytes) return Response( content=signed_bytes, media_type="application/pdf", headers={"Content-Disposition": 'attachment; filename="signed.pdf"'} ) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
Node.js实现方案
依赖安装
npm install pdf-lib pkcs11js express multer crypto
核心代码示例
const express = require('express'); const multer = require('multer'); const { PDFDocument, StandardFonts, rgb } = require('pdf-lib'); const pkcs11 = require('pkcs11js'); const crypto = require('crypto'); const app = express(); const upload = multer(); // 从环境变量读取PIN const TOKEN_PIN = process.env.DSC_PIN; // 替换为你的令牌PKCS#11库路径 const PKCS11_LIB_PATH = '/usr/lib/libemudhra-pkcs11.so'; // 初始化PKCS#11模块 const module = new pkcs11.Module(PKCS11_LIB_PATH); module.initialize(); async function signPdf(inputBuffer) { // 打开令牌会话并登录 const slots = module.getSlots(true); const session = slots[0].open(pkcs11.CKF_RW_SESSION | pkcs11.CKF_SERIAL_SESSION); session.login(TOKEN_PIN); // 获取私钥和证书 const privateKey = session.findObjects([ { type: pkcs11.CKO_PRIVATE_KEY, class: pkcs11.CKO_PRIVATE_KEY } ])[0]; const certBuffer = session.findObjects([ { type: pkcs11.CKO_CERTIFICATE, class: pkcs11.CKO_CERTIFICATE } ])[0].getAttribute(pkcs11.CKA_VALUE); // 加载并处理PDF const pdfDoc = await PDFDocument.load(inputBuffer); const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); // 添加可见签名域 const signatureField = pdfDoc.addSignatureField('DigitalSignature'); signatureField.setRectangle({ x: 50, y: 50, width: 220, height: 60 }); signatureField.setContent(`Signed with eMudhra DSC\n${new Date().toLocaleString()}`); signatureField.setFont(helveticaFont); signatureField.setFontSize(10); signatureField.setTextColor(rgb(0, 0, 0)); // 生成待签名哈希并签名 const pdfBytesForSigning = await pdfDoc.saveToBytes(); const hash = crypto.createHash('sha256').update(pdfBytesForSigning).digest(); const signature = session.sign( { mechanism: pkcs11.CKM_SHA256_RSA_PKCS }, privateKey, hash ); // 嵌入签名到PDF await pdfDoc.addSignature(signature, certBuffer, { signatureLength: 256, hashAlgorithm: 'SHA-256' }); const signedPdfBytes = await pdfDoc.save(); session.logout(); module.finalize(); return signedPdfBytes; } // API接口 app.post('/sign-pdf', upload.single('pdf'), async (req, res) => { try { const signedPdf = await signPdf(req.file.buffer); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename="signed.pdf"'); res.send(signedPdf); } catch (err) { res.status(500).json({ error: err.message }); } }); app.listen(3000, () => console.log('PDF签名API运行在端口3000'));
.NET实现方案
NuGet包安装
Install-Package Pkcs11Interop Install-Package iTextSharp Install-Package Microsoft.AspNetCore.Mvc
核心代码示例
using System; using System.IO; using System.Security.Cryptography.X509Certificates; using iTextSharp.text; using iTextSharp.text.pdf; using Net.Pkcs11Interop.Common; using Net.Pkcs11Interop.HighLevelAPI; using Microsoft.AspNetCore.Mvc; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); app.MapPost("/sign-pdf", async (IFormFile file) => { var tokenPin = Environment.GetEnvironmentVariable("DSC_PIN"); // 替换为Windows平台的令牌PKCS#11 DLL路径 var pkcs11LibPath = @"C:\Windows\System32\emudhra_pkcs11.dll"; using var pkcs11 = new Pkcs11(pkcs11LibPath, AppType.MultiThreaded); var slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent); using var slot = slots[0]; using var session = slot.OpenSession(SessionType.ReadWrite); session.Login(CKU.CKU_USER, tokenPin); // 获取私钥和证书 var privateKey = session.FindAllObjects(new() { new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY), new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_RSA) })[0]; var certBytes = session.FindAllObjects(new() { new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE), new ObjectAttribute(CKA.CKA_CERTIFICATE_TYPE, CKC.CKC_X_509) })[0].GetValueAsByteArray(); var x509Cert = new X509Certificate2(certBytes); // 处理PDF签名 using var inputStream = file.OpenReadStream(); using var outputStream = new MemoryStream(); var reader = new PdfReader(inputStream); var stamper = PdfStamper.CreateSignature(reader, outputStream, '\0'); var appearance = stamper.SignatureAppearance; // 设置签名域位置和外观 appearance.SetVisibleSignature(new Rectangle(50, 50, 250, 100), 1, "DigitalSignature"); appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION; appearance.SignDate = DateTime.Now; appearance.Reason = "文档合规验证"; appearance.Location = "API签名服务"; // 生成待签名哈希并签名 var hashAlgorithm = "SHA-256"; var externalDigest = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); stamper.SignatureExternalContainer = externalDigest; stamper.Close(); outputStream.Position = 0; var pdfBytes = outputStream.ToArray(); var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(pdfBytes); var signedHash = session.Sign( new Mechanism(CKM.CKM_SHA256_RSA_PKCS), privateKey, hash ); // 嵌入签名到PDF var signedPdf = PdfSignatureAppearance.IntegrateSignature(appearance, signedHash, x509Cert); return File(signedPdf, "application/pdf", "signed.pdf"); }); app.Run("http://0.0.0.0:5000");
关键注意事项
- PIN安全:绝对禁止硬编码令牌PIN,优先使用环境变量、密钥管理服务或加密的HTTP头部传输
- 跨平台兼容:Windows默认使用CSP驱动,Linux/macOS需指定PKCS#11库路径
- 签名外观:可根据业务需求调整签名域的位置、文本内容和样式
- 错误处理:需添加令牌连接失败、证书未找到、PIN错误等异常捕获逻辑
内容的提问来源于stack exchange,提问作者Wanda Maximoff
相关产品推荐
相关产品推荐

