整理代码

This commit is contained in:
cheney 2026-06-23 11:29:46 +08:00
parent 823c69c72b
commit 0107736416
6 changed files with 361 additions and 158 deletions

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# sectool
`sectool` 当前统一入口为 `com.sunyard.cisd.device.tool.Main``pom.xml` 已配置打包后的 `Main-Class` 指向该类。
## 支持功能
- `genkey`:生成 SM2 密钥对,输出 Base64 编码的 PKCS#8 私钥和 X.509 公钥。
- `env-create`:生成普通 SM2 数字信封。
- `env-open`:解密普通 SM2 数字信封。
- `signed-env-create`:生成带签名的 SM2 数字信封。
- `signed-env-open`:解密并验证带签名的 SM2 数字信封。
## 使用方式
```bash
java -jar target/sectool-1.0.3-jar-with-dependencies.jar genkey
java -jar target/sectool-1.0.3-jar-with-dependencies.jar env-create <publicKeyBase64> <text|@file>
java -jar target/sectool-1.0.3-jar-with-dependencies.jar env-open <privateKeyBase64> <envelopeBase64|@file>
java -jar target/sectool-1.0.3-jar-with-dependencies.jar signed-env-create <signPrivateKeyBase64> <encryptPublicKeyBase64> <text|@file>
java -jar target/sectool-1.0.3-jar-with-dependencies.jar signed-env-open <decryptPrivateKeyBase64> <signPublicKeyBase64> <envelopeBase64|@file>
```
说明:`@file` 表示从 UTF-8 文件读取输入内容;命令输出的数字信封均为 Base64。
## 测试用例
- `1-1`:普通 SM2 数字信封生成和解密。
- `1-2`:带签名 SM2 数字信封生成、解密和验签。

View File

