Skip to content

Commit

Permalink
feat: SM2Util 支持通过密钥对和证书的字符串形式加载其对象
Browse files Browse the repository at this point in the history
Signed-off-by: photowey <[email protected]>
  • Loading branch information
photowey committed Dec 3, 2023
1 parent 9f870e8 commit a757e30
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 27 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ check.dependsOn 'checkstyle'

jacoco {
toolVersion = "0.8.5"
reportsDir = file("$buildDir/customJacocoReportDir")
reportsDirectory = layout.buildDirectory.dir('customJacocoReportDir')
}

jacocoTestReport {
reports {
xml.enabled false
csv.enabled false
xml.required = false
csv.required = false
html.destination file("${buildDir}/jacocoHtml")
}
}
Expand Down
100 changes: 85 additions & 15 deletions src/main/java/twgc/gm/sm2/SM2Util.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
package twgc.gm.sm2;

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
Expand Down Expand Up @@ -50,6 +35,19 @@
import twgc.gm.random.SecureRandomFactory;
import twgc.gm.utils.Const;

import javax.security.auth.x500.X500Principal;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.function.Supplier;

/**
* @author SamYuan; 吴仙杰
* @Description 国密SM2工具类, 算法提供者 Bouncy Castle
Expand Down Expand Up @@ -220,6 +218,50 @@ public static X509Certificate loadX509CertificateFromFile(String filename) throw
}
}

/**
* 从字符串加载私钥
*
* @param privateKey 字符串字私钥
* @param password 密码
* @return {@link PrivateKey} 私钥对象
* @throws IOException
* @throws OperatorCreationException
* @throws PKCSException
*/
public static PrivateKey loadPrivFromString(String privateKey, String password) throws IOException, OperatorCreationException, PKCSException {
return loadPriv(password, () -> new StringReader(privateKey));
}

/**
* 从字符串加载公钥
*
* @param publicKey 字符串公钥
* @return {@link PublicKey} 公钥对象
* @throws IOException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public static PublicKey loadPublicFromString(String publicKey) throws IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
return loadPublic(() -> new StringReader(publicKey));
}

/**
* 从字符串加载证书
*
* @param cert 字符串证书
* @return {@link X509Certificate} 证书对象
* @throws IOException
* @throws CertificateException
* @throws NoSuchProviderException
*/
public static X509Certificate loadX509CertificateFromString(String cert) throws IOException, CertificateException, NoSuchProviderException {
try (InputStream in = new ByteArrayInputStream(cert.getBytes())) {
CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
return (X509Certificate) cf.generateCertificate(in);
}
}

public static PublicKey derivePublicFromPrivate(PrivateKey privateKey) {
BCECPrivateKey localECPrivateKey = (BCECPrivateKey) privateKey;
BigInteger d = localECPrivateKey.getD();
Expand All @@ -228,4 +270,32 @@ public static PublicKey derivePublicFromPrivate(PrivateKey privateKey) {
return new BCECPublicKey(privateKey.getAlgorithm(), pubKeySpec,
BouncyCastleProvider.CONFIGURATION);
}

public static PrivateKey loadPriv(String password, Supplier<Reader> fx) throws IOException, OperatorCreationException, PKCSException {
PrivateKey priv = null;
try (PEMParser pemParser = new PEMParser(fx.get())) {
Object obj = pemParser.readObject();
if (password != null && password.length() > 0) {
if (obj instanceof PKCS8EncryptedPrivateKeyInfo) {
PKCS8EncryptedPrivateKeyInfo epkInfo = (PKCS8EncryptedPrivateKeyInfo) obj;
InputDecryptorProvider decryptor = new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(password.toCharArray());
PrivateKeyInfo pkInfo = epkInfo.decryptPrivateKeyInfo(decryptor);
priv = CONVERTER.getPrivateKey(pkInfo);
}
} else {
priv = CONVERTER.getPrivateKey((PrivateKeyInfo) obj);
}
}
return priv;
}

public static PublicKey loadPublic(Supplier<Reader> fx) throws IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
try (PemReader pemReader = new PemReader(fx.get())) {
PemObject spki = pemReader.readPemObject();
Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
return KeyFactory.getInstance(Const.EC_VALUE, BouncyCastleProvider.PROVIDER_NAME).generatePublic(new X509EncodedKeySpec(spki.getContent()));
}
}
}
176 changes: 167 additions & 9 deletions src/test/java/SM2UtilTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.security.auth.x500.X500Principal;

import org.apache.commons.lang3.RandomStringUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
Expand All @@ -25,7 +16,18 @@
import twgc.gm.random.RandomSNAllocator;
import twgc.gm.sm2.SM2Util;
import twgc.gm.sm2.SM2X509CertFactory;
import twgc.gm.utils.ConfigLoader;

