463 字
2 分鐘
[範例] 使用 node-forge 解析 PKCS7 | 自然人憑證開發筆記

PKCS7 簽章值中有包含憑證(含公鑰、身分證末四碼、姓名)、TBS、卡號、簽章時間、Hash 演算法、Nonce 等資訊。本篇說明如何取出這些資訊。


Demo
程式碼
讀取 PKCS7 簽章值的資訊,透過 OID 找到對應欄位,可以先到 ASN.1 JavaScript decoder 解碼了解欄位的 OID,再到程式中抓取。
<script src="https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js"></script>
<h1>自然人憑證 node-forge 解析 PKCS7</h1>PKCS7<br><textarea id="pkcs7" rows="8" cols="65"></textarea><br><button onclick="submit()">解析</button></form><hr /><pre id="result"></pre>function submit() { let result = "";
// 從 certb64 提取公鑰 const begin = "-----BEGIN PKCS7-----"; const end = "-----END PKCS7-----"; const certPem = begin + document.getElementById("pkcs7").value + end;
const pkcs7 = forge.pkcs7.messageFromPem(certPem); const data = toUtf8(pkcs7);
result += "簽章資訊\n===\n"; result += `tbs: ${data.rawCapture.content.value[0].value}\n`; result += `hashAlgorithm: ${data.rawCapture.digestAlgorithm}\n`; const attrs = data.rawCapture.authenticatedAttributes; const nonce = data.rawCapture.authenticatedAttributes.find( (i) => i.value[0].value == "1.2.840.113549.1.9.25.3" ); result += `nonce: ${nonce ? nonce.value[1].value[0].value : "null"}\n`; const cardNumber = data.rawCapture.authenticatedAttributes.find( (i) => i.value[0].value == "2.16.886.1.100.2.204" ); result += `cardNumber: ${ cardNumber ? cardNumber.value[1].value[0].value : "null" }\n`; const signingTime = data.rawCapture.authenticatedAttributes.find( (i) => i.value[0].value == "1.2.840.113549.1.9.5" ); result += `signingTime: ${ signingTime ? signingTime.value[1].value[0].value : "null" }\n`;
result += "\n\n憑證資訊\n===\n"; const issuer = data.certificates[0].issuer.attributes; result += `issuerDN: C=${issuer[0].value}, O=${issuer[1].value}, OU=${issuer[2].value}\n`; const subject = data.certificates[0].subject.attributes; result += `subjectDN: C=${subject[0].value}, O=${subject[1].value}, serialNumber=${subject[2].value}\n`; result += `serialNumber: ${data.certificates[0].serialNumber}\n`; result += `notAfter: ${data.certificates[0].validity.notAfter}\n`; result += `notBefore: ${data.certificates[0].validity.notBefore}\n`;
result += JSON.stringify(data, null, 2); document.getElementById("result").innerHTML = result;}
// 將object中所有value轉為utf8function toUtf8(obj) { if (typeof obj === "object" && obj !== null) { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj[key] = toUtf8(obj[key]); } } return obj; } else if (typeof obj === "string") { if (/[\u0000-\u001F]/.test(obj)) { // 解決OID亂碼問題 return toOid(obj); } else { // 解決中文亂碼問題 const textDecoder = new TextDecoder("utf-8"); const byteArray = new Uint8Array([...obj].map((c) => c.charCodeAt(0))); return textDecoder.decode(byteArray); } } else { return obj; }}
// 將HEX轉為OIDfunction toOid(str) { const bytes = Array.from(str, c => c.charCodeAt(0)); const oid = [Math.floor(bytes[0] / 40), bytes[0] % 40]; let t = 0; for (let i = 1; i < bytes.length; i++) { if (bytes[i] >= 128) { t = (t << 7) + (bytes[i] - 128); } else { oid.push((t << 7) + bytes[i]); t = 0; } } return oid.join(".");}