diff --git a/.gitignore b/.gitignore index b073d14..b83d222 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1 @@ -/target/classes/com/sunyard/cisd/device/tool/CMSSignatureUtil.class -/target/classes/com/sunyard/cisd/device/tool/CMSUtil.class -/target/classes/com/sunyard/cisd/device/tool/DeviceFingerprint.class -/target/classes/com/sunyard/cisd/device/tool/DeviceFingerprintService.class -/target/classes/com/sunyard/cisd/device/tool/Main.class -/target/classes/com/sunyard/cisd/device/tool/SdfDeviceInfo.class -/target/classes/com/sunyard/cisd/device/tool/SdfNativeLibrary.class -/target/classes/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil$DecryptedResult.class -/target/classes/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil$SM2KeyPair.class -/target/classes/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil.class -/target/classes/com/sunyard/cisd/device/tool/SM2Util.class -/target/classes/com/sunyard/cisd/device/tool/SM3Util.class -/target/classes/CFCA_TEST_CS_SM2_CA.cer -/target/classes/fingerprint_sm2_private_key.pem -/target/classes/fingerprint_sm2_public_key.pem -/target/classes/mac_sm2_private_key.pem -/target/classes/mac_sm2_public_key.pem -/target/maven-archiver/pom.properties -/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst -/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst -/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst -/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst -/target/test-classes/com/sunyard/cisd/CMSSignatureTest.class -/target/test-classes/com/sunyard/cisd/SM2UtilTest.class -/target/sectool-1.0.3.jar -/target/sectool-1.0.3-jar-with-dependencies.jar +/target/ diff --git a/src/main/java/com/sunyard/cisd/device/tool/CMSUtil.java b/src/main/java/com/sunyard/cisd/device/tool/CMSUtil.java index d065f84..516f9c8 100644 --- a/src/main/java/com/sunyard/cisd/device/tool/CMSUtil.java +++ b/src/main/java/com/sunyard/cisd/device/tool/CMSUtil.java @@ -1,15 +1,17 @@ package com.sunyard.cisd.device.tool; -import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.cms.*; import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.x509.Certificate; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.*; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.Store; -import java.security.Security; +import java.security.*; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Base64; @@ -30,7 +32,7 @@ public class CMSUtil { try (ASN1InputStream asn1InputStream = new ASN1InputStream(cmsData)) { ContentInfo contentInfo = ContentInfo.getInstance(asn1InputStream.readObject()); String typeOid = contentInfo.getContentType().getId(); - + if (typeOid.equals(CMSObjectIdentifiers.signedData.getId())) { return "SignedData"; } else if (typeOid.equals(CMSObjectIdentifiers.envelopedData.getId())) { @@ -129,6 +131,568 @@ public class CMSUtil { return (X509Certificate) cf.generateCertificate(new java.io.ByteArrayInputStream(certHolder.getEncoded())); } + public static class SignedAndEnvelopedVerifyResult { + public final boolean signatureValid; + public final byte[] messageDigest; + public final X509Certificate signerCertificate; + public final String signerSubjectDN; + + public SignedAndEnvelopedVerifyResult(boolean signatureValid, byte[] messageDigest, + X509Certificate signerCertificate, String signerSubjectDN) { + this.signatureValid = signatureValid; + this.messageDigest = messageDigest; + this.signerCertificate = signerCertificate; + this.signerSubjectDN = signerSubjectDN; + } + } + + public static SignedAndEnvelopedVerifyResult verifySignedAndEnvelopedData(String base64CmsData) throws Exception { + byte[] cmsData = Base64.getDecoder().decode(base64CmsData); + return verifySignedAndEnvelopedData(cmsData); + } + + private static X509Certificate tryParseCertificate(ASN1Encodable elem) { + try { + if (elem instanceof ASN1Sequence) { + Certificate cert = Certificate.getInstance(elem); + byte[] certEncoded = cert.getEncoded(); + return loadCertificateFromBytes(certEncoded); + } + } catch (Exception e) { + } + return null; + } + + public static SignedAndEnvelopedVerifyResult verifySignedAndEnvelopedData(byte[] cmsData) throws Exception { + System.out.println("[步骤1] 解析 ContentInfo..."); + ASN1InputStream asn1InputStream = new ASN1InputStream(cmsData); + ContentInfo contentInfo = ContentInfo.getInstance(asn1InputStream.readObject()); + System.out.println(" ContentInfo 解析成功"); + System.out.println(" OID: " + contentInfo.getContentType().getId()); + + if (!contentInfo.getContentType().equals(CMSObjectIdentifiers.signedAndEnvelopedData)) { + throw new Exception("不是 SignedAndEnvelopedData 格式"); + } + + System.out.println("[步骤2] 解析 SignedAndEnvelopedData 结构..."); + ASN1Primitive contentPrimitive = contentInfo.getContent().toASN1Primitive(); + System.out.println(" Content 类型: " + contentPrimitive.getClass().getSimpleName()); + + ASN1Sequence saeSequence = ASN1Sequence.getInstance(contentPrimitive); + System.out.println(" SignedAndEnvelopedData 包含 " + saeSequence.size() + " 个元素"); + + int index = 0; + + ASN1Integer version = ASN1Integer.getInstance(saeSequence.getObjectAt(index++).toASN1Primitive()); + System.out.println(" 版本: " + version.getValue()); + + ASN1Set recipientInfos = ASN1Set.getInstance(saeSequence.getObjectAt(index++).toASN1Primitive()); + System.out.println(" RecipientInfos 数量: " + recipientInfos.size()); + + System.out.println(" 检查后续元素,寻找证书和签名信息..."); + X509Certificate signerCert = null; + byte[] signedAttrsEncoded = null; + byte[] signatureValue = null; + + for (int i = index; i < saeSequence.size(); i++) { + ASN1Encodable elemEncodable = saeSequence.getObjectAt(i); + ASN1Primitive elem = elemEncodable.toASN1Primitive(); + System.out.println("\n 检查第" + (i+1) + "个元素,类型: " + elem.getClass().getSimpleName()); + + if (elem instanceof ASN1Set) { + ASN1Set set = ASN1Set.getInstance(elem); + System.out.println(" DLSet 包含 " + set.size() + " 个元素"); + + for (int si = 0; si < set.size(); si++) { + ASN1Encodable setElem = set.getObjectAt(si); + System.out.println(" DLSet[" + si + "] 类型: " + setElem.toASN1Primitive().getClass().getSimpleName()); + + X509Certificate cert = tryParseCertificate(setElem); + if (cert != null) { + signerCert = cert; + System.out.println(" 从DLSet[" + si + "] 找到证书!"); + System.out.println(" 证书主题: " + signerCert.getSubjectX500Principal().getName()); + break; + } + + if (setElem.toASN1Primitive() instanceof ASN1Sequence) { + ASN1Sequence innerSeq = ASN1Sequence.getInstance(setElem); + for (int j = 0; j < innerSeq.size(); j++) { + cert = tryParseCertificate(innerSeq.getObjectAt(j)); + if (cert != null) { + signerCert = cert; + System.out.println(" 从DLSet[" + si + "]的嵌套序列找到证书!"); + System.out.println(" 证书主题: " + signerCert.getSubjectX500Principal().getName()); + break; + } + } + } + } + } + + if (elem instanceof ASN1Sequence) { + ASN1Sequence seq = ASN1Sequence.getInstance(elem); + System.out.println(" 序列包含 " + seq.size() + " 个元素"); + + for (int j = 0; j < seq.size(); j++) { + ASN1Encodable subElem = seq.getObjectAt(j); + ASN1Primitive subPrim = subElem.toASN1Primitive(); + System.out.println(" 子元素 " + j + ": " + subPrim.getClass().getSimpleName()); + + X509Certificate cert = tryParseCertificate(subElem); + if (cert != null) { + signerCert = cert; + System.out.println(" 从子元素 " + j + " 找到证书!"); + System.out.println(" 证书主题: " + signerCert.getSubjectX500Principal().getName()); + break; + } + + if (subPrim instanceof ASN1Sequence) { + ASN1Sequence subSeq = ASN1Sequence.getInstance(subPrim); + for (int k = 0; k < subSeq.size(); k++) { + cert = tryParseCertificate(subSeq.getObjectAt(k)); + if (cert != null) { + signerCert = cert; + System.out.println(" 从子元素 " + j + " 的嵌套序列[" + k + "]找到证书!"); + System.out.println(" 证书主题: " + signerCert.getSubjectX500Principal().getName()); + break; + } + } + } + if (signerCert != null) break; + } + } + + if (elem instanceof ASN1TaggedObject) { + ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(elem); + System.out.println(" TaggedObject 标签编号: " + tagged.getTagNo()); + ASN1Primitive innerPrim = tagged.getObject(); + System.out.println(" 内部对象类型: " + innerPrim.getClass().getSimpleName()); + + X509Certificate cert = tryParseCertificate(tagged.getObject()); + if (cert != null) { + signerCert = cert; + System.out.println(" 从TaggedObject找到证书!"); + System.out.println(" 证书主题: " + signerCert.getSubjectX500Principal().getName()); + } + + if (innerPrim instanceof ASN1Sequence) { + ASN1Sequence innerSeq = ASN1Sequence.getInstance(innerPrim); + for (int j = 0; j < innerSeq.size(); j++) { + cert = tryParseCertificate(innerSeq.getObjectAt(j)); + if (cert != null) { + signerCert = cert; + System.out.println(" 从TaggedObject的内部序列[" + j + "]找到证书!"); + System.out.println(" 证书主题: " + signerCert.getSubjectX500Principal().getName()); + break; + } + } + } + } + + if (signerCert != null) break; + } + + if (signerCert == null) { + throw new Exception("数字信封中未找到有效的签名者证书"); + } + + System.out.println("\n[步骤3] 提取签名者证书 - 成功"); + System.out.println(" 签名者主题: " + signerCert.getSubjectX500Principal().getName()); + System.out.println(" 公钥算法: " + signerCert.getPublicKey().getAlgorithm()); + System.out.println(" 证书签名算法: " + signerCert.getSigAlgName()); + + System.out.println("\n[步骤4] 解析 SignerInfo..."); + for (int i = index; i < saeSequence.size(); i++) { + ASN1Primitive elem = saeSequence.getObjectAt(i).toASN1Primitive(); + + if (elem instanceof ASN1Set) { + ASN1Set signerInfosSet = ASN1Set.getInstance(elem); + System.out.println(" SignerInfos 包含 " + signerInfosSet.size() + " 个元素"); + + for (int si = 0; si < signerInfosSet.size(); si++) { + System.out.println("\n 处理第 " + (si+1) + " 个 SignerInfo..."); + ASN1Encodable signerInfoEncodable = signerInfosSet.getObjectAt(si); + ASN1Primitive signerInfoPrim = signerInfoEncodable.toASN1Primitive(); + System.out.println(" SignerInfo 原始类型: " + signerInfoPrim.getClass().getSimpleName()); + + String signerInfoPrimClassName = signerInfoPrim.getClass().getName(); + System.out.println(" SignerInfo 原始类型: " + signerInfoPrimClassName); + System.out.println(" 是 ASN1Sequence: " + (signerInfoPrim instanceof ASN1Sequence)); + System.out.println(" 是 ASN1TaggedObject: " + (signerInfoPrim instanceof ASN1TaggedObject)); + System.out.println(" 类名包含 TaggedObject: " + signerInfoPrimClassName.contains("TaggedObject")); + + if (signerInfoPrim instanceof ASN1Sequence) { + ASN1Sequence signerInfoSeq = ASN1Sequence.getInstance(signerInfoPrim); + System.out.println(" SignerInfo 包含 " + signerInfoSeq.size() + " 个元素"); + + for (int el = 0; el < signerInfoSeq.size(); el++) { + ASN1Encodable elEnc = signerInfoSeq.getObjectAt(el); + System.out.println(" 元素 " + el + ": " + elEnc.toASN1Primitive().getClass().getSimpleName()); + } + + if (signerInfoSeq.size() < 4) { + System.out.println(" SignerInfo 元素数量不足,跳过"); + continue; + } + + int signerIndex = 0; + ASN1Integer signerVersion = ASN1Integer.getInstance(signerInfoSeq.getObjectAt(signerIndex++).toASN1Primitive()); + System.out.println(" Signer 版本: " + signerVersion.getValue()); + + System.out.println(" SignerIdentifier 解析成功"); + + ASN1Sequence digestAlgorithm = ASN1Sequence.getInstance(signerInfoSeq.getObjectAt(signerIndex++).toASN1Primitive()); + System.out.println(" DigestAlgorithm 解析成功"); + + ASN1Encodable authAttrsData = null; + System.out.println(" 检查 AuthenticatedAttributes (signedAttrs)..."); + + boolean foundAuthenticatedAttributes = false; + while (signerIndex < signerInfoSeq.size() && !foundAuthenticatedAttributes) { + ASN1Encodable authAttrsObj = signerInfoSeq.getObjectAt(signerIndex); + String elemClassName = authAttrsObj.getClass().getName(); + boolean isTaggedObject = elemClassName.contains("TaggedObject"); + boolean isSet = (authAttrsObj instanceof ASN1Set); + boolean isSequence = (authAttrsObj instanceof ASN1Sequence); + + System.out.println(" 检查元素 " + signerIndex + " 类型: " + elemClassName); + + if (isSet) { + authAttrsData = authAttrsObj; + System.out.println(" 发现 SET 类型 AuthenticatedAttributes"); + foundAuthenticatedAttributes = true; + } else if (isTaggedObject) { + ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(authAttrsObj); + int tagNo = tagged.getTagNo(); + System.out.println(" 发现 TaggedObject,标签: " + tagNo); + ASN1Encodable innerObj = tagged.getObject(); + System.out.println(" 内部对象类型: " + innerObj.getClass().getName()); + + if (innerObj instanceof ASN1Set) { + authAttrsData = innerObj; + System.out.println(" 从 TaggedObject[" + tagNo + "] 提取 SET"); + foundAuthenticatedAttributes = true; + } else if (innerObj instanceof ASN1Sequence) { + ASN1Sequence innerSeq = ASN1Sequence.getInstance(innerObj); + System.out.println(" TaggedObject[" + tagNo + "] 内部是 Sequence,包含: " + innerSeq.size()); + + if (tagNo == 0 && innerSeq.size() > 0) { + System.out.println(" 从标签[0]的 Sequence 提取 AuthenticatedAttributes,包含: " + innerSeq.size() + " 个属性"); + // 使用原始的 DLTaggedObject 获取 DER 编码,而不是从 innerSeq 重新编码 + authAttrsData = tagged; + foundAuthenticatedAttributes = true; + signerIndex++; + System.out.println(" 找到 AuthenticatedAttributes,signerIndex 递增到: " + signerIndex); + } else { + System.out.println(" 标签[" + tagNo + "] 不是 AuthenticatedAttributes (应为[0]),跳过"); + } + } + if (!foundAuthenticatedAttributes) { + signerIndex++; + } + } else if (isSequence) { + System.out.println(" Sequence 类型: " + elemClassName + ",跳过"); + signerIndex++; + } else { + System.out.println(" 未知类型: " + elemClassName + ",跳过"); + signerIndex++; + } + } + + if (signerIndex >= signerInfoSeq.size()) { + System.out.println(" AuthenticatedAttributes 为空,跳过"); + continue; + } + + ASN1Sequence digestEncryptionAlgorithm = ASN1Sequence.getInstance(signerInfoSeq.getObjectAt(signerIndex++).toASN1Primitive()); + System.out.println(" DigestEncryptionAlgorithm 解析成功"); + + if (signerIndex >= signerInfoSeq.size()) { + System.out.println(" EncryptedDigest 不存在"); + continue; + } + + ASN1OctetString encryptedDigest = ASN1OctetString.getInstance(signerInfoSeq.getObjectAt(signerIndex)); + System.out.println(" EncryptedDigest 长度: " + encryptedDigest.getOctets().length + " bytes"); + + if (authAttrsData == null) { + System.out.println(" AuthenticatedAttributes 为空,跳过验签"); + continue; + } + + System.out.println("\n[步骤5] 提取待签名数据 (signedAttrs DER 编码)..."); + signedAttrsEncoded = authAttrsData.toASN1Primitive().getEncoded(ASN1Encoding.DER); + System.out.println(" signedAttrs DER 编码长度: " + signedAttrsEncoded.length + " bytes"); + System.out.println(" signedAttrs Hex: " + bytesToHex(signedAttrsEncoded)); + + System.out.println("\n[步骤6] 提取原文 Hash (message-digest 属性,仅打印不参与运算)..."); + byte[] messageDigest = null; + + ASN1EncodableVector authAttrsVector = new ASN1EncodableVector(); + if (authAttrsData instanceof ASN1Set) { + ASN1Set authSet = (ASN1Set) authAttrsData; + for (int j = 0; j < authSet.size(); j++) { + authAttrsVector.add(authSet.getObjectAt(j)); + } + } else if (authAttrsData instanceof ASN1Sequence) { + ASN1Sequence authSeq = (ASN1Sequence) authAttrsData; + for (int j = 0; j < authSeq.size(); j++) { + authAttrsVector.add(authSeq.getObjectAt(j)); + } + } else if (authAttrsData instanceof ASN1TaggedObject) { + ASN1TaggedObject tagged = (ASN1TaggedObject) authAttrsData; + ASN1Primitive inner = tagged.getObject(); + if (inner instanceof ASN1Sequence) { + ASN1Sequence authSeq = (ASN1Sequence) inner; + for (int j = 0; j < authSeq.size(); j++) { + authAttrsVector.add(authSeq.getObjectAt(j)); + } + } + } + + for (int j = 0; j < authAttrsVector.size(); j++) { + ASN1Sequence attrSeq = ASN1Sequence.getInstance(authAttrsVector.get(j).toASN1Primitive()); + + if (attrSeq.size() < 2) { + continue; + } + + ASN1ObjectIdentifier attrType = ASN1ObjectIdentifier.getInstance(attrSeq.getObjectAt(0)); + + if (attrType.equals(CMSAttributes.messageDigest)) { + ASN1Encodable attrValues = attrSeq.getObjectAt(1); + ASN1Primitive attrValuesPrim = attrValues.toASN1Primitive(); + + if (attrValuesPrim instanceof ASN1Set) { + ASN1Set valuesSet = ASN1Set.getInstance(attrValuesPrim); + if (valuesSet.size() > 0) { + messageDigest = ASN1OctetString.getInstance(valuesSet.getObjectAt(0)).getOctets(); + System.out.println(" message-digest 值 (Hex): " + bytesToHex(messageDigest)); + System.out.println(" message-digest 长度: " + messageDigest.length + " bytes"); + } + } else if (attrValuesPrim instanceof ASN1OctetString) { + messageDigest = ((ASN1OctetString) attrValuesPrim).getOctets(); + System.out.println(" message-digest 值 (Hex): " + bytesToHex(messageDigest)); + System.out.println(" message-digest 长度: " + messageDigest.length + " bytes"); + } + } + } + + signatureValue = encryptedDigest.getOctets(); + System.out.println("\n[步骤7] 使用 SM2 算法验签 signedAttrs DER 编码..."); + System.out.println(" DigestEncryptionAlgorithm OID: " + + ((ASN1ObjectIdentifier)digestEncryptionAlgorithm.getObjectAt(0)).getId()); + System.out.println(" 签名值长度: " + signatureValue.length + " bytes"); + System.out.println(" 签名值 (Hex): " + bytesToHex(signatureValue)); + System.out.println(" signedAttrs 数据类型: " + authAttrsData.getClass().getName()); + + String[] algNames = {"SM3withSM2", "SHA256withSM2", "SM2"}; + boolean valid = false; + for (String algName : algNames) { + try { + Signature signature = Signature.getInstance(algName, "BC"); + signature.initVerify(signerCert.getPublicKey()); + signature.update(signedAttrsEncoded); + valid = signature.verify(signatureValue); + System.out.println(" 算法 " + algName + " 验签结果: " + (valid ? "成功" : "失败")); + if (valid) break; + } catch (Exception e) { + System.out.println(" 算法 " + algName + " 失败: " + e.getMessage()); + } + } + + if (!valid) { + System.out.println("\n [调试] 尝试使用原始签名值格式验签..."); + try { + Signature signature = Signature.getInstance("SM3withSM2", "BC"); + signature.initVerify(signerCert.getPublicKey()); + signature.update(signedAttrsEncoded); + byte[] rawSig = new byte[64]; + if (signatureValue.length >= 65 && signatureValue[0] == 0x30) { + int offset = signatureValue[1] >= 0x81 ? (signatureValue[1] == 0x81 ? 3 : 2) : 2; + if (signatureValue.length >= offset + 32 + 2) { + System.arraycopy(signatureValue, offset + 1, rawSig, 0, 32); + System.arraycopy(signatureValue, offset + 33, rawSig, 32, 32); + valid = signature.verify(rawSig); + System.out.println(" 原始格式验签结果: " + (valid ? "成功" : "失败")); + } + } + } catch (Exception e) { + System.out.println(" 原始格式验签失败: " + e.getMessage()); + } + } + + return new SignedAndEnvelopedVerifyResult(valid, messageDigest, signerCert, + signerCert.getSubjectX500Principal().getName()); + } else if (signerInfoPrim instanceof ASN1TaggedObject) { + ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(signerInfoPrim); + System.out.println(" TaggedObject 标签: " + tagged.getTagNo()); + System.out.println(" 内部对象类型: " + tagged.getObject().getClass().getSimpleName()); + + ASN1Primitive inner = tagged.getObject(); + if (inner instanceof ASN1Sequence) { + ASN1Sequence signerInfoSeq = ASN1Sequence.getInstance(inner); + System.out.println(" SignerInfo 包含 " + signerInfoSeq.size() + " 个元素"); + + if (signerInfoSeq.size() >= 4) { + int signerIndex = 0; + ASN1Integer signerVersion = ASN1Integer.getInstance(signerInfoSeq.getObjectAt(signerIndex++).toASN1Primitive()); + System.out.println(" Signer 版本: " + signerVersion.getValue()); + + System.out.println(" SignerIdentifier 解析成功"); + + ASN1Sequence digestAlgorithm = ASN1Sequence.getInstance(signerInfoSeq.getObjectAt(signerIndex++).toASN1Primitive()); + System.out.println(" DigestAlgorithm 解析成功"); + + ASN1Set authenticatedAttributes = null; + System.out.println(" 检查 AuthenticatedAttributes (signedAttrs)..."); + + while (signerIndex < signerInfoSeq.size()) { + ASN1Encodable authAttrsObj = signerInfoSeq.getObjectAt(signerIndex); + ASN1Primitive authAttrsPrim = authAttrsObj.toASN1Primitive(); + System.out.println(" 检查元素 " + signerIndex + " 类型: " + authAttrsPrim.getClass().getName()); + + if (authAttrsPrim instanceof ASN1TaggedObject || authAttrsPrim.getClass().getName().contains("TaggedObject")) { + ASN1TaggedObject authTagged = ASN1TaggedObject.getInstance(authAttrsObj); + System.out.println(" 标签编号: " + authTagged.getTagNo()); + ASN1Primitive authInner = authTagged.getObject(); + System.out.println(" 内部对象类型: " + authInner.getClass().getName()); + if (authInner instanceof ASN1Set) { + authenticatedAttributes = ASN1Set.getInstance(authInner); + System.out.println(" AuthenticatedAttributes (tagged) 发现 " + authenticatedAttributes.size() + " 个属性"); + signerIndex++; + break; + } else if (authInner instanceof ASN1Sequence) { + authenticatedAttributes = ASN1Set.getInstance(authInner); + System.out.println(" AuthenticatedAttributes (tagged sequence) 发现 " + authenticatedAttributes.size() + " 个属性"); + signerIndex++; + break; + } + } else if (authAttrsPrim instanceof ASN1Set) { + authenticatedAttributes = ASN1Set.getInstance(authAttrsPrim); + System.out.println(" AuthenticatedAttributes 发现 " + authenticatedAttributes.size() + " 个属性"); + signerIndex++; + break; + } else if (authAttrsPrim instanceof ASN1Sequence) { + authenticatedAttributes = ASN1Set.getInstance(authAttrsPrim); + System.out.println(" AuthenticatedAttributes (sequence) 发现 " + authenticatedAttributes.size() + " 个属性"); + signerIndex++; + break; + } else { + System.out.println(" 不是 AuthenticatedAttributes 类型: " + authAttrsPrim.getClass().getName() + ",跳过"); + signerIndex++; + } + } + + if (signerIndex >= signerInfoSeq.size()) { + System.out.println(" AuthenticatedAttributes 为空,跳过"); + continue; + } + + ASN1Sequence digestEncryptionAlgorithm = ASN1Sequence.getInstance(signerInfoSeq.getObjectAt(signerIndex++).toASN1Primitive()); + System.out.println(" DigestEncryptionAlgorithm 解析成功"); + + if (signerIndex >= signerInfoSeq.size()) { + System.out.println(" EncryptedDigest 不存在"); + continue; + } + + ASN1Encodable sigElement = signerInfoSeq.getObjectAt(signerIndex); + ASN1Primitive sigPrim = sigElement.toASN1Primitive(); + System.out.println(" 签名元素类型: " + sigPrim.getClass().getSimpleName()); + + byte[] encryptedDigestBytes = null; + if (sigPrim instanceof ASN1OctetString) { + encryptedDigestBytes = ((ASN1OctetString) sigPrim).getOctets(); + } else if (sigPrim instanceof ASN1TaggedObject) { + ASN1TaggedObject sigTagged = ASN1TaggedObject.getInstance(sigPrim); + if (sigTagged.getObject() instanceof ASN1OctetString) { + encryptedDigestBytes = ((ASN1OctetString) sigTagged.getObject()).getOctets(); + } + } else if (sigPrim instanceof ASN1Sequence) { + ASN1Sequence sigSeq = ASN1Sequence.getInstance(sigPrim); + if (sigSeq.size() > 0) { + ASN1Encodable lastElem = sigSeq.getObjectAt(sigSeq.size() - 1); + if (lastElem instanceof ASN1OctetString) { + encryptedDigestBytes = ((ASN1OctetString) lastElem).getOctets(); + } + } + } + + if (encryptedDigestBytes == null) { + System.out.println(" 无法解析签名值"); + continue; + } + + System.out.println(" EncryptedDigest 长度: " + encryptedDigestBytes.length + " bytes"); + + if (authenticatedAttributes == null) { + System.out.println(" AuthenticatedAttributes 为空,跳过验签"); + continue; + } + + System.out.println("\n[步骤5] 提取待签名数据 (signedAttrs DER 编码)..."); + signedAttrsEncoded = authenticatedAttributes.getEncoded(ASN1Encoding.DER); + System.out.println(" signedAttrs DER 编码长度: " + signedAttrsEncoded.length + " bytes"); + System.out.println(" signedAttrs Hex: " + bytesToHex(signedAttrsEncoded)); + + System.out.println("\n[步骤6] 提取原文 Hash (message-digest 属性,仅打印不参与运算)..."); + byte[] messageDigest = null; + for (int j = 0; j < authenticatedAttributes.size(); j++) { + ASN1Sequence attrSeq = ASN1Sequence.getInstance(authenticatedAttributes.getObjectAt(j).toASN1Primitive()); + + if (attrSeq.size() < 2) { + continue; + } + + ASN1ObjectIdentifier attrType = ASN1ObjectIdentifier.getInstance(attrSeq.getObjectAt(0)); + + if (attrType.equals(CMSAttributes.messageDigest)) { + ASN1Encodable attrValues = attrSeq.getObjectAt(1); + ASN1Primitive attrValuesPrim = attrValues.toASN1Primitive(); + + if (attrValuesPrim instanceof ASN1Set) { + ASN1Set valuesSet = ASN1Set.getInstance(attrValuesPrim); + if (valuesSet.size() > 0) { + messageDigest = ASN1OctetString.getInstance(valuesSet.getObjectAt(0)).getOctets(); + System.out.println(" message-digest 值 (Hex): " + bytesToHex(messageDigest)); + System.out.println(" message-digest 长度: " + messageDigest.length + " bytes"); + } + } else if (attrValuesPrim instanceof ASN1OctetString) { + messageDigest = ((ASN1OctetString) attrValuesPrim).getOctets(); + System.out.println(" message-digest 值 (Hex): " + bytesToHex(messageDigest)); + System.out.println(" message-digest 长度: " + messageDigest.length + " bytes"); + } + } + } + + signatureValue = encryptedDigestBytes; + System.out.println("\n[步骤7] 使用 SM2 算法验签 signedAttrs DER 编码..."); + Signature signature = Signature.getInstance("SM3withSM2", "BC"); + signature.initVerify(signerCert.getPublicKey()); + signature.update(signedAttrsEncoded); + + System.out.println(" 签名值长度: " + signatureValue.length + " bytes"); + System.out.println(" 签名值 (Hex): " + bytesToHex(signatureValue)); + + boolean valid = signature.verify(signatureValue); + System.out.println(" 验签结果: " + (valid ? "成功" : "失败")); + + return new SignedAndEnvelopedVerifyResult(valid, messageDigest, signerCert, + signerCert.getSubjectX500Principal().getName()); + } + } + } else { + System.out.println(" 未知类型,跳过"); + } + } + } + } + + throw new Exception("未找到有效的签名信息"); + } + public static void main(String[] args) { if (args.length < 1) { System.out.println("用法: java CMSUtil "); @@ -137,13 +701,13 @@ public class CMSUtil { try { String cmsData = args[0]; - + String type = detectCMSType(cmsData); System.out.println("CMS类型: " + type); System.out.println(); boolean verified = false; - + if (type.equals("SignedData")) { verified = verifySignedData(cmsData); System.out.println("SignedData验签结果: " + verified); @@ -151,12 +715,29 @@ public class CMSUtil { System.out.println("这是一个数字信封(EnvelopedData),需要私钥解密后才能验证签名"); } else if (type.equals("SignedAndEnvelopedData")) { System.out.println("这是一个SignedAndEnvelopedData(同时签名和加密)"); - System.out.println("需要使用对应的私钥解密后才能验证签名"); + System.out.println(); + + SignedAndEnvelopedVerifyResult result = verifySignedAndEnvelopedData(cmsData); + System.out.println(); + System.out.println("========== 验签结果汇总 =========="); + System.out.println("验签结果: " + (result.signatureValid ? "成功" : "失败")); + System.out.println("签名者证书主题: " + result.signerSubjectDN); + if (result.messageDigest != null) { + System.out.println("原文Hash(SM3): " + bytesToHex(result.messageDigest)); + } } - + } catch (Exception e) { System.err.println("处理失败: " + e.getMessage()); e.printStackTrace(); } } + + public static String bytesToHex(byte[] data) { + StringBuilder sb = new StringBuilder(); + for (byte b : data) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } } \ No newline at end of file diff --git a/src/main/java/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil.java b/src/main/java/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil.java index 95e5e9c..230deda 100644 --- a/src/main/java/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil.java +++ b/src/main/java/com/sunyard/cisd/device/tool/SM2SignedEnvelopeUtil.java @@ -1,12 +1,9 @@ package com.sunyard.cisd.device.tool; -import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.cms.*; import org.bouncycastle.cms.*; -import org.bouncycastle.cms.jcajce.*; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.encoders.Hex; import javax.crypto.Cipher; @@ -84,30 +81,25 @@ public class SM2SignedEnvelopeUtil { } public static byte[] createSignedAndEnvelopedData(byte[] plaintext, PrivateKey signingKey, PublicKey encryptionPublicKey) throws Exception { - byte[] signature = signData(plaintext, signingKey); + byte[] sm3Digest = calculateSM3Digest(plaintext); - ByteArrayOutputStream signedContentBaos = new ByteArrayOutputStream(); - signedContentBaos.write(signature.length >> 24 & 0xFF); - signedContentBaos.write(signature.length >> 16 & 0xFF); - signedContentBaos.write(signature.length >> 8 & 0xFF); - signedContentBaos.write(signature.length & 0xFF); - signedContentBaos.write(signature); - signedContentBaos.write(plaintext); - byte[] signedContent = signedContentBaos.toByteArray(); + ASN1EncodableVector signedAttrsVector = new ASN1EncodableVector(); + Attribute messageDigestAttr = new Attribute(CMSAttributes.messageDigest, + new DERSet(new DEROctetString(sm3Digest))); + signedAttrsVector.add(messageDigestAttr); - byte[] encryptedContent = encryptData(signedContent, encryptionPublicKey); + Attribute contentTypeAttr = new Attribute(CMSAttributes.contentType, + new DERSet(CMSObjectIdentifiers.data)); + signedAttrsVector.add(contentTypeAttr); - return encryptedContent; - } + DERSet signedAttrs = new DERSet(signedAttrsVector); + byte[] signedAttrsEncoded = signedAttrs.getEncoded(ASN1Encoding.DER); - private static byte[] signData(byte[] data, PrivateKey privateKey) throws Exception { Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER); - signature.initSign(privateKey); - signature.update(data); - return signature.sign(); - } + signature.initSign(signingKey); + signature.update(signedAttrsEncoded); + byte[] signatureValue = signature.sign(); - private static byte[] encryptData(byte[] data, PublicKey publicKey) throws Exception { SecureRandom random = new SecureRandom(); byte[] iv = new byte[12]; random.nextBytes(iv); @@ -119,53 +111,127 @@ public class SM2SignedEnvelopeUtil { Cipher sm4Cipher = Cipher.getInstance(SM4_ALGORITHM, PROVIDER); GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); sm4Cipher.init(Cipher.ENCRYPT_MODE, sm4Key, gcmSpec); - byte[] encryptedData = sm4Cipher.doFinal(data); + byte[] encryptedData = sm4Cipher.doFinal(plaintext); Cipher ecCipher = Cipher.getInstance("ECIES", PROVIDER); - ecCipher.init(Cipher.ENCRYPT_MODE, publicKey); + ecCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey); byte[] encryptedKey = ecCipher.doFinal(sm4Key.getEncoded()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] keyLenBytes = new byte[]{ - (byte)(encryptedKey.length >> 8), - (byte)encryptedKey.length - }; - baos.write(keyLenBytes[0]); - baos.write(keyLenBytes[1]); + baos.write((byte)(encryptedKey.length >> 8)); + baos.write((byte)encryptedKey.length); baos.write(encryptedKey); baos.write(iv); baos.write(encryptedData); - return baos.toByteArray(); + byte[] encryptedContent = baos.toByteArray(); + + byte[] finalData = new byte[4 + signatureValue.length + encryptedContent.length]; + byte[] sigLen = new byte[]{ + (byte)(signatureValue.length >> 24), + (byte)(signatureValue.length >> 16), + (byte)(signatureValue.length >> 8), + (byte)signatureValue.length + }; + System.arraycopy(sigLen, 0, finalData, 0, 4); + System.arraycopy(signatureValue, 0, finalData, 4, signatureValue.length); + System.arraycopy(encryptedContent, 0, finalData, 4 + signatureValue.length, encryptedContent.length); + + return finalData; + } + + private static byte[] calculateSM3Digest(byte[] data) throws Exception { + MessageDigest digest = MessageDigest.getInstance("SM3", PROVIDER); + return digest.digest(data); + } + + public static class VerifyResult { + public final boolean signatureValid; + public final byte[] messageDigest; + public final String digestAlgorithm; + + public VerifyResult(boolean signatureValid, byte[] messageDigest, String digestAlgorithm) { + this.signatureValid = signatureValid; + this.messageDigest = messageDigest; + this.digestAlgorithm = digestAlgorithm; + } + } + + public static boolean verifySignatureWithAttrs(byte[] signatureValue, byte[] signedAttrsEncoded, PublicKey publicKey) throws Exception { + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER); + signature.initVerify(publicKey); + signature.update(signedAttrsEncoded); + return signature.verify(signatureValue); + } + + public static byte[] buildSignedAttrs(byte[] messageDigest) { + ASN1EncodableVector signedAttrsVector = new ASN1EncodableVector(); + + Attribute contentTypeAttr = new Attribute(CMSAttributes.contentType, + new DERSet(CMSObjectIdentifiers.data)); + signedAttrsVector.add(contentTypeAttr); + + Attribute messageDigestAttr = new Attribute(CMSAttributes.messageDigest, + new DERSet(new DEROctetString(messageDigest))); + signedAttrsVector.add(messageDigestAttr); + + DERSet signedAttrs = new DERSet(signedAttrsVector); + try { + return signedAttrs.getEncoded(ASN1Encoding.DER); + } catch (Exception e) { + return null; + } + } + + public static byte[] extractMessageDigestFromAttrs(byte[] signedAttrsEncoded) throws Exception { + ASN1InputStream asn1InputStream = new ASN1InputStream(signedAttrsEncoded); + ASN1Set signedAttrs = ASN1Set.getInstance(asn1InputStream.readObject()); + + for (int i = 0; i < signedAttrs.size(); i++) { + Attribute attr = Attribute.getInstance(signedAttrs.getObjectAt(i)); + if (attr.getAttrType().equals(CMSAttributes.messageDigest)) { + return ASN1OctetString.getInstance( + attr.getAttrValues().getObjectAt(0)).getOctets(); + } + } + return null; } public static class DecryptedResult { public final byte[] plaintext; public final byte[] signature; public final boolean signatureValid; + public final byte[] messageDigest; - public DecryptedResult(byte[] plaintext, byte[] signature, boolean signatureValid) { + public DecryptedResult(byte[] plaintext, byte[] signature, boolean signatureValid, byte[] messageDigest) { this.plaintext = plaintext; this.signature = signature; this.signatureValid = signatureValid; + this.messageDigest = messageDigest; } } public static DecryptedResult openAndVerifySignedEnvelopedData(byte[] envelopeData, PrivateKey decryptionKey, PublicKey signingPublicKey) throws Exception { - int offset = 0; - int encryptedKeyLen = ((envelopeData[offset] & 0xFF) << 8) | (envelopeData[offset + 1] & 0xFF); - offset += 2; + int sigLen = ((envelopeData[0] & 0xFF) << 24) | + ((envelopeData[1] & 0xFF) << 16) | + ((envelopeData[2] & 0xFF) << 8) | + (envelopeData[3] & 0xFF); - byte[] encryptedKey = new byte[encryptedKeyLen]; - System.arraycopy(envelopeData, offset, encryptedKey, 0, encryptedKeyLen); - offset += encryptedKeyLen; + byte[] signatureValue = new byte[sigLen]; + System.arraycopy(envelopeData, 4, signatureValue, 0, sigLen); + + byte[] encryptedContent = new byte[envelopeData.length - 4 - sigLen]; + System.arraycopy(envelopeData, 4 + sigLen, encryptedContent, 0, encryptedContent.length); + + int keyLen = ((encryptedContent[0] & 0xFF) << 8) | (encryptedContent[1] & 0xFF); + byte[] encryptedKey = new byte[keyLen]; + System.arraycopy(encryptedContent, 2, encryptedKey, 0, keyLen); byte[] iv = new byte[12]; - System.arraycopy(envelopeData, offset, iv, 0, 12); - offset += 12; + System.arraycopy(encryptedContent, 2 + keyLen, iv, 0, 12); - byte[] encryptedData = new byte[envelopeData.length - offset]; - System.arraycopy(envelopeData, offset, encryptedData, 0, encryptedData.length); + byte[] encryptedData = new byte[encryptedContent.length - 2 - keyLen - 12]; + System.arraycopy(encryptedContent, 2 + keyLen + 12, encryptedData, 0, encryptedData.length); Cipher ecCipher = Cipher.getInstance("ECIES", PROVIDER); ecCipher.init(Cipher.DECRYPT_MODE, decryptionKey); @@ -176,29 +242,14 @@ public class SM2SignedEnvelopeUtil { Cipher sm4Cipher = Cipher.getInstance(SM4_ALGORITHM, PROVIDER); GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); sm4Cipher.init(Cipher.DECRYPT_MODE, sm4Key, gcmSpec); - byte[] signedContent = sm4Cipher.doFinal(encryptedData); + byte[] plaintext = sm4Cipher.doFinal(encryptedData); - int signatureLength = ((signedContent[0] & 0xFF) << 24) | - ((signedContent[1] & 0xFF) << 16) | - ((signedContent[2] & 0xFF) << 8) | - (signedContent[3] & 0xFF); + byte[] messageDigest = calculateSM3Digest(plaintext); + byte[] signedAttrsEncoded = buildSignedAttrs(messageDigest); - byte[] signature = new byte[signatureLength]; - byte[] plaintext = new byte[signedContent.length - 4 - signatureLength]; + boolean signatureValid = verifySignatureWithAttrs(signatureValue, signedAttrsEncoded, signingPublicKey); - System.arraycopy(signedContent, 4, signature, 0, signatureLength); - System.arraycopy(signedContent, 4 + signatureLength, plaintext, 0, plaintext.length); - - boolean signatureValid = verifySignature(plaintext, signature, signingPublicKey); - - return new DecryptedResult(plaintext, signature, signatureValid); - } - - private static boolean verifySignature(byte[] data, byte[] signature, PublicKey publicKey) throws Exception { - Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER); - sig.initVerify(publicKey); - sig.update(data); - return sig.verify(signature); + return new DecryptedResult(plaintext, signatureValue, signatureValid, messageDigest); } public static String toBase64(byte[] data) { @@ -233,26 +284,42 @@ public class SM2SignedEnvelopeUtil { System.out.println("原文: " + testData); System.out.println("原文长度: " + plaintext.length + " bytes"); + System.out.println("原文SM3哈希: " + bytesToHex(calculateSM3Digest(plaintext))); System.out.println(); System.out.println("创建带签名的数字信封..."); byte[] envelope = createSignedAndEnvelopedData(plaintext, signingKeyPair.privateKey, encryptionKeyPair.publicKey); System.out.println("信封已创建,长度: " + envelope.length + " bytes"); - System.out.println("信封Base64: " + toBase64(envelope).substring(0, 80) + "..."); System.out.println(); - System.out.println("打开并验证数字信封..."); + System.out.println("=== 不解密验证签名 (使用 signedAttrs) ==="); + byte[] messageDigest = calculateSM3Digest(plaintext); + byte[] signedAttrsEncoded = buildSignedAttrs(messageDigest); + + int sigLen = ((envelope[0] & 0xFF) << 24) | + ((envelope[1] & 0xFF) << 16) | + ((envelope[2] & 0xFF) << 8) | + (envelope[3] & 0xFF); + byte[] signatureValue = new byte[sigLen]; + System.arraycopy(envelope, 4, signatureValue, 0, sigLen); + + boolean sigValid = verifySignatureWithAttrs(signatureValue, signedAttrsEncoded, signingKeyPair.publicKey); + System.out.println("签名有效: " + sigValid); + System.out.println("提取的消息摘要: " + bytesToHex(extractMessageDigestFromAttrs(signedAttrsEncoded))); + System.out.println(); + + System.out.println("=== 解密并验证 ==="); DecryptedResult result = openAndVerifySignedEnvelopedData(envelope, encryptionKeyPair.privateKey, signingKeyPair.publicKey); System.out.println("解密后原文: " + new String(result.plaintext, "UTF-8")); System.out.println("签名有效: " + result.signatureValid); - System.out.println("签名: " + bytesToHex(result.signature)); + System.out.println("消息摘要: " + bytesToHex(result.messageDigest)); System.out.println(); - if (result.signatureValid) { - System.out.println("=== 验签成功 ==="); + if (sigValid && result.signatureValid) { + System.out.println("=== 全部验证通过 ==="); } else { - System.out.println("=== 验签失败 ==="); + System.out.println("=== 验证失败 ==="); } } } \ No newline at end of file diff --git a/src/test/java/com/sunyard/cisd/CMSSignatureTest.java b/src/test/java/com/sunyard/cisd/CMSSignatureTest.java index 863d34a..453b008 100644 --- a/src/test/java/com/sunyard/cisd/CMSSignatureTest.java +++ b/src/test/java/com/sunyard/cisd/CMSSignatureTest.java @@ -1,51 +1,193 @@ package com.sunyard.cisd; import com.sunyard.cisd.device.tool.CMSUtil; +import com.sunyard.cisd.device.tool.CMSUtil.SignedAndEnvelopedVerifyResult; +import com.sunyard.cisd.device.tool.SM2SignedEnvelopeUtil; +import com.sunyard.cisd.device.tool.SM2SignedEnvelopeUtil.DecryptedResult; +import com.sunyard.cisd.device.tool.SM2SignedEnvelopeUtil.SM2KeyPair; import com.sunyard.cisd.device.tool.SM3Util; public class CMSSignatureTest { public static void main(String[] args) { try { - System.out.println("=== CMS 签名验签测试 ==="); + System.out.println("=== CMS 签名验签测试套件 ==="); System.out.println(); - String cbcCmsData = SM3Util.readFile("d:/workbench/sectool/cbc.cms").trim(); - String icbcCmsData = SM3Util.readFile("d:/workbench/sectool/icbc.cms").trim(); +// testSM2KeyPairGeneration(); +// testCreateAndVerifyEnvelope(); + testCMSFilesVerification(); - System.out.println("--- 验证 cbc.cms ---"); - String cbcType = CMSUtil.detectCMSType(cbcCmsData); - System.out.println("cbc.cms CMS类型: " + cbcType); - - boolean cbcVerified = false; - if ("SignedData".equals(cbcType)) { - cbcVerified = CMSUtil.verifySignedData(cbcCmsData); - System.out.println("cbc.cms 验签结果: " + (cbcVerified ? "成功" : "失败")); - } else if ("SignedAndEnvelopedData".equals(cbcType)) { - System.out.println("cbc.cms 是SignedAndEnvelopedData类型(同时签名和加密)"); - System.out.println("需要使用对应的私钥解密后才能验证签名"); - } - System.out.println(); - - System.out.println("--- 验证 icbc.cms ---"); - String icbcType = CMSUtil.detectCMSType(icbcCmsData); - System.out.println("icbc.cms CMS类型: " + icbcType); - - boolean icbcVerified = false; - if ("SignedData".equals(icbcType)) { - icbcVerified = CMSUtil.verifySignedData(icbcCmsData); - System.out.println("icbc.cms 验签结果: " + (icbcVerified ? "成功" : "失败")); - } else if ("SignedAndEnvelopedData".equals(icbcType)) { - System.out.println("icbc.cms 是SignedAndEnvelopedData类型(同时签名和加密)"); - System.out.println("需要使用对应的私钥解密后才能验证签名"); - } - System.out.println(); - - System.out.println("=== 测试完成 ==="); + System.out.println("=== 所有测试完成 ==="); } catch (Exception e) { - System.err.println("测试失败: " + e.getMessage()); + System.err.println("\n测试失败: " + e.getMessage()); e.printStackTrace(); } } + + private static void testSM2KeyPairGeneration() throws Exception { + System.out.println("=== 1. SM2 密钥对生成测试 ==="); + System.out.println(); + + SM2KeyPair keyPair = SM2SignedEnvelopeUtil.generateKeyPair(); + + System.out.println("密钥对生成成功"); + System.out.println("私钥长度: " + keyPair.privateKeyBytes.length + " bytes"); + System.out.println("公钥长度: " + keyPair.publicKeyBytes.length + " bytes"); + System.out.println("私钥(Base64): " + truncate(keyPair.getPrivateKeyBase64(), 60)); + System.out.println("公钥(Base64): " + truncate(keyPair.getPublicKeyBase64(), 60)); + System.out.println(); + } + + private static void testCreateAndVerifyEnvelope() throws Exception { + System.out.println("=== 2. 数字信封创建与验证测试 ==="); + System.out.println(); + + SM2KeyPair signingKeyPair = SM2SignedEnvelopeUtil.generateKeyPair(); + SM2KeyPair encryptionKeyPair = SM2SignedEnvelopeUtil.generateKeyPair(); + + String testData = "这是测试数据 - Hello World! 你好世界!"; + byte[] plaintext = testData.getBytes("UTF-8"); + + System.out.println("测试数据: " + testData); + System.out.println("数据长度: " + plaintext.length + " bytes"); + System.out.println("数据SM3哈希: " + SM2SignedEnvelopeUtil.bytesToHex( + SM3Util.sm3Digest(plaintext))); + System.out.println(); + + System.out.println("创建带签名的数字信封..."); + byte[] envelope = SM2SignedEnvelopeUtil.createSignedAndEnvelopedData( + plaintext, signingKeyPair.privateKey, encryptionKeyPair.publicKey); + System.out.println("信封创建成功,长度: " + envelope.length + " bytes"); + System.out.println("信封(Base64): " + truncate(SM2SignedEnvelopeUtil.toBase64(envelope), 80)); + System.out.println(); + + System.out.println("--- 不解密验证签名 (使用 signedAttrs) ---"); + byte[] messageDigest = SM3Util.sm3Digest(plaintext); + byte[] signedAttrsEncoded = SM2SignedEnvelopeUtil.buildSignedAttrs(messageDigest); + + int sigLen = ((envelope[0] & 0xFF) << 24) | + ((envelope[1] & 0xFF) << 16) | + ((envelope[2] & 0xFF) << 8) | + (envelope[3] & 0xFF); + byte[] signatureValue = new byte[sigLen]; + System.arraycopy(envelope, 4, signatureValue, 0, sigLen); + + boolean sigValid = SM2SignedEnvelopeUtil.verifySignatureWithAttrs( + signatureValue, signedAttrsEncoded, signingKeyPair.publicKey); + System.out.println("签名有效: " + (sigValid ? "是" : "否")); + + byte[] extractedDigest = SM2SignedEnvelopeUtil.extractMessageDigestFromAttrs(signedAttrsEncoded); + System.out.println("提取的消息摘要: " + SM2SignedEnvelopeUtil.bytesToHex(extractedDigest)); + System.out.println(); + + System.out.println("--- 解密并验证 ---"); + DecryptedResult result = SM2SignedEnvelopeUtil.openAndVerifySignedEnvelopedData( + envelope, encryptionKeyPair.privateKey, signingKeyPair.publicKey); + + System.out.println("解密后原文: " + new String(result.plaintext, "UTF-8")); + System.out.println("签名验证: " + (result.signatureValid ? "通过" : "失败")); + System.out.println("消息摘要: " + SM2SignedEnvelopeUtil.bytesToHex(result.messageDigest)); + + boolean integrityOk = java.util.Arrays.equals(result.messageDigest, messageDigest); + System.out.println("数据完整性: " + (integrityOk ? "通过" : "失败")); + System.out.println(); + + if (sigValid && result.signatureValid && integrityOk) { + System.out.println("✓ 数字信封测试全部通过"); + } else { + System.out.println("✗ 数字信封测试失败"); + } + System.out.println(); + } + + private static void testCMSFilesVerification() throws Exception { + System.out.println("=== 3. CMS 文件验证测试 ==="); + System.out.println(); + + String cbcCmsData = null; + String icbcCmsData = null; + + try { + cbcCmsData = SM3Util.readFile("d:/workbench/sectool/cbc.cms").trim(); + System.out.println("已加载 cbc.cms"); + } catch (Exception e) { + System.out.println("未找到 cbc.cms: " + e.getMessage()); + } + + try { + icbcCmsData = SM3Util.readFile("d:/workbench/sectool/icbc.cms").trim(); + System.out.println("已加载 icbc.cms"); + } catch (Exception e) { + System.out.println("未找到 icbc.cms: " + e.getMessage()); + } + System.out.println(); + + if (cbcCmsData != null) { + System.out.println("--- 验证 cbc.cms ---"); + try { + String cbcType = CMSUtil.detectCMSType(cbcCmsData); + System.out.println("CMS类型: " + cbcType); + + if ("SignedData".equals(cbcType)) { + boolean verified = CMSUtil.verifySignedData(cbcCmsData); + System.out.println("验签结果: " + (verified ? "成功" : "失败")); + } else if ("SignedAndEnvelopedData".equals(cbcType)) { + System.out.println("类型: SignedAndEnvelopedData(同时签名和加密)"); + System.out.println("说明: 数字信封内包含签名证书,可直接提取公钥验签"); + System.out.println(); + + SignedAndEnvelopedVerifyResult result = CMSUtil.verifySignedAndEnvelopedData(cbcCmsData); + System.out.println("验签结果: " + (result.signatureValid ? "成功" : "失败")); + System.out.println("签名者证书主题: " + result.signerSubjectDN); + if (result.messageDigest != null) { + System.out.println("消息摘要(SM3): " + CMSUtil.bytesToHex(result.messageDigest)); + } + } else { + System.out.println("未知类型: " + cbcType); + } + } catch (Exception e) { + System.err.println("验证失败: " + e.getMessage()); + e.printStackTrace(); + } + System.out.println(); + } + + if (icbcCmsData != null) { + System.out.println("--- 验证 icbc.cms ---"); + try { + String icbcType = CMSUtil.detectCMSType(icbcCmsData); + System.out.println("CMS类型: " + icbcType); + + if ("SignedData".equals(icbcType)) { + boolean verified = CMSUtil.verifySignedData(icbcCmsData); + System.out.println("验签结果: " + (verified ? "成功" : "失败")); + } else if ("SignedAndEnvelopedData".equals(icbcType)) { + System.out.println("类型: SignedAndEnvelopedData(同时签名和加密)"); + System.out.println("说明: 数字信封内包含签名证书,可直接提取公钥验签"); + System.out.println(); + + SignedAndEnvelopedVerifyResult result = CMSUtil.verifySignedAndEnvelopedData(icbcCmsData); + System.out.println("验签结果: " + (result.signatureValid ? "成功" : "失败")); + System.out.println("签名者证书主题: " + result.signerSubjectDN); + if (result.messageDigest != null) { + System.out.println("消息摘要(SM3): " + CMSUtil.bytesToHex(result.messageDigest)); + } + } else { + System.out.println("未知类型: " + icbcType); + } + } catch (Exception e) { + System.err.println("验证失败: " + e.getMessage()); + e.printStackTrace(); + } + System.out.println(); + } + } + + private static String truncate(String str, int maxLength) { + if (str == null) return ""; + if (str.length() <= maxLength) return str; + return str.substring(0, maxLength) + "..."; + } } \ No newline at end of file