import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Map;


@FixMethodOrder(MethodSorters.JVM)
Expand Down Expand Up @@ -291,6 +293,162 @@ public void issueCertificate() throws Exception {

}

/**
* 测试从 `testdata.yml` 加载配置文件
*
* @throws IOException
*/
@Test
public void testLoadConfigMap() throws IOException {
InputStream in = SM2UtilTest.class.getResourceAsStream("testdata.yml");
Map<String, Map<String, Object>> configMap = ConfigLoader.loadConfig(in);

Assert.assertNotNull(configMap);

Map<String, Object> javagm = configMap.get("javagm");
Assert.assertNotNull(javagm);

Object testdata = javagm.get("testdata");

Assert.assertNotNull(testdata);
String publicKey = (String) ((Map<String, Object>) testdata).get("public-key");
String privateKey = (String) ((Map<String, Object>) testdata).get("private-key");
String cert = (String) ((Map<String, Object>) testdata).get("cert");

Assert.assertNotNull(publicKey);
Assert.assertNotNull(privateKey);
Assert.assertNotNull(cert);
}

/**
* 测试从从字符串加载私钥对象
*
* <pre>
* javagm:
* testdata:
* private-key: |
* -----BEGIN PRIVATE KEY-----
* MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgc0UCgfELjC0V+xUm
* ELYFmy0J0cee42ZpKyQ4FRTBlJSgCgYIKoEcz1UBgi2hRANCAATJbIFbxcAaDxMk
* 7XExTRU/bBnGEu6YfaleJxnLZS40NDNjZV+ztveWfLZk2+oWieykM3/yZ/6IieJk
* 5uuohUjD
* -----END PRIVATE KEY-----
* public-key: |
* -----BEGIN PUBLIC KEY-----
* MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEyWyBW8XAGg8TJO1xMU0VP2wZxhLu
* mH2pXicZy2UuNDQzY2Vfs7b3lny2ZNvqFonspDN/8mf+iIniZObrqIVIww==
* -----END PUBLIC KEY-----
* cert: |
* -----BEGIN CERTIFICATE-----
* MIIBdzCCAR2gAwIBAgIJAfA3Qnph7CieMAoGCCqBHM9VAYN1MBIxEDAOBgNVBAMM
* B1Jvb3QgQ0EwHhcNMjMxMjAyMTQzMjMxWhcNODkxMjI3MDAwMDAwWjASMRAwDgYD
* VQQDEwdSb290IENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEyWyBW8XAGg8T
* JO1xMU0VP2wZxhLumH2pXicZy2UuNDQzY2Vfs7b3lny2ZNvqFonspDN/8mf+iIni
* ZObrqIVIw6NcMFowHQYDVR0OBBYEFOMvj2LPGlkOw1M1Pj34klVi8SFgMA8GA1Ud
* EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMBgGA1UdEQQRMA+BDXRlc3RAdHdn
* Yy5jb20wCgYIKoEcz1UBg3UDSAAwRQIgTeoLjt+eP3kwQg17G+l12wj4MQNed1hW
* aZZkJe43rkICIQCdI3WhnrvzhbEijsTXL1woIwnFgY9MIci7BmKLMpMM6w==
* -----END CERTIFICATE-----
* </pre>
*/
@Test
public void testLoadPrivFromString() throws Exception {
InputStream in = SM2UtilTest.class.getResourceAsStream("testdata.yml");
Map<String, Map<String, Object>> configMap = ConfigLoader.loadConfig(in);
Map<String, Object> javagm = configMap.get("javagm");
Object testdata = javagm.get("testdata");

// 模拟从 配置文件 (`testdata.yml` | `application.yml` | 配置中心等) 获取密钥对字符串
String privateKey = (String) ((Map<String, Object>) testdata).get("private-key");

PrivateKey privKey = SM2Util.loadPrivFromString(privateKey, "");
Assert.assertNotNull(privKey);
}

/**
* 测试从从字符串加载公钥对象
*
* @throws Exception
*/
@Test
public void testLoadPublicFromString() throws Exception {
InputStream in = SM2UtilTest.class.getResourceAsStream("testdata.yml");
Map<String, Map<String, Object>> configMap = ConfigLoader.loadConfig(in);
Map<String, Object> javagm = configMap.get("javagm");
Object testdata = javagm.get("testdata");

// 模拟从 配置文件 (`testdata.yml` | `application.yml` | 配置中心等) 获取密钥对字符串
String publicKey = (String) ((Map<String, Object>) testdata).get("public-key");
PublicKey pubKey = SM2Util.loadPublicFromString(publicKey);
Assert.assertNotNull(pubKey);
}