@ -100,31 +100,4 @@ public class CMSSignatureUtil {
return (X509Certificate) cf.generateCertificate(new java.io.ByteArrayInputStream(certHolder.getEncoded()));
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("用法: java CMSSignatureUtil <base64_cms_data> [base64_certificate]");
return;
}
try {
String cmsData = args[0];
if (args.length >= 2) {
String certBase64 = args[1];
X509Certificate cert = loadCertificateFromBase64(certBase64);
boolean result = verifyCMSSignatureWithCertificate(cmsData, cert);
System.out.println("使用指定证书验签结果: " + result);
} else {
boolean result = verifyCMSSignature(cmsData);
System.out.println("使用CMS内置证书验签结果: " + result);
}
byte[] content = extractContent(cmsData);
System.out.println("提取的内容长度: " + content.length + " bytes");
} catch (Exception e) {
System.err.println("验签失败: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@ -819,45 +819,6 @@ public class CMSUtil {
return false;
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("用法: java CMSUtil <base64_cms_data>");
return;
}
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);
} else if (type.equals("EnvelopedData")) {
System.out.println("这是一个数字信封(EnvelopedData),需要私钥解密后才能验证签名");
} else if (type.equals("SignedAndEnvelopedData")) {
System.out.println("这是一个SignedAndEnvelopedData同时签名和加密");
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();

View File

@ -0,0 +1,162 @@
package com.sunyard.cisd.device.tool;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
public class Main {
/**
* 统一命令行入口
*
* @param args 命令行参数第一位为子命令
* @return 无返回值
* @throws Exception 参数错误密钥读取失败或加解密失败时抛出
*/
public static void main(String[] args) throws Exception {
if (args.length == 0) {
printUsage();
return;
}
String command = args[0];
if ("genkey".equals(command)) {
generateKeyPair();
} else if ("env-create".equals(command)) {
createEnvelope(args);
} else if ("env-open".equals(command)) {
openEnvelope(args);
} else if ("signed-env-create".equals(command)) {
createSignedEnvelope(args);
} else if ("signed-env-open".equals(command)) {
openSignedEnvelope(args);
} else {
printUsage();
throw new IllegalArgumentException("未知命令: " + command);
}
}
/**
* 生成 SM2 密钥对并输出 Base64
*
* @param 无参数
* @return 无返回值
* @throws Exception 生成密钥失败时抛出
*/
private static void generateKeyPair() throws Exception {
SM2SignedEnvelopeUtil.SM2KeyPair keyPair = SM2SignedEnvelopeUtil.generateKeyPair();
System.out.println("privateKeyBase64=" + keyPair.getPrivateKeyBase64());
System.out.println("publicKeyBase64=" + keyPair.getPublicKeyBase64());
}
/**
* 创建普通 SM2 数字信封
*
* @param args 命令行参数格式为 env-create <publicKeyBase64> <text|@file>
* @return 无返回值标准输出为信封 Base64
* @throws Exception 参数错误密钥解析失败或加密失败时抛出
*/
private static void createEnvelope(String[] args) throws Exception {
requireArgs(args, 3, "env-create <publicKeyBase64> <text|@file>");
PublicKey publicKey = SM2SignedEnvelopeUtil.loadPublicKey(Base64.getDecoder().decode(args[1]));
byte[] plaintext = readTextArg(args[2]);
byte[] envelope = SM2SignedEnvelopeUtil.createEnvelopedData(plaintext, publicKey);
System.out.println(Base64.getEncoder().encodeToString(envelope));
}
/**
* 解密普通 SM2 数字信封
*
* @param args 命令行参数格式为 env-open <privateKeyBase64> <envelopeBase64|@file>
* @return 无返回值标准输出为 UTF-8 原文
* @throws Exception 参数错误密钥解析失败或解密失败时抛出
*/
private static void openEnvelope(String[] args) throws Exception {
requireArgs(args, 3, "env-open <privateKeyBase64> <envelopeBase64|@file>");
PrivateKey privateKey = SM2SignedEnvelopeUtil.loadPrivateKey(Base64.getDecoder().decode(args[1]));
byte[] envelope = Base64.getDecoder().decode(new String(readTextArg(args[2]), StandardCharsets.UTF_8).trim());
byte[] plaintext = SM2SignedEnvelopeUtil.openEnvelopedData(envelope, privateKey);
System.out.println(new String(plaintext, StandardCharsets.UTF_8));
}
/**
* 创建带签名的 SM2 数字信封
*
* @param args 命令行参数格式为 signed-env-create <signPrivateKeyBase64> <encryptPublicKeyBase64> <text|@file>
* @return 无返回值标准输出为信封 Base64
* @throws Exception 参数错误密钥解析失败签名或加密失败时抛出
*/
private static void createSignedEnvelope(String[] args) throws Exception {
requireArgs(args, 4, "signed-env-create <signPrivateKeyBase64> <encryptPublicKeyBase64> <text|@file>");
PrivateKey signPrivateKey = SM2SignedEnvelopeUtil.loadPrivateKey(Base64.getDecoder().decode(args[1]));
PublicKey encryptPublicKey = SM2SignedEnvelopeUtil.loadPublicKey(Base64.getDecoder().decode(args[2]));
byte[] plaintext = readTextArg(args[3]);
byte[] envelope = SM2SignedEnvelopeUtil.createSignedAndEnvelopedData(plaintext, signPrivateKey, encryptPublicKey);
System.out.println(Base64.getEncoder().encodeToString(envelope));
}
/**
* 解密并验签带签名的 SM2 数字信封
*
* @param args 命令行参数格式为 signed-env-open <decryptPrivateKeyBase64> <signPublicKeyBase64> <envelopeBase64|@file>
* @return 无返回值标准输出为验签结果和 UTF-8 原文
* @throws Exception 参数错误密钥解析失败解密或验签失败时抛出
*/
private static void openSignedEnvelope(String[] args) throws Exception {
requireArgs(args, 4, "signed-env-open <decryptPrivateKeyBase64> <signPublicKeyBase64> <envelopeBase64|@file>");
PrivateKey decryptPrivateKey = SM2SignedEnvelopeUtil.loadPrivateKey(Base64.getDecoder().decode(args[1]));
PublicKey signPublicKey = SM2SignedEnvelopeUtil.loadPublicKey(Base64.getDecoder().decode(args[2]));
byte[] envelope = Base64.getDecoder().decode(new String(readTextArg(args[3]), StandardCharsets.UTF_8).trim());
SM2SignedEnvelopeUtil.DecryptedResult result = SM2SignedEnvelopeUtil.openAndVerifySignedEnvelopedData(envelope, decryptPrivateKey, signPublicKey);
System.out.println("signatureValid=" + result.signatureValid);
System.out.println(new String(result.plaintext, StandardCharsets.UTF_8));
}
/**
* 校验命令行参数数量
*
* @param args 实际参数数组
* @param expected 期望参数数量
* @param usage 当前命令用法说明
* @return 无返回值
*/
private static void requireArgs(String[] args, int expected, String usage) {
if (args.length != expected) {
printUsage();
throw new IllegalArgumentException("用法: " + usage);
}
}
/**
* 读取文本参数
*
* @param value 文本值 @ 开头时表示从文件读取 UTF-8 文本
* @return UTF-8 文本字节
* @throws Exception 文件读取失败时抛出
*/
private static byte[] readTextArg(String value) throws Exception {
if (value.startsWith("@")) {
return Files.readAllBytes(Paths.get(value.substring(1)));
}
return value.getBytes(StandardCharsets.UTF_8);
}
/**
* 打印统一入口用法
*
* @param 无参数
* @return 无返回值
*/
private static void printUsage() {
System.out.println("统一入口: java -jar sectool-*-jar-with-dependencies.jar <command> [args]");
System.out.println("命令:");
System.out.println(" genkey");
System.out.println(" env-create <publicKeyBase64> <text|@file>");
System.out.println(" env-open <privateKeyBase64> <envelopeBase64|@file>");
System.out.println(" signed-env-create <signPrivateKeyBase64> <encryptPublicKeyBase64> <text|@file>");
System.out.println(" signed-env-open <decryptPrivateKeyBase64> <signPublicKeyBase64> <envelopeBase64|@file>");
}
}

View File

@ -80,6 +80,27 @@ public class SM2SignedEnvelopeUtil {
return keyFactory.generatePublic(keySpec);
}
/**
* ???? SM2 ?????
*
* @param plaintext ??????
* @param encryptionPublicKey ??? SM2 ???
* @return ??????????
* @throws Exception ???????????
*/
public static byte[] createEnvelopedData(byte[] plaintext, PublicKey encryptionPublicKey) throws Exception {
return encryptContent(plaintext, encryptionPublicKey);
}
/**
* ?????? SM2 ?????
*
* @param plaintext ??????????
* @param signingKey ??? SM2 ???
* @param encryptionPublicKey ??? SM2 ???
* @return ??????????????
* @throws Exception ??????????????
*/
public static byte[] createSignedAndEnvelopedData(byte[] plaintext, PrivateKey signingKey, PublicKey encryptionPublicKey) throws Exception {
byte[] sm3Digest = calculateSM3Digest(plaintext);
@ -100,6 +121,36 @@ public class SM2SignedEnvelopeUtil {
signature.update(signedAttrsEncoded);
byte[] signatureValue = signature.sign();
byte[] encryptedContent = encryptContent(plaintext, encryptionPublicKey);
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);
}
/**
* ???????
*
* @param plaintext ??????
* @param encryptionPublicKey ??? SM2 ???
* @return ?????????????SM2 ??? SM4 ???IV????
* @throws Exception ????????
*/
private static byte[] encryptContent(byte[] plaintext, PublicKey encryptionPublicKey) throws Exception {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[12];
random.nextBytes(iv);
@ -123,26 +174,46 @@ public class SM2SignedEnvelopeUtil {
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;
return baos.toByteArray();
}
private static byte[] calculateSM3Digest(byte[] data) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SM3", PROVIDER);
return digest.digest(data);
/**
* ???????
*
* @param encryptedContent ?????????????SM2 ??? SM4 ???IV????
* @param decryptionKey ??? SM2 ???
* @return ???????
* @throws Exception ???????????????
*/
private static byte[] decryptContent(byte[] encryptedContent, PrivateKey decryptionKey) throws Exception {
if (encryptedContent.length < 14) {
throw new IllegalArgumentException("??????????");
}
int keyLen = ((encryptedContent[0] & 0xFF) << 8) | (encryptedContent[1] & 0xFF);
if (keyLen <= 0 || encryptedContent.length < 2 + keyLen + 12) {
throw new IllegalArgumentException("??????????");
}
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);
return sm4Cipher.doFinal(encryptedData);
}
public static class VerifyResult {
@ -211,38 +282,48 @@ public class SM2SignedEnvelopeUtil {
}
}
/**
* ???? SM2 ?????
*
* @param envelopeData ??????????
* @param decryptionKey ??? SM2 ???
* @return ???????
* @throws Exception ???????????????
*/
public static byte[] openEnvelopedData(byte[] envelopeData, PrivateKey decryptionKey) throws Exception {
return decryptContent(envelopeData, decryptionKey);
}
/**
* ????????? SM2 ?????
*
* @param envelopeData ??????????????
* @param decryptionKey ??? SM2 ???
* @param signingPublicKey ??? SM2 ???
* @return ???????????????????????
* @throws Exception ??????????????????
*/
public static DecryptedResult openAndVerifySignedEnvelopedData(byte[] envelopeData, PrivateKey decryptionKey, PublicKey signingPublicKey) throws Exception {
if (envelopeData.length < 6) {
throw new IllegalArgumentException("?????????????");
}
int sigLen = ((envelopeData[0] & 0xFF) << 24) |
((envelopeData[1] & 0xFF) << 16) |
((envelopeData[2] & 0xFF) << 8) |
(envelopeData[3] & 0xFF);
if (sigLen <= 0 || envelopeData.length < 4 + sigLen) {
throw new IllegalArgumentException("?????????????");
}
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[] plaintext = decryptContent(encryptedContent, decryptionKey);
byte[] messageDigest = calculateSM3Digest(plaintext);
byte[] signedAttrsEncoded = buildSignedAttrs(messageDigest);
@ -268,58 +349,4 @@ public class SM2SignedEnvelopeUtil {
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("=== 验证失败 ===");
}
}
}

