Skip to content

Commit

Permalink
Merge pull request #82 from photowey/feature/loadkeystr
Browse files Browse the repository at this point in the history
feat: SM2Util 支持通过密钥对和证书的字符串形式加载其对象
  • Loading branch information
SamYuan1990 authored Dec 9, 2023
2 parents c730429 + 6b2a5b8 commit 1fe8186
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 25 deletions.
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-----

0 comments on commit 1fe8186

Please sign in to comment.