/**
* 测试从字符串加载密钥对并测试加解密
*
* @throws Exception
*/
@Test
public void testLoadPublicAndPrivFromString() throws Exception {
InputStream in = SM2UtilTest.class.getResourceAsStream("testdata.yml");
Map<String, Map<String, Object>> configMap = ConfigLoader.loadConfig(in);
Map<String, Object> javagm = configMap.get("javagm");
Object testdata = javagm.get("testdata");

// 模拟从 配置文件 (`testdata.yml` | `application.yml` | 配置中心等) 获取密钥对字符串
String publicKey = (String) ((Map<String, Object>) testdata).get("public-key");
String privateKey = (String) ((Map<String, Object>) testdata).get("private-key");

PublicKey pubKey = SM2Util.loadPublicFromString(publicKey);
PrivateKey privKey = SM2Util.loadPrivFromString(privateKey, "");

Assert.assertNotNull(pubKey);
Assert.assertNotNull(privKey);

SM2EnginePool sm2EnginePool = new SM2EnginePool(1, SM2Engine.Mode.C1C3C2);
SM2Engine sm2Engine = null;

try {
SM2Util instance = new SM2Util();
sm2Engine = sm2EnginePool.borrowObject();
byte[] encrypted = instance.encrypt(sm2Engine, pubKey, message);
byte[] rs = instance.decrypt(sm2Engine, privKey, encrypted);
Assert.assertEquals(new String(message), new String(rs));

byte[] encrypted2 = instance.encrypt(sm2Engine, pubKey, "msg".getBytes());
rs = instance.decrypt(sm2Engine, privKey, encrypted2);
Assert.assertNotEquals(new String(message), new String(rs));
} catch (Exception e) {
e.printStackTrace();
Assert.fail(exceptionHappened);
} finally {
if (sm2Engine != null) {
sm2EnginePool.returnObject(sm2Engine);
}
}
}

/**
* 测试从字符串加载证书对象
*
* @throws Exception
*/
@Test
public void testLoadX509CertificateFromString() throws Exception {
InputStream in = SM2UtilTest.class.getResourceAsStream("testdata.yml");
Map<String, Map<String, Object>> configMap = ConfigLoader.loadConfig(in);
Map<String, Object> javagm = configMap.get("javagm");
Object testdata = javagm.get("testdata");

// 模拟从 配置文件 (`testdata.yml` | `application.yml` | 配置中心等) 获取证书字符串
String cert = (String) ((Map<String, Object>) testdata).get("cert");
Assert.assertNotNull(cert);

X509Certificate certificate = SM2Util.loadX509CertificateFromString(cert);
Assert.assertNotNull(certificate);
Assert.assertEquals("SM3WITHSM2", certificate.getSigAlgName());
}

static {
try {
Security.addProvider(new BouncyCastleProvider());
Expand Down
25 changes: 25 additions & 0 deletions src/test/resources/testdata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
javagm:
testdata:
private-key: |
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgc0UCgfELjC0V+xUm
ELYFmy0J0cee42ZpKyQ4FRTBlJSgCgYIKoEcz1UBgi2hRANCAATJbIFbxcAaDxMk
7XExTRU/bBnGEu6YfaleJxnLZS40NDNjZV+ztveWfLZk2+oWieykM3/yZ/6IieJk
5uuohUjD
-----END PRIVATE KEY-----
public-key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEyWyBW8XAGg8TJO1xMU0VP2wZxhLu
mH2pXicZy2UuNDQzY2Vfs7b3lny2ZNvqFonspDN/8mf+iIniZObrqIVIww==
-----END PUBLIC KEY-----
cert: |
-----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIJAfA3Qnph7CieMAoGCCqBHM9VAYN1MBIxEDAOBgNVBAMM
B1Jvb3QgQ0EwHhcNMjMxMjAyMTQzMjMxWhcNODkxMjI3MDAwMDAwWjASMRAwDgYD
VQQDEwdSb290IENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEyWyBW8XAGg8T
JO1xMU0VP2wZxhLumH2pXicZy2UuNDQzY2Vfs7b3lny2ZNvqFonspDN/8mf+iIni
ZObrqIVIw6NcMFowHQYDVR0OBBYEFOMvj2LPGlkOw1M1Pj34klVi8SFgMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMBgGA1UdEQQRMA+BDXRlc3RAdHdn
Yy5jb20wCgYIKoEcz1UBg3UDSAAwRQIgTeoLjt+eP3kwQg17G+l12wj4MQNed1hW
aZZkJe43rkICIQCdI3WhnrvzhbEijsTXL1woIwnFgY9MIci7BmKLMpMM6w==
-----END CERTIFICATE-----

0 comments on commit a757e30

Please sign in to comment.