农行验签不过
This commit is contained in:
parent
970c631e52
commit
72eff5f4a4
27
.gitignore
vendored
27
.gitignore
vendored
@ -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/
|
||||
|
||||
@ -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 <base64_cms_data>");
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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("=== 验证失败 ===");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) + "...";
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user