Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SM2Util 支持通过密钥对和证书的字符串形式加载其对象 #82

Merged
merged 5 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 106 additions & 24 deletions src/main/java/twgc/gm/sm2/SM2Util.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package twgc.gm.sm2;

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
Expand All @@ -13,6 +10,7 @@
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.function.Supplier;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.gm.GMNamedCurves;
Expand Down Expand Up @@ -185,8 +183,99 @@ public static String pemFrom(X509Certificate x509Certificate) throws IOException
}

public static PrivateKey loadPrivFromFile(String filename, String password) throws IOException, OperatorCreationException, PKCSException {
return loadPriv(password, () -> {
try {
return new FileReader(filename);
} catch (FileNotFoundException e) {
throw new RuntimeException("Private key \"" + filename + "\" not found", e);
}
});
}

public static PublicKey loadPublicFromFile(String filename) throws IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
return loadPublic(() -> {
try {
return new FileReader(filename);
} catch (FileNotFoundException e) {
throw new RuntimeException("Public key \"" + filename + "\" not found", e);
}
});
}

public static X509Certificate loadX509CertificateFromFile(String filename) throws IOException, CertificateException,
NoSuchProviderException {
try (FileInputStream in = new FileInputStream(filename)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
return (X509Certificate) cf.generateCertificate(in);
}
}

/**
* 从字符串加载私钥
*
* @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();
ECPoint ecpoint = new FixedPointCombMultiplier().multiply(GMNamedCurves.getByName(Const.CURVE_NAME).getG(), d);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ecpoint, PARAMETER_SPEC);
return new BCECPublicKey(privateKey.getAlgorithm(), pubKeySpec,
BouncyCastleProvider.CONFIGURATION);
}

/**
* 加载私钥
*
* @param password 密码
* @param fx {@link Reader} 回调函数
* @return {@link PrivateKey}
* @throws IOException
* @throws OperatorCreationException
* @throws PKCSException
*/
public static PrivateKey loadPriv(String password, Supplier<Reader> fx) throws IOException, OperatorCreationException, PKCSException {
PrivateKey priv = null;
try (PEMParser pemParser = new PEMParser(new FileReader(filename))) {
try (PEMParser pemParser = new PEMParser(fx.get())) {
Object obj = pemParser.readObject();
if (password != null && password.length() > 0) {
if (obj instanceof PKCS8EncryptedPrivateKeyInfo) {
Expand All @@ -204,28 +293,21 @@ public static PrivateKey loadPrivFromFile(String filename, String password) thro
return priv;
}

public static PublicKey loadPublicFromFile(String filename) throws IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
try (PemReader pemReader = new PemReader(new FileReader(filename))) {
/**
* 加载公钥
*
* @param fx {@link Reader} 回调函数
* @return {@link PublicKey}
* @throws IOException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
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()));
}
}

public static X509Certificate loadX509CertificateFromFile(String filename) throws IOException, CertificateException,
NoSuchProviderException {
try (FileInputStream in = new FileInputStream(filename)) {
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();
ECPoint ecpoint = new FixedPointCombMultiplier().multiply(GMNamedCurves.getByName(Const.CURVE_NAME).getG(), d);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ecpoint, PARAMETER_SPEC);
return new BCECPublicKey(privateKey.getAlgorithm(), pubKeySpec,
BouncyCastleProvider.CONFIGURATION);
}
}
157 changes: 156 additions & 1 deletion src/test/java/SM2UtilTest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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;
import javax.security.auth.x500.X500Principal;

import org.apache.commons.lang3.RandomStringUtils;
Expand All @@ -25,7 +27,7 @@
import twgc.gm.random.RandomSNAllocator;
import twgc.gm.sm2.SM2Util;
import twgc.gm.sm2.SM2X509CertFactory;

import twgc.gm.utils.ConfigLoader;


@FixMethodOrder(MethodSorters.JVM)
Expand All @@ -46,6 +48,8 @@ public class SM2UtilTest {
X509Certificate x509Certificate;
KeyPair keyPair;

Map<String, Map<String, Object>> configMap;

public static void saveCSRInPem(PKCS10CertificationRequest csr, String csrFile) throws IOException, OperatorCreationException {
String csrPem = SM2Util.pemFrom(csr);
Files.write(Paths.get(csrFile), csrPem.getBytes());
Expand Down Expand Up @@ -126,6 +130,19 @@ public void generateFile() {
Assert.assertTrue(privFile.exists());
Assert.assertTrue(reqFile.exists());
Assert.assertTrue(certFile.exists());

}

@Before
public void loadTestDataConfigMap() {
try {
InputStream in = SM2UtilTest.class.getResourceAsStream("testdata.yml");
this.configMap = ConfigLoader.loadConfig(in);

Assert.assertNotNull(this.configMap);
} catch (Exception e) {
Assert.fail(exceptionHappened);
}
}

//encrypt and decrypt
Expand Down Expand Up @@ -291,6 +308,144 @@ public void issueCertificate() throws Exception {

}

/**
* 测试从 `testdata.yml` 加载配置文件
*
* @throws IOException
*/
@Test
public void testLoadConfigMap() {
Map<String, Object> javagm = this.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 {
Map<String, Object> javagm = this.configMap.get("javagm");
Object testdata = javagm.get("testdata");
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 {
Map<String, Object> javagm = this.configMap.get("javagm");
Object testdata = javagm.get("testdata");

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 {
Map<String, Object> javagm = this.configMap.get("javagm");
Object testdata = javagm.get("testdata");

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 {
Map<String, Object> javagm = this.configMap.get("javagm");
Object testdata = javagm.get("testdata");

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-----