325 lines
13 KiB
Java
325 lines
13 KiB
Java
package com.sunyard.cisd.device.tool;
|
|
|
|
import org.bouncycastle.asn1.*;
|
|
import org.bouncycastle.asn1.cms.*;
|
|
import org.bouncycastle.cms.*;
|
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
import org.bouncycastle.util.encoders.Hex;
|
|
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.KeyGenerator;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.spec.GCMParameterSpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.security.*;
|
|
import java.security.spec.ECGenParameterSpec;
|
|
import java.security.spec.PKCS8EncodedKeySpec;
|
|
import java.security.spec.X509EncodedKeySpec;
|
|
import java.util.*;
|
|
|
|
public class SM2SignedEnvelopeUtil {
|
|
|
|
private static final String PROVIDER = "BC";
|
|
private static final String CURVE_NAME = "sm2p256v1";
|
|
private static final String KEY_ALGORITHM = "EC";
|
|
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
|
|
private static final String SM4_ALGORITHM = "SM4/GCM/NoPadding";
|
|
|
|
static {
|
|
Security.addProvider(new BouncyCastleProvider());
|
|
}
|
|
|
|
public static class SM2KeyPair {
|
|
public final PrivateKey privateKey;
|
|
public final PublicKey publicKey;
|
|
public final byte[] privateKeyBytes;
|
|
public final byte[] publicKeyBytes;
|
|
|
|
public SM2KeyPair(PrivateKey privateKey, PublicKey publicKey) {
|
|
this.privateKey = privateKey;
|
|
this.publicKey = publicKey;
|
|
this.privateKeyBytes = privateKey.getEncoded();
|
|
this.publicKeyBytes = publicKey.getEncoded();
|
|
}
|
|
|
|
public String getPrivateKeyBase64() {
|
|
return Base64.getEncoder().encodeToString(privateKeyBytes);
|
|
}
|
|
|
|
public String getPublicKeyBase64() {
|
|
return Base64.getEncoder().encodeToString(publicKeyBytes);
|
|
}
|
|
|
|
public String getPrivateKeyHex() {
|
|
return Hex.toHexString(privateKeyBytes);
|
|
}
|
|
|
|
public String getPublicKeyHex() {
|
|
return Hex.toHexString(publicKeyBytes);
|
|
}
|
|
}
|
|
|
|
public static SM2KeyPair generateKeyPair() throws Exception {
|
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM, PROVIDER);
|
|
ECGenParameterSpec ecSpec = new ECGenParameterSpec(CURVE_NAME);
|
|
keyPairGenerator.initialize(ecSpec, new SecureRandom());
|
|
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
|
return new SM2KeyPair(keyPair.getPrivate(), keyPair.getPublic());
|
|
}
|
|
|
|
public static PrivateKey loadPrivateKey(byte[] privateKeyBytes) throws Exception {
|
|
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, PROVIDER);
|
|
return keyFactory.generatePrivate(keySpec);
|
|
}
|
|
|
|
public static PublicKey loadPublicKey(byte[] publicKeyBytes) throws Exception {
|
|
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
|
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, PROVIDER);
|
|
return keyFactory.generatePublic(keySpec);
|
|
}
|
|
|
|
public static byte[] createSignedAndEnvelopedData(byte[] plaintext, PrivateKey signingKey, PublicKey encryptionPublicKey) throws Exception {
|
|
byte[] sm3Digest = calculateSM3Digest(plaintext);
|
|
|
|
ASN1EncodableVector signedAttrsVector = new ASN1EncodableVector();
|
|
Attribute messageDigestAttr = new Attribute(CMSAttributes.messageDigest,
|
|
new DERSet(new DEROctetString(sm3Digest)));
|
|
signedAttrsVector.add(messageDigestAttr);
|
|
|
|
Attribute contentTypeAttr = new Attribute(CMSAttributes.contentType,
|
|
new DERSet(CMSObjectIdentifiers.data));
|
|
signedAttrsVector.add(contentTypeAttr);
|
|
|
|
DERSet signedAttrs = new DERSet(signedAttrsVector);
|
|
byte[] signedAttrsEncoded = signedAttrs.getEncoded(ASN1Encoding.DER);
|
|
|
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
|
|
signature.initSign(signingKey);
|
|
signature.update(signedAttrsEncoded);
|
|
byte[] signatureValue = signature.sign();
|
|
|
|
SecureRandom random = new SecureRandom();
|
|
byte[] iv = new byte[12];
|
|
random.nextBytes(iv);
|
|
|
|
KeyGenerator keyGen = KeyGenerator.getInstance("SM4", PROVIDER);
|
|
keyGen.init(128, random);
|
|
SecretKey sm4Key = keyGen.generateKey();
|
|
|
|
Cipher sm4Cipher = Cipher.getInstance(SM4_ALGORITHM, PROVIDER);
|
|
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
|
|
sm4Cipher.init(Cipher.ENCRYPT_MODE, sm4Key, gcmSpec);
|
|
byte[] encryptedData = sm4Cipher.doFinal(plaintext);
|
|
|
|
Cipher ecCipher = Cipher.getInstance("ECIES", PROVIDER);
|
|
ecCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey);
|
|
byte[] encryptedKey = ecCipher.doFinal(sm4Key.getEncoded());
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
baos.write((byte)(encryptedKey.length >> 8));
|
|
baos.write((byte)encryptedKey.length);
|
|
baos.write(encryptedKey);
|
|
baos.write(iv);
|
|
baos.write(encryptedData);
|
|
|
|
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, 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 sigLen = ((envelopeData[0] & 0xFF) << 24) |
|
|
((envelopeData[1] & 0xFF) << 16) |
|
|
((envelopeData[2] & 0xFF) << 8) |
|
|
(envelopeData[3] & 0xFF);
|
|
|
|
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(encryptedContent, 2 + keyLen, iv, 0, 12);
|
|
|
|
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);
|
|
byte[] sm4KeyBytes = ecCipher.doFinal(encryptedKey);
|
|
|
|
SecretKeySpec sm4Key = new SecretKeySpec(sm4KeyBytes, "SM4");
|
|
|
|
Cipher sm4Cipher = Cipher.getInstance(SM4_ALGORITHM, PROVIDER);
|
|
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
|
|
sm4Cipher.init(Cipher.DECRYPT_MODE, sm4Key, gcmSpec);
|
|
byte[] plaintext = sm4Cipher.doFinal(encryptedData);
|
|
|
|
byte[] messageDigest = calculateSM3Digest(plaintext);
|
|
byte[] signedAttrsEncoded = buildSignedAttrs(messageDigest);
|
|
|
|
boolean signatureValid = verifySignatureWithAttrs(signatureValue, signedAttrsEncoded, signingPublicKey);
|
|
|
|
return new DecryptedResult(plaintext, signatureValue, signatureValid, messageDigest);
|
|
}
|
|
|
|
public static String toBase64(byte[] data) {
|
|
return Base64.getEncoder().encodeToString(data);
|
|
}
|
|
|
|
public static byte[] fromBase64(String base64) {
|
|
return Base64.getDecoder().decode(base64);
|
|
}
|
|
|
|
public static String bytesToHex(byte[] data) {
|
|
return Hex.toHexString(data);
|
|
}
|
|
|
|
public static byte[] hexToBytes(String hex) {
|
|
return Hex.decode(hex);
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
System.out.println("=== SM2 带签名数字信封测试 ===");
|
|
System.out.println();
|
|
|
|
SM2KeyPair signingKeyPair = generateKeyPair();
|
|
SM2KeyPair encryptionKeyPair = generateKeyPair();
|
|
|
|
System.out.println("签名密钥对已生成");
|
|
System.out.println("加密密钥对已生成");
|
|
System.out.println();
|
|
|
|
String testData = "这是测试数据 - Hello World! 你好世界!";
|
|
byte[] plaintext = testData.getBytes("UTF-8");
|
|
|
|
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();
|
|
|
|
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.messageDigest));
|
|
System.out.println();
|
|
|
|
if (sigValid && result.signatureValid) {
|
|
System.out.println("=== 全部验证通过 ===");
|
|
} else {
|
|
System.out.println("=== 验证失败 ===");
|
|
}
|
|
}
|
|
} |