kit.program/kit/src/crypto.js
cheney b73d9b0aac
Some checks are pending
TDevOPsCICD / build-image (push) Waiting to run
测试通过
2026-04-04 18:24:03 +08:00

464 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const sm4 = require('sm-crypto').sm4
const sm3 = require('sm-crypto').sm3
const sm2 = require('sm-crypto').sm2
const crypto = require('crypto')
const forge = require('node-forge')
const rs = require('jsrsasign')
module.exports.sm4en3 = function (key, data){
let sm3key = sm3(key)
sm3key = sm3key.substring(0, 32)
let encryptData = sm4.encrypt(data, sm3key)
return encryptData
}
module.exports.sm4de3 = function (key, data){
let sm3key = sm3(key)
sm3key = sm3key.substring(0, 32)
let decryptData = sm4.decrypt(data, sm3key)
return decryptData
}
/**
* 从 PKCS7 签名中获取信息(包含证书、公钥和签名值)
* 支持 RSA 和 SM2 算法
* @param {Buffer|string} p7sign - PKCS7 签名数据
* @returns {Object} 包含证书、公钥和签名值的对象
*/
module.exports.getInfoFromPKCS7 = function (p7sign) {
// 处理输入数据
let base64Data
if (Buffer.isBuffer(p7sign)) {
base64Data = p7sign.toString('base64')
} else if (typeof p7sign === 'string') {
// 判断是否是 PEM 格式
if (p7sign.includes('-----BEGIN PKCS7-----')) {
// 提取 base64 部分
base64Data = p7sign
.replace(/-----BEGIN PKCS7-----\n?/, '')
.replace(/-----END PKCS7-----\n?/, '')
.replace(/\n/g, '')
} else {
// 如果不是 PEM 格式,假设是 base64
base64Data = p7sign
}
} else {
throw new Error('Invalid input type for p7sign')
}
// 尝试使用 node-forge 解析(支持 RSA
try {
const pemData = `-----BEGIN PKCS7-----\n${base64Data}\n-----END PKCS7-----`
const p7 = forge.pkcs7.messageFromPem(pemData)
return extractInfoFromForgeP7(p7)
} catch (forgeError) {
// 如果 node-forge 解析失败,尝试使用 jsrsasign支持 SM2
try {
return extractInfoFromJsrsasignP7(base64Data)
} catch (jsrsasignError) {
throw new Error(`Failed to parse PKCS7: ${forgeError.message}, ${jsrsasignError.message}`)
}
}
}
/**
* 从 node-forge 解析的 PKCS7 中提取信息RSA
*/
function extractInfoFromForgeP7(p7) {
// 辅助函数:将属性数组转换为 DN 字符串
function attrsToDN(attrs) {
if (!attrs || attrs.length === 0) return ''
return attrs.map(attr => `${attr.name}=${attr.value}`).join(', ')
}
// 提取证书
let cert = null
if (p7.certificates && p7.certificates.length > 0) {
const certObj = p7.certificates[0]
cert = {
subject: certObj.subject.attributes.map(attr => ({
name: attr.name,
value: attr.value
})),
subjectDN: attrsToDN(certObj.subject.attributes),
issuer: certObj.issuer.attributes.map(attr => ({
name: attr.name,
value: attr.value
})),
issuerDN: attrsToDN(certObj.issuer.attributes),
serialNumber: certObj.serialNumber,
notBefore: certObj.validity.notBefore,
notAfter: certObj.validity.notAfter,
pem: forge.pki.certificateToPem(certObj)
}
}
// 提取公钥 - 从证书中提取公钥对象并转换为 PEM 和 Hex
let publicKey = null
let publicKeyHex = null
if (p7.certificates && p7.certificates.length > 0) {
const certObj = p7.certificates[0]
// 提取公钥对象并转换为 PEM 格式
if (certObj.publicKey) {
publicKey = forge.pki.publicKeyToPem(certObj.publicKey)
// 将公钥转换为 hex 格式
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(certObj.publicKey)
const publicKeyDer = forge.asn1.toDer(publicKeyAsn1)
publicKeyHex = forge.util.bytesToHex(publicKeyDer.getBytes())
}
}
// 提取签名值 - 从 rawCapture 中获取(已经是原始签名值)
let signature = null
let signatureHex = null
if (p7.rawCapture && p7.rawCapture.signature) {
// rawCapture.signature 已经是原始签名值字符串
signature = forge.util.encode64(p7.rawCapture.signature)
// 同时提供 hex 格式
signatureHex = forge.util.bytesToHex(p7.rawCapture.signature)
}
return {
cert,
publicKey,
publicKeyHex,
signature,
signatureHex
}
}
/**
* 从 jsrsasign 解析的 PKCS7 中提取信息(支持 SM2
*/
function extractInfoFromJsrsasignP7(base64Data) {
const parser = new rs.KJUR.asn1.cms.CMSParser()
const p7 = parser.getCMSSignedData(rs.b64tohex(base64Data))
// 提取证书
let cert = null
let publicKey = null
let publicKeyHex = null
// p7.certs 是一个对象,包含 array 属性
const certs = p7.certs && p7.certs.array ? p7.certs.array : []
if (certs.length > 0) {
const certPem = certs[0]
// 解析证书获取详细信息
const x509 = new rs.X509()
x509.readCertPEM(certPem)
// 获取主题和颁发者
const subjectStr = x509.getSubjectString()
const issuerStr = x509.getIssuerString()
// 将字符串转换为数组格式
function parseDNString(dnStr) {
if (!dnStr || dnStr === '/') return []
const parts = dnStr.substring(1).split('/')
return parts.map(part => {
const [name, value] = part.split('=')
return { name, value }
})
}
const subjectAttrs = parseDNString(subjectStr)
const issuerAttrs = parseDNString(issuerStr)
// 转换为 DN 字符串
function attrsToDN(attrs) {
if (!attrs || attrs.length === 0) return ''
return attrs.map(attr => `${attr.name}=${attr.value}`).join(', ')
}
cert = {
subject: subjectAttrs,
subjectDN: attrsToDN(subjectAttrs),
issuer: issuerAttrs,
issuerDN: attrsToDN(issuerAttrs),
serialNumber: x509.getSerialNumberHex(),
notBefore: x509.getNotBefore(),
notAfter: x509.getNotAfter(),
pem: certPem
}
// 提取公钥 - 使用 ASN.1 解析提取原始公钥字节
try {
// 将 PEM 转换为 hex
const certHex = rs.pemtohex(certPem)
// 获取 tbsCertificate
const tbsCert = rs.ASN1HEX.getTLVbyList(certHex, 0, [0])
// 获取 subjectPublicKeyInfo (在 tbsCertificate 中的索引是 6)
const spki = rs.ASN1HEX.getTLVbyList(tbsCert, 0, [6])
// 提取公钥值 (BIT STRING)
const publicKeyBitString = rs.ASN1HEX.getTLVbyList(spki, 0, [1])
// BIT STRING 的内容:跳过 tag 03 (2 hex), 跳过 length (2 hex), 跳过 unused bits (2 hex)
// 所以从第 6 个字符开始
const publicKeyHexContent = publicKeyBitString.substring(6)
// 提取算法 OID 来检测是否是 SM2
const algorithmSeq = rs.ASN1HEX.getTLVbyList(spki, 0, [0])
const oidTlv = rs.ASN1HEX.getTLVbyList(algorithmSeq, 0, [0])
const oidHex = oidTlv.substring(4)
// SM2 OID: 1.2.156.10197.1.301 -> hex 2A811CCF550101301
const isSM2 = oidHex.includes('2A811CCF55') || oidHex.includes('2a811ccf55')
// 创建完整的 subjectPublicKeyInfo PEM
publicKey = rs.KJUR.asn1.ASN1Util.getPEMStringFromHex(spki, 'PUBLIC KEY')
// publicKeyHex 就是 subjectPublicKeyInfo 的完整 hex
publicKeyHex = spki
} catch (e) {
// SM2 公钥可能无法通过标准方式获取
console.error('提取 SM2 公钥错误:', e.message)
publicKey = null
publicKeyHex = null
}
}
// 提取签名值
let signature = null
let signatureHex = null
if (p7.sinfos && p7.sinfos.length > 0) {
const signer = p7.sinfos[0]
if (signer.sighex) {
signatureHex = signer.sighex
signature = rs.hextob64(signer.sighex)
}
}
return {
cert,
publicKey,
publicKeyHex,
signature,
signatureHex
}
}
/**
* 验签 PKCS7 签名(自动判断 RSA 或 SM2 算法)
* @param {Buffer|string} p7sign - PKCS7 签名数据
* @param {Buffer|string} [originalData] - 原始数据,当签名是 detech 时必须,当签名是 attach 时不传
* @returns {boolean} 验签结果
*/
module.exports.verifyPKCS7 = function (p7sign, originalData) {
// 处理输入数据
let base64Data
if (Buffer.isBuffer(p7sign)) {
base64Data = p7sign.toString('base64')
} else if (typeof p7sign === 'string') {
// 判断是否是 PEM 格式
if (p7sign.includes('-----BEGIN PKCS7-----')) {
// 提取 base64 部分
base64Data = p7sign
.replace(/-----BEGIN PKCS7-----\n?/, '')
.replace(/-----END PKCS7-----\n?/, '')
.replace(/\n/g, '')
} else {
// 如果不是 PEM 格式,假设是 base64
base64Data = p7sign
}
} else {
throw new Error('Invalid input type for p7sign')
}
// 准备验证所需的数据
let verifyData
if (originalData) {
// 当提供了原始数据时使用原始数据进行验证detch 模式)
if (Buffer.isBuffer(originalData)) {
verifyData = originalData
} else if (typeof originalData === 'string') {
verifyData = Buffer.from(originalData)
} else {
throw new Error('Invalid input type for originalData')
}
}
// 尝试使用 node-forge 解析和验证(支持 RSA
try {
const pemData = `-----BEGIN PKCS7-----\n${base64Data}\n-----END PKCS7-----`
const p7 = forge.pkcs7.messageFromPem(pemData)
// 如果没有提供原始数据,从 PKCS7 中提取attach 模式)
if (!verifyData) {
if (p7.content) {
verifyData = Buffer.from(p7.content.getBytes(), 'binary')
} else {
throw new Error('No content found in PKCS7 signature for attach mode')
}
}
// 对于 RSA 签名,使用公钥手动验证
if (p7.certificates && p7.certificates.length > 0) {
const publicKey = p7.certificates[0].publicKey
if (publicKey && p7.rawCapture && p7.rawCapture.signature) {
// 计算数据的摘要
const md = forge.md.sha256.create()
md.update(verifyData.toString(), 'utf8')
const digest = md.digest().getBytes()
// 使用公钥验证签名
return publicKey.verify(digest, p7.rawCapture.signature)
}
}
// 如果 RSA 验证失败或不适用,尝试使用 node-forge 的 verify如果支持
return p7.verify()
} catch (forgeError) {
// 如果 node-forge 解析失败,尝试使用 jsrsasign 解析和验证(支持 SM2
try {
return verifyPKCS7WithJsrsasign(base64Data, verifyData)
} catch (jsrsasignError) {
// 静默处理错误,直接返回 false
return false
}
}
}
/**
* 使用 jsrsasign 验证 PKCS7 签名(支持 SM2
*/
function verifyPKCS7WithJsrsasign(base64Data, verifyData) {
const parser = new rs.KJUR.asn1.cms.CMSParser()
const p7 = parser.getCMSSignedData(rs.b64tohex(base64Data))
// 如果没有提供原始数据,从 PKCS7 中提取attach 模式)
if (!verifyData) {
if (p7.econtent && p7.econtent.hex) {
// econtent.hex 是原始数据的 hex 编码
verifyData = Buffer.from(p7.econtent.hex, 'hex')
} else {
throw new Error('No content found in PKCS7 signature for attach mode')
}
}
// 获取签名信息
if (!p7.sinfos || p7.sinfos.length === 0) {
throw new Error('No signer info found in PKCS7')
}
const signer = p7.sinfos[0]
// 获取证书和公钥
if (!p7.certs || !p7.certs.array || p7.certs.array.length === 0) {
throw new Error('No certificate found in PKCS7')
}
const certPem = p7.certs.array[0]
const x509 = new rs.X509()
x509.readCertPEM(certPem)
// 判断签名算法
const sigAlg = signer.sigalg
// SM2 签名算法 OID: 1.2.156.10197.1.301.1 或 1.2.156.10197.1.501
if (sigAlg === '1.2.156.10197.1.301.1' || sigAlg === '1.2.156.10197.1.501' || sigAlg.includes('sm2')) {
// SM2 验证
// 提取公钥 hex
const certHex = rs.pemtohex(certPem)
const tbsCert = rs.ASN1HEX.getTLVbyList(certHex, 0, [0])
const spki = rs.ASN1HEX.getTLVbyList(tbsCert, 0, [6])
const publicKeyBitString = rs.ASN1HEX.getTLVbyList(spki, 0, [1])
let publicKeyHex = publicKeyBitString.substring(6) // 跳过 tag, length, unused bits
// 确保公钥有 04 前缀sm-crypto 0.3.13 需要)
if (!publicKeyHex.startsWith('04')) {
publicKeyHex = '04' + publicKeyHex
}
// 计算 SM3 哈希
const hash = sm3(verifyData.toString())
// 签名格式:用户提供的是 ASN.1 DER 格式,需要使用 der: true 选项
let signatureHex = signer.sighex
// 使用 SM2 验证签名 (注意:使用 doVerifySignature不是 doVerify)
// 尝试不同的选项组合,包括 userId
const userId = '1234567812345678' // 默认 userId
try {
// 尝试 1: 使用原始数据 + DER + SM3 + userId
const result1 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true, hash: true, userId })
if (result1) return true
// 尝试 2: 使用原始数据 + DER + SM3
const result2 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true, hash: true })
if (result2) return true
// 尝试 3: 使用原始数据 + DER
const result3 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true })
if (result3) return true
// 尝试 4: 使用哈希 + DER + SM3 + userId
const result4 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true, hash: true, userId })
if (result4) return true
// 尝试 5: 使用哈希 + DER + SM3
const result5 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true, hash: true })
if (result5) return true
// 尝试 6: 使用哈希 + DER
const result6 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true })
if (result6) return true
// 尝试 7: 不使用 der 选项(直接使用签名)
const result7 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex)
if (result7) return true
// 尝试 8: 不使用 der 选项 + SM3 + userId
const result8 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { hash: true, userId })
if (result8) return true
// 尝试 9: 转换签名格式为原始 r|s 格式
if (signatureHex.startsWith('30')) {
try {
// 解析 ASN.1 DER 格式
// 格式: 30 len 02 len r 02 len s
const rStart = 8 // 30440220
const r = signatureHex.substring(rStart, rStart + 64) // 32字节 = 64 hex 字符
const sStart = rStart + 64 + 4 // 0220
const s = signatureHex.substring(sStart, sStart + 64) // 32字节 = 64 hex 字符
const rawSignature = r + s
// 尝试使用原始 r|s 格式
const result9 = sm2.doVerifySignature(verifyData.toString(), rawSignature, publicKeyHex)
if (result9) return true
// 尝试使用原始 r|s 格式 + SM3 + userId
const result10 = sm2.doVerifySignature(verifyData.toString(), rawSignature, publicKeyHex, { hash: true, userId })
if (result10) return true
} catch (e) {
// 忽略错误,继续尝试
}
}
} catch (e) {
// 忽略错误,继续尝试其他选项
}
return false
} else {
// RSA 验证(通过 jsrsasign
try {
const pubKey = x509.getPublicKey()
const sig = new rs.KJUR.crypto.Signature({ alg: 'SHA256withRSA' })
sig.init(pubKey)
sig.updateString(verifyData.toString())
return sig.verify(signer.sighex)
} catch (e) {
throw new Error('RSA verification failed: ' + e.message)
}
}
}