From 959f058def1bcbc817b46771d123fb72c17a53a8 Mon Sep 17 00:00:00 2001 From: cheney Date: Sat, 4 Apr 2026 17:54:13 +0800 Subject: [PATCH] =?UTF-8?q?sm2=20=E9=AA=8C=E7=AD=BE=E4=B8=8D=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kit/src/crypto.js | 61 ++++++++++++- kit/test/debug_sm2_different_data.js | 42 +++++++++ kit/test/debug_sm2_public_key.js | 45 ++++++++++ kit/test/debug_sm2_signature_format.js | 67 ++++++++++++++ kit/test/debug_sm2_signature_format2.js | 50 +++++++++++ kit/test/debug_sm2_verify_detail.js | 113 ++++++++++++++++++++++++ 6 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 kit/test/debug_sm2_different_data.js create mode 100644 kit/test/debug_sm2_public_key.js create mode 100644 kit/test/debug_sm2_signature_format.js create mode 100644 kit/test/debug_sm2_signature_format2.js create mode 100644 kit/test/debug_sm2_verify_detail.js diff --git a/kit/src/crypto.js b/kit/src/crypto.js index 4001895..a3787ec 100644 --- a/kit/src/crypto.js +++ b/kit/src/crypto.js @@ -370,13 +370,70 @@ function verifyPKCS7WithJsrsasign(base64Data, verifyData) { const tbsCert = rs.ASN1HEX.getTLVbyList(certHex, 0, [0]) const spki = rs.ASN1HEX.getTLVbyList(tbsCert, 0, [6]) const publicKeyBitString = rs.ASN1HEX.getTLVbyList(spki, 0, [1]) - const publicKeyHex = publicKeyBitString.substring(6) // 跳过 tag, length, unused bits + let publicKeyHex = publicKeyBitString.substring(6) // 跳过 tag, length, unused bits + + // 去掉公钥的 04 前缀(如果有) + if (publicKeyHex.startsWith('04')) { + publicKeyHex = publicKeyHex.substring(2) + } // 计算 SM3 哈希 const hash = sm3(verifyData.toString()) + // 转换签名格式:从 ASN.1 DER 格式转换为原始 r|s 格式 + let signatureHex = signer.sighex + if (signatureHex.startsWith('30')) { + // ASN.1 DER 格式: 30 44 02 20 [r] 02 20 [s] + try { + 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 字符 + signatureHex = r + s + } catch (e) { + // 如果解析失败,使用原始签名 + } + } + // 使用 SM2 验证签名 (注意:使用 doVerifySignature,不是 doVerify) - return sm2.doVerifySignature(hash, signer.sighex, publicKeyHex) + // 尝试不同的选项组合 + try { + // 尝试 1: 直接验证 + const result1 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex) + if (result1) return true + + // 尝试 2: 使用 DER 格式 + const result2 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true }) + if (result2) return true + + // 尝试 3: 使用 SM3 哈希 + const result3 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { hash: true }) + if (result3) return true + + // 尝试 4: 同时使用 DER 和 SM3 + const result4 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true, hash: true }) + if (result4) return true + + // 尝试 5: 直接使用原始数据而不是哈希 + const result5 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex) + if (result5) return true + + // 尝试 6: 使用原始数据 + DER + const result6 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true }) + if (result6) return true + + // 尝试 7: 使用原始数据 + SM3 + const result7 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { hash: true }) + if (result7) return true + + // 尝试 8: 所有选项 + const result8 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true, hash: true }) + if (result8) return true + } catch (e) { + // 忽略错误,继续尝试其他选项 + } + + return false } else { // RSA 验证(通过 jsrsasign) try { diff --git a/kit/test/debug_sm2_different_data.js b/kit/test/debug_sm2_different_data.js new file mode 100644 index 0000000..3a83a2d --- /dev/null +++ b/kit/test/debug_sm2_different_data.js @@ -0,0 +1,42 @@ +const crypto = require('../src/crypto.js'); + +// 用户提供的 SM2 PKCS7 签名数据 +const sm2P7Base64 = 'MIIDKgYKKoEcz1UGAQQCAqCCAxowggMWAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIICNzCCAjMwggHWoAMCAQICBgD1tfRoKTAMBggqgRzPVQGDdQUAMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBMB4XDTI2MDQwMjA4MDEzOVoXDTM2MDMzMDA4MDEzOVowTTELMAkGA1UEBhMCQ04xDzANBgNVBAoMBlBCQ0NSQzEtMCsGA1UEAwwkOTRmMWY5MzgtZjBiZC00NzYyLTkyOGUtZjY4YTdjY2UxNDFkMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEu3dU5Vn20VjVL9xGCCRPB/EpATY6HW24+zIZv46Cfv0R2PGM+ZJs/dYarw7qJCnpIbe5T0PKmyyJITNVrVnnMaOBrDCBqTAfBgNVHSMEGDAWgBRUqfVQHMpD+o+YdrPK1JlRJL757zAJBgNVHRMEAjAAME8GA1UdHwRIMEYwRKBCoECkPjA8MQ4wDAYDVQQDDAVjcmw3MTEMMAoGA1UECwwDY3JsMQ8wDQYDVQQKDAZQQkNDUkMxCzAJBgNVBAYTAkNOMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU5bG6U0lYzWtkPJE74M/3HdafKKcwDAYIKoEcz1UBg3UFAANJADBGAiEAuLTE36/Eobcui23bFiPvOU4MOT0O0r9SyyHZ1JuvLwMCIQCejuSk03jyUAkbh1SaPx5n7WlDjI6tx9v9RJ7iw5rlojGBtzCBtAIBATBKMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBAgYA9bX0aCkwDAYIKoEcz1UBgxEFADANBgkqgRzPVQGCLQEFAARGMEQCIALTgWMD7io1JHfzInBwzDQpe9bonchuLFSED//JLDtnAiBmEUtr+ssAQIzFG4wY0CAj9QZxhENyCU2UYL1fhaysUA=='; + +console.log('=== 测试不同的原始数据 ==='); + +// 测试不同的原始数据 +const testDataList = [ + 'QDJNHHVB_FWQ', + 'QDJNHHVB_FWQ\n', + 'QDJNHHVB_FWQ\r\n', + 'QDJNHHVB FWQ', // 空格替换下划线 + 'QDJNHHVB_FW', // 少一个字符 + 'QDJNHHVB_FWQQ', // 多一个字符 + 'qDJNHHVB_FWQ', // 小写 + 'QDJnhhVB_FWQ', // 混合大小写 + '1234567890', // 数字 + '', // 空字符串 +]; + +testDataList.forEach((data, index) => { + console.log(`\n测试数据 ${index + 1}: "${data}"`); + console.log(` 长度: ${data.length}`); + console.log(` UTF-8 编码: ${Buffer.from(data, 'utf8').toString('hex')}`); + + const result = crypto.verifyPKCS7(sm2P7Base64, data); + console.log(` 验证结果: ${result}`); + + if (result) { + console.log(' ✅ 验证成功!'); + } +}); + +// 也测试一下 attach 模式 +console.log('\n=== 测试 attach 模式 ==='); +try { + const result = crypto.verifyPKCS7(sm2P7Base64); + console.log('attach 模式验证结果:', result); +} catch (e) { + console.error('attach 模式错误:', e.message); +} diff --git a/kit/test/debug_sm2_public_key.js b/kit/test/debug_sm2_public_key.js new file mode 100644 index 0000000..3f52238 --- /dev/null +++ b/kit/test/debug_sm2_public_key.js @@ -0,0 +1,45 @@ +const sm2 = require('sm-crypto').sm2; +const sm3 = require('sm-crypto').sm3; + +console.log('=== 调试 SM2 公钥格式 ==='); + +// 测试数据 +const testData = 'QDJNHHVB_FWQ'; +const hash = sm3(testData); +console.log('测试数据:', testData); +console.log('SM3 哈希:', hash); + +// 用户提供的签名(ASN.1 DER 格式) +const userSignature = '3044022002d3816303ee2a352477f3227070cc34297bd6e89dc86e2c54840fffc92c3b67022066114b6bfacb00408cc51b8c18d02023f50671844372094d9460bd5f85acac50'; + +// 解析签名 +const rStart = 8; +const r = userSignature.substring(rStart, rStart + 64); +const sStart = rStart + 64 + 4; +const s = userSignature.substring(sStart, sStart + 64); +const rawSignature = r + s; +console.log('\n原始签名格式:', rawSignature); + +// 用户公钥 +const userPublicKey = '04bb7754e559f6d158d52fdc4608244f07f12901363a1d6db8fb3219bf8e827efd11d8f18cf9926cfdd61aaf0eea2429e921b7b94f43ca9b2c89213355ad59e731'; +console.log('\n完整公钥:', userPublicKey); + +// 去掉 04 前缀 +const pubKeyWithoutPrefix = userPublicKey.substring(2); +console.log('去掉前缀的公钥:', pubKeyWithoutPrefix); + +// 尝试验证 +console.log('\n验证结果:'); +try { + const result1 = sm2.doVerifySignature(hash, rawSignature, userPublicKey); + console.log(' 使用完整公钥:', result1); +} catch (e) { + console.error(' 完整公钥错误:', e.message); +} + +try { + const result2 = sm2.doVerifySignature(hash, rawSignature, pubKeyWithoutPrefix); + console.log(' 使用去掉前缀的公钥:', result2); +} catch (e) { + console.error(' 去掉前缀错误:', e.message); +} diff --git a/kit/test/debug_sm2_signature_format.js b/kit/test/debug_sm2_signature_format.js new file mode 100644 index 0000000..b4ca2ab --- /dev/null +++ b/kit/test/debug_sm2_signature_format.js @@ -0,0 +1,67 @@ +const sm2 = require('sm-crypto').sm2; +const sm3 = require('sm-crypto').sm3; + +console.log('=== 调试 SM2 签名格式 ==='); + +// 生成测试数据 +const testData = 'QDJNHHVB_FWQ'; +const hash = sm3(testData); +console.log('测试数据:', testData); +console.log('SM3 哈希:', hash); + +// 生成密钥对 +const keypair = sm2.generateKeyPairHex(); +console.log('\n生成的密钥对:'); +console.log(' 私钥:', keypair.privateKey); +console.log(' 公钥:', keypair.publicKey); + +// 生成签名 +const sig = sm2.doSignature(hash, keypair.privateKey); +console.log('\nsm-crypto 生成的签名:', sig); +console.log('签名长度:', sig.length); + +// 验证签名 +const verifyResult = sm2.doVerifySignature(hash, sig, keypair.publicKey); +console.log('验证结果:', verifyResult); + +// 对比用户提供的签名 +const userSignature = '3044022002d3816303ee2a352477f3227070cc34297bd6e89dc86e2c54840fffc92c3b67022066114b6bfacb00408cc51b8c18d02023f50671844372094d9460bd5f85acac50'; +console.log('\n用户提供的签名:', userSignature); +console.log('用户签名长度:', userSignature.length); + +// 分析签名格式 +console.log('\n签名格式分析:'); +console.log('sm-crypto 签名格式:', sig.length === 128 ? '原始 r|s 格式' : '其他格式'); +console.log('用户签名格式:', userSignature.startsWith('30') ? 'ASN.1 DER 格式' : '其他格式'); + +// 尝试转换用户签名格式 +if (userSignature.startsWith('30')) { + console.log('\n尝试将 ASN.1 DER 格式转换为原始格式:'); + try { + // 解析 ASN.1 DER 格式 + // 格式: 30 len 02 len r 02 len s + const rStart = 6; // 30 44 02 20 + const rLen = parseInt(userSignature.substring(4, 6), 16) * 2; + const r = userSignature.substring(rStart, rStart + rLen); + + const sStart = rStart + rLen + 4; // 02 20 + const sLen = parseInt(userSignature.substring(rStart + rLen + 2, rStart + rLen + 4), 16) * 2; + const s = userSignature.substring(sStart, sStart + sLen); + + console.log(' 提取的 r:', r); + console.log(' 提取的 s:', s); + + // 组合成原始格式 + const rawSignature = r + s; + console.log(' 转换后的原始格式:', rawSignature); + console.log(' 原始格式长度:', rawSignature.length); + + // 尝试验证 + const userPublicKey = '04bb7754e559f6d158d52fdc4608244f07f12901363a1d6db8fb3219bf8e827efd11d8f18cf9926cfdd61aaf0eea2429e921b7b94f43ca9b2c89213355ad59e731'; + const result = sm2.doVerifySignature(hash, rawSignature, userPublicKey); + console.log(' 验证结果:', result); + + } catch (e) { + console.error(' 转换错误:', e.message); + } +} diff --git a/kit/test/debug_sm2_signature_format2.js b/kit/test/debug_sm2_signature_format2.js new file mode 100644 index 0000000..f971cff --- /dev/null +++ b/kit/test/debug_sm2_signature_format2.js @@ -0,0 +1,50 @@ +const sm2 = require('sm-crypto').sm2; +const sm3 = require('sm-crypto').sm3; + +console.log('=== 调试 SM2 签名格式(正确解析)==='); + +// 测试数据 +const testData = 'QDJNHHVB_FWQ'; +const hash = sm3(testData); +console.log('测试数据:', testData); +console.log('SM3 哈希:', hash); + +// 用户提供的签名(ASN.1 DER 格式) +const userSignature = '3044022002d3816303ee2a352477f3227070cc34297bd6e89dc86e2c54840fffc92c3b67022066114b6bfacb00408cc51b8c18d02023f50671844372094d9460bd5f85acac50'; +console.log('\n用户提供的签名:', userSignature); + +// 正确解析 ASN.1 DER 格式 +console.log('\n正确解析 ASN.1 DER 格式:'); +try { + // 30 44 02 20 [r] 02 20 [s] + // 30 = 序列 + // 44 = 长度 68 字节 + // 02 = 整数 + // 20 = 长度 32 字节 + + // 提取 r 值 + const rStart = 8; // 30440220 + const r = userSignature.substring(rStart, rStart + 64); // 32字节 = 64 hex 字符 + console.log(' r 值:', r); + + // 提取 s 值 + const sStart = rStart + 64 + 4; // 0220 + const s = userSignature.substring(sStart, sStart + 64); // 32字节 = 64 hex 字符 + console.log(' s 值:', s); + + // 组合成原始格式 (r + s) + const rawSignature = r + s; + console.log(' 转换后的原始格式:', rawSignature); + console.log(' 原始格式长度:', rawSignature.length); + + // 用户公钥 + const userPublicKey = '04bb7754e559f6d158d52fdc4608244f07f12901363a1d6db8fb3219bf8e827efd11d8f18cf9926cfdd61aaf0eea2429e921b7b94f43ca9b2c89213355ad59e731'; + console.log('\n用户公钥:', userPublicKey); + + // 尝试验证 + const result = sm2.doVerifySignature(hash, rawSignature, userPublicKey); + console.log('\n验证结果:', result); + +} catch (e) { + console.error('解析错误:', e.message); +} diff --git a/kit/test/debug_sm2_verify_detail.js b/kit/test/debug_sm2_verify_detail.js new file mode 100644 index 0000000..190e2be --- /dev/null +++ b/kit/test/debug_sm2_verify_detail.js @@ -0,0 +1,113 @@ +const rs = require('jsrsasign'); +const sm2 = require('sm-crypto').sm2; +const sm3 = require('sm-crypto').sm3; + +// 用户提供的 SM2 PKCS7 签名数据 +const sm2P7Base64 = 'MIIDKgYKKoEcz1UGAQQCAqCCAxowggMWAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIICNzCCAjMwggHWoAMCAQICBgD1tfRoKTAMBggqgRzPVQGDdQUAMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBMB4XDTI2MDQwMjA4MDEzOVoXDTM2MDMzMDA4MDEzOVowTTELMAkGA1UEBhMCQ04xDzANBgNVBAoMBlBCQ0NSQzEtMCsGA1UEAwwkOTRmMWY5MzgtZjBiZC00NzYyLTkyOGUtZjY4YTdjY2UxNDFkMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEu3dU5Vn20VjVL9xGCCRPB/EpATY6HW24+zIZv46Cfv0R2PGM+ZJs/dYarw7qJCnpIbe5T0PKmyyJITNVrVnnMaOBrDCBqTAfBgNVHSMEGDAWgBRUqfVQHMpD+o+YdrPK1JlRJL757zAJBgNVHRMEAjAAME8GA1UdHwRIMEYwRKBCoECkPjA8MQ4wDAYDVQQDDAVjcmw3MTEMMAoGA1UECwwDY3JsMQ8wDQYDVQQKDAZQQkNDUkMxCzAJBgNVBAYTAkNOMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU5bG6U0lYzWtkPJE74M/3HdafKKcwDAYIKoEcz1UBg3UFAANJADBGAiEAuLTE36/Eobcui23bFiPvOU4MOT0O0r9SyyHZ1JuvLwMCIQCejuSk03jyUAkbh1SaPx5n7WlDjI6tx9v9RJ7iw5rlojGBtzCBtAIBATBKMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBAgYA9bX0aCkwDAYIKoEcz1UBgxEFADANBgkqgRzPVQGCLQEFAARGMEQCIALTgWMD7io1JHfzInBwzDQpe9bonchuLFSED//JLDtnAiBmEUtr+ssAQIzFG4wY0CAj9QZxhENyCU2UYL1fhaysUA=='; + +// 测试用的原始数据 +const mockOriginalData = 'QDJNHHVB_FWQ'; + +console.log('=== 详细调试 SM2 PKCS7 验证 ==='); +console.log('原始数据:', mockOriginalData); +console.log('原始数据长度:', mockOriginalData.length); +console.log('原始数据 UTF-8 编码:', Buffer.from(mockOriginalData, 'utf8').toString('hex')); + +const parser = new rs.KJUR.asn1.cms.CMSParser(); +const p7 = parser.getCMSSignedData(rs.b64tohex(sm2P7Base64)); + +console.log('\nPKCS7 信息:'); +console.log('版本:', p7.version); +console.log('哈希算法:', p7.hashalgs); +console.log('签名者数量:', p7.sinfos ? p7.sinfos.length : 0); + +if (p7.sinfos && p7.sinfos.length > 0) { + const signer = p7.sinfos[0]; + console.log('\n签名者信息:'); + console.log(' 签名算法:', signer.sigalg); + console.log(' 签名值 (hex):', signer.sighex); + console.log(' 签名值长度:', signer.sighex ? signer.sighex.length / 2 : 0, '字节'); + + // 解析签名值(ASN.1 DER 格式) + console.log('\n解析签名值:'); + try { + const sigAsn1 = rs.ASN1HEX.getTLVbyList(signer.sighex, 0, []); + console.log(' 签名 ASN.1 结构:', sigAsn1); + + // 提取 r 和 s 值 + const r = rs.ASN1HEX.getTLVbyList(sigAsn1, 0, [0]); + const s = rs.ASN1HEX.getTLVbyList(sigAsn1, 0, [1]); + console.log(' r 值:', r.substring(4)); // 跳过 tag 和 length + console.log(' s 值:', s.substring(4)); + } catch (e) { + console.error(' 解析签名值错误:', e.message); + } +} + +// 提取公钥 +if (p7.certs && p7.certs.array && p7.certs.array.length > 0) { + const certPem = p7.certs.array[0]; + 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]); + const publicKeyHex = publicKeyBitString.substring(6); + + console.log('\n公钥信息:'); + console.log(' 公钥 (hex):', publicKeyHex); + console.log(' 公钥长度:', publicKeyHex.length / 2, '字节'); + console.log(' 公钥格式:', publicKeyHex.startsWith('04') ? '未压缩格式' : '压缩格式'); + + // 尝试验证签名 + if (p7.sinfos && p7.sinfos.length > 0) { + const signer = p7.sinfos[0]; + + // 计算 SM3 哈希 + console.log('\n计算 SM3 哈希:'); + const hash = sm3(mockOriginalData); + console.log(' 原始数据哈希:', hash); + + // 测试不同的公钥格式 + console.log('\n测试不同的公钥格式:'); + + // 1. 使用完整公钥(带 04 前缀) + try { + const result1 = sm2.doVerifySignature(hash, signer.sighex, publicKeyHex); + console.log(' 验证结果(完整公钥):', result1); + } catch (e) { + console.error(' 验证错误(完整公钥):', e.message); + } + + // 2. 使用不带 04 前缀的公钥 + try { + const pubKeyWithoutPrefix = publicKeyHex.substring(2); + const result2 = sm2.doVerifySignature(hash, signer.sighex, pubKeyWithoutPrefix); + console.log(' 验证结果(不带前缀):', result2); + } catch (e) { + console.error(' 验证错误(不带前缀):', e.message); + } + + // 3. 尝试使用签名值的不同格式 + console.log('\n测试不同的签名值格式:'); + try { + // 直接使用 sighex + const result3 = sm2.doVerifySignature(hash, signer.sighex, publicKeyHex); + console.log(' 验证结果(直接使用 sighex):', result3); + } catch (e) { + console.error(' 验证错误(直接使用 sighex):', e.message); + } + + // 4. 测试不同的编码 + console.log('\n测试不同的编码:'); + try { + // 使用 Buffer 转换 + const hashBuffer = Buffer.from(mockOriginalData, 'utf8'); + const hash2 = sm3(hashBuffer); + console.log(' Buffer 哈希:', hash2); + const result4 = sm2.doVerifySignature(hash2, signer.sighex, publicKeyHex); + console.log(' 验证结果(Buffer 编码):', result4); + } catch (e) { + console.error(' 验证错误(Buffer 编码):', e.message); + } + } +}