如何在JavaScript中读取USB Token中的证书及公私钥?
Hey there! Let's break down how to interact with USB tokens (smart cards) to retrieve certificates and work with their associated public/private keys in JavaScript. The approach varies a lot depending on whether you're building for the browser or Node.js environment, so I'll cover both scenarios below.
Browser Environment
Browsers enforce strict security restrictions that prevent direct access to local hardware, so we need to use PKCS#11 (the global standard for smart card cryptography) via a WebAssembly (WASM) binding. The most reliable library for this is pkcs11js—a WASM port of the PKCS#11 API.
Key Pre-Requisites & Notes:
- Users must have the official PKCS#11 driver for their USB Token installed locally (most token vendors provide these for free on their websites).
- Critical: Private keys cannot be exported from the token—they stay locked on the device for security. You can only invoke cryptographic operations (like signing) through the PKCS#11 API, not extract the raw key data.
Example Workflow:
Load the PKCS#11 WASM library
import * as pkcs11 from 'pkcs11js'; // Initialize with your token's driver path // (In browsers, you may need to prompt users to select the driver file manually) pkcs11.load('/path/to/token-driver.so'); // Linux/macOS // pkcs11.load('C:\\Windows\\System32\\token-driver.dll'); // WindowsConnect to the token and log in
// Get slots with active tokens inserted const slots = pkcs11.getSlots(true); const targetSlot = slots[0]; // Use the first detected token (adjust if multiple exist) const session = targetSlot.open(pkcs11.CKF_RW_SESSION | pkcs11.CKF_SERIAL_SESSION); // Prompt the user for their token PIN (never hardcode this!) const userPin = prompt('Enter your USB Token PIN:'); session.login(pkcs11.CKU_USER, userPin);Retrieve certificates and extract public keys
// Find all X.509 certificates on the token const certObjects = session.findObjects({ class: pkcs11.CKO_CERTIFICATE, certType: pkcs11.CKC_X_509, }); for (const cert of certObjects) { // Fetch key certificate attributes const certAttrs = session.getAttributeValue(cert, [ pkcs11.CKA_SUBJECT, pkcs11.CKA_VALUE, // Raw DER-encoded certificate data pkcs11.CKA_ID, // Unique ID to match the certificate's private key ]); // Parse the DER certificate to get a usable public key via Web Crypto API const certDer = new Uint8Array(certAttrs[pkcs11.CKA_VALUE]); const publicKey = await window.crypto.subtle.importKey( 'spki', certDer, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['verify'] ); console.log('Certificate Subject:', certAttrs[pkcs11.CKA_SUBJECT].toString()); console.log('Public Key (Web Crypto):', publicKey); }Sign data using the token's private key
// Find the private key linked to the certificate (match by ID) const privateKeyObjects = session.findObjects({ class: pkcs11.CKO_PRIVATE_KEY, keyType: pkcs11.CKK_RSA, id: certAttrs[pkcs11.CKA_ID], }); const privateKey = privateKeyObjects[0]; const dataToSign = new TextEncoder().encode('Data to sign with USB Token'); // Perform signing directly on the token const signature = session.sign( { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, privateKey, dataToSign ); console.log('Generated Signature:', new Uint8Array(signature));Clean up resources
session.logout(); session.close(); pkcs11.finalize();
Node.js Environment
In Node.js, you have direct access to local system resources, so you can use native PKCS#11 bindings like pkcs11js (same library as above, but optimized for Node.js) or node-pkcs11.
Example Workflow:
Install the library
npm install pkcs11jsInitialize the PKCS#11 driver
const pkcs11 = require('pkcs11js'); // Load your token's PKCS#11 driver pkcs11.load('/path/to/token-driver.so'); // Linux/macOS // pkcs11.load('C:\\Windows\\System32\\token-driver.dll'); // WindowsConnect and log in
const slots = pkcs11.getSlots(true); const targetSlot = slots[0]; const session = targetSlot.open(pkcs11.CKF_RW_SESSION | pkcs11.CKF_SERIAL_SESSION); // Get PIN from environment variable or user input (never hardcode!) const userPin = process.env.TOKEN_PIN || prompt('Enter USB Token PIN:'); session.login(pkcs11.CKU_USER, userPin);Retrieve certificates and public keys
const certObjects = session.findObjects({ class: pkcs11.CKO_CERTIFICATE, certType: pkcs11.CKC_X_509, }); const crypto = require('crypto'); for (const cert of certObjects) { const certAttrs = session.getAttributeValue(cert, [ pkcs11.CKA_SUBJECT, pkcs11.CKA_VALUE, pkcs11.CKA_ID, ]); // Parse public key using Node.js crypto module const publicKey = crypto.createPublicKey({ key: certAttrs[pkcs11.CKA_VALUE], format: 'der', type: 'spki', }); console.log('Certificate Subject:', certAttrs[pkcs11.CKA_SUBJECT].toString()); console.log('Public Key (PEM):', publicKey.export({ type: 'spki', format: 'pem' })); }Sign data with the private key
const privateKeyObjects = session.findObjects({ class: pkcs11.CKO_PRIVATE_KEY, keyType: pkcs11.CKK_RSA, id: certAttrs[pkcs11.CKA_ID], }); const privateKey = privateKeyObjects[0]; const dataToSign = Buffer.from('Data to sign with USB Token'); const signature = session.sign( { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA256' }, privateKey, dataToSign ); console.log('Signature (Hex):', Buffer.from(signature).toString('hex'));Clean up
session.logout(); session.close(); pkcs11.finalize();
Critical Reminders
- Private Key Security: USB tokens are designed to never let you export raw private key data—this is a core security feature. All private key operations happen on the device itself.
- Driver Compatibility: Always use the exact PKCS#11 driver provided by your token vendor—generic drivers won’t work.
- PIN Handling: Never store or hardcode user PINs. Use secure input methods (native prompts, environment variables) to avoid exposing sensitive data.
内容的提问来源于stack exchange,提问作者Mayur Saini