View File

@ -0,0 +1,52 @@
package com.sunyard.cisd;
import com.sunyard.cisd.device.tool.SM2SignedEnvelopeUtil;
import com.sunyard.cisd.device.tool.SM2SignedEnvelopeUtil.DecryptedResult;
import com.sunyard.cisd.device.tool.SM2SignedEnvelopeUtil.SM2KeyPair;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
public class SM2EnvelopeTest {
/**
* 1-1 验证普通 SM2 数字信封可以生成并解密回原文
*
* @param 无参数
* @return 无返回值
* @throws Exception 生成密钥加密或解密失败时抛出
*/
@Test
public void test1_1CreateAndOpenEnvelope() throws Exception {
SM2KeyPair encryptionKeyPair = SM2SignedEnvelopeUtil.generateKeyPair();
byte[] plaintext = "1-1 普通 SM2 数字信封".getBytes(StandardCharsets.UTF_8);
byte[] envelope = SM2SignedEnvelopeUtil.createEnvelopedData(plaintext, encryptionKeyPair.publicKey);
byte[] opened = SM2SignedEnvelopeUtil.openEnvelopedData(envelope, encryptionKeyPair.privateKey);
Assert.assertArrayEquals(plaintext, opened);
}
/**
* 1-2 验证带签名的 SM2 数字信封可以生成解密并验签通过
*
* @param 无参数
* @return 无返回值
* @throws Exception 生成密钥签名加密解密或验签失败时抛出
*/
@Test
public void test1_2CreateAndOpenSignedEnvelope() throws Exception {
SM2KeyPair signingKeyPair = SM2SignedEnvelopeUtil.generateKeyPair();
SM2KeyPair encryptionKeyPair = SM2SignedEnvelopeUtil.generateKeyPair();
byte[] plaintext = "1-2 带签名 SM2 数字信封".getBytes(StandardCharsets.UTF_8);
byte[] envelope = SM2SignedEnvelopeUtil.createSignedAndEnvelopedData(
plaintext, signingKeyPair.privateKey, encryptionKeyPair.publicKey);
DecryptedResult result = SM2SignedEnvelopeUtil.openAndVerifySignedEnvelopedData(
envelope, encryptionKeyPair.privateKey, signingKeyPair.publicKey);
Assert.assertArrayEquals(plaintext, result.plaintext);
Assert.assertTrue(result.signatureValid);
}
}