diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4182165dc..23ce0686d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
* 【core 】 Bean字段支持Alias注解(包括转map,转bean等)
* 【core 】 增加ValueListHandler,优化结果集获取方式
* 【http 】 支持patch方法(issue#666@Github)
+* 【crypto】 BCUtil支持更加灵活的密钥类型,增加writePemObject方法
### Bug修复
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
index e8152ffec..72a073ca6 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
@@ -296,6 +296,17 @@ public class IoUtil {
// -------------------------------------------------------------------------------------- Copy end
// -------------------------------------------------------------------------------------- getReader and getWriter start
+ /**
+ * 获得一个文件读取器,默认使用UTF-8编码
+ *
+ * @param in 输入流
+ * @return BufferedReader对象
+ * @since 5.1.6
+ */
+ public static BufferedReader getUtf8Reader(InputStream in) {
+ return getReader(in, CharsetUtil.CHARSET_UTF_8);
+ }
+
/**
* 获得一个文件读取器
*
@@ -358,6 +369,17 @@ public class IoUtil {
return (reader instanceof PushbackReader) ? (PushbackReader) reader : new PushbackReader(reader, pushBackSize);
}
+ /**
+ * 获得一个Writer,默认编码UTF-8
+ *
+ * @param out 输入流
+ * @return OutputStreamWriter对象
+ * @since 5.1.6
+ */
+ public static OutputStreamWriter getUtf8Writer(OutputStream out) {
+ return getWriter(out, CharsetUtil.CHARSET_UTF_8);
+ }
+
/**
* 获得一个Writer
*
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java
index f260c2f1d..0b423c2ff 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java
@@ -1,19 +1,8 @@
package cn.hutool.crypto;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.EllipticCurve;
-
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
@@ -21,12 +10,24 @@ import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.bouncycastle.util.io.pem.PemReader;
+import org.bouncycastle.util.io.pem.PemWriter;
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.CharsetUtil;
-import cn.hutool.core.util.StrUtil;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
/**
* Bouncy Castle相关工具类封装
@@ -98,7 +99,7 @@ public class BCUtil {
* @since 4.5.2
*/
public static PrivateKey readPrivateKey(InputStream pemStream) {
- return KeyUtil.generateRSAPrivateKey(readKeyBytes(pemStream));
+ return (PrivateKey) readPemKey(pemStream);
}
/**
@@ -109,11 +110,21 @@ public class BCUtil {
* @since 4.5.2
*/
public static PublicKey readPublicKey(InputStream pemStream) {
- final Certificate certificate = KeyUtil.readX509Certificate(pemStream);
- if (null == certificate) {
- return null;
- }
- return certificate.getPublicKey();
+ return (PublicKey) readPemKey(pemStream);
+ }
+
+ /**
+ * 从pem文件中读取公钥或私钥
+ * 根据类型返回{@link PublicKey} 或者 {@link PrivateKey}
+ *
+ * @param pemKeyStream pem流
+ * @return {@link Key}
+ * @since 4.5.2
+ * @deprecated 请使用{@link #readPemKey(InputStream)}
+ */
+ @Deprecated
+ public static Key readKey(InputStream pemKeyStream) {
+ return readPemKey(pemKeyStream);
}
/**
@@ -121,17 +132,24 @@ public class BCUtil {
* 根据类型返回{@link PublicKey} 或者 {@link PrivateKey}
*
* @param keyStream pem流
- * @return {@link Key}
- * @since 4.5.2
+ * @return {@link Key},null表示无法识别的密钥类型
+ * @since 5.1.6
*/
- public static Key readKey(InputStream keyStream) {
+ public static Key readPemKey(InputStream keyStream) {
final PemObject object = readPemObject(keyStream);
final String type = object.getType();
- if (StrUtil.isNotBlank(type) && type.endsWith("PRIVATE KEY")) {
- return KeyUtil.generateRSAPrivateKey(object.getContent());
- } else {
- return KeyUtil.readX509Certificate(keyStream).getPublicKey();
+ if (StrUtil.isNotBlank(type)) {
+ if (type.endsWith("PRIVATE KEY")) {
+ return KeyUtil.generateRSAPrivateKey(object.getContent());
+ } else if (type.endsWith("PUBLIC KEY")) {
+ return KeyUtil.generateRSAPublicKey(object.getContent());
+ } else if (type.endsWith("CERTIFICATE")) {
+ return KeyUtil.readPublicKeyFromCert(IoUtil.toStream(object.getContent()));
+ }
}
+
+ //表示无法识别的密钥类型
+ return null;
}
/**
@@ -140,8 +158,21 @@ public class BCUtil {
* @param keyStream pem流
* @return 密钥bytes
* @since 4.5.2
+ * @deprecated 使用{@link #readPem(InputStream)}
*/
+ @Deprecated
public static byte[] readKeyBytes(InputStream keyStream) {
+ return readPem(keyStream);
+ }
+
+ /**
+ * 从pem流中读取公钥或私钥
+ *
+ * @param keyStream pem流
+ * @return 密钥bytes
+ * @since 5.1.6
+ */
+ public static byte[] readPem(InputStream keyStream) {
PemObject pemObject = readPemObject(keyStream);
if (null != pemObject) {
return pemObject.getContent();
@@ -157,9 +188,20 @@ public class BCUtil {
* @since 4.5.2
*/
public static PemObject readPemObject(InputStream keyStream) {
+ return readPemObject(IoUtil.getUtf8Reader(keyStream));
+ }
+
+ /**
+ * 读取pem文件中的信息,包括类型、头信息和密钥内容
+ *
+ * @param reader pem Reader
+ * @return {@link PemObject}
+ * @since 5.1.6
+ */
+ public static PemObject readPemObject(Reader reader) {
PemReader pemReader = null;
try {
- pemReader = new PemReader(IoUtil.getReader(keyStream, CharsetUtil.CHARSET_UTF_8));
+ pemReader = new PemReader(reader);
return pemReader.readPemObject();
} catch (IOException e) {
throw new IORuntimeException(e);
@@ -167,4 +209,35 @@ public class BCUtil {
IoUtil.close(pemReader);
}
}
+
+ /**
+ * 写出pem密钥(私钥、公钥、证书)
+ *
+ * @param type 密钥类型(私钥、公钥、证书)
+ * @param content 密钥内容
+ * @param keyStream pem流
+ * @since 5.1.6
+ */
+ public static void writePemObject(String type, byte[] content, OutputStream keyStream) {
+ writePemObject(new PemObject(type, content), keyStream);
+ }
+
+ /**
+ * 写出pem密钥(私钥、公钥、证书)
+ *
+ * @param pemObject pem对象,包括密钥和密钥类型等信息
+ * @param keyStream pem流
+ * @since 5.1.6
+ */
+ public static void writePemObject(PemObjectGenerator pemObject, OutputStream keyStream) {
+ PemWriter writer = null;
+ try {
+ writer = new PemWriter(IoUtil.getUtf8Writer(keyStream));
+ writer.writeObject(pemObject);
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ } finally {
+ IoUtil.close(writer);
+ }
+ }
}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java
index 3354d4c13..3dc10d01c 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java
@@ -1,9 +1,8 @@
package cn.hutool.crypto.asymmetric;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
+import cn.hutool.crypto.CryptoException;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
@@ -12,9 +11,9 @@ import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
-import cn.hutool.crypto.CryptoException;
-import cn.hutool.crypto.SecureUtil;
-import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
/**
* 国密SM2算法实现,基于BC库
@@ -41,7 +40,7 @@ public class SM2 extends AbstractAsymmetricCrypto {
* 构造,生成新的私钥公钥对
*/
public SM2() {
- this((byte[]) null, (byte[]) null);
+ this(null, (byte[]) null);
}
/**
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java
index 1492dac58..abe4bf6d4 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java
@@ -199,12 +199,10 @@ public class SM2Engine {
this.digest.doFinal(c3, 0);
// 按照对应模式输出结果
- switch (mode) {
- case C1C3C2:
+ if (mode == SM2Mode.C1C3C2) {
return Arrays.concatenate(c1, c3, c2);
- default:
- return Arrays.concatenate(c1, c2, c3);
}
+ return Arrays.concatenate(c1, c2, c3);
}
/**
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java
index f5077bdc8..9b8ab803a 100644
--- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java
@@ -24,7 +24,13 @@ public class BCUtilTest {
PublicKey publicKey = BCUtil.readPublicKey(ResourceUtil.getStream("test_public_key.csr"));
Assert.assertNotNull(publicKey);
}
-
+
+ @Test
+ public void readPemKeyTest() {
+ PublicKey publicKey = (PublicKey) BCUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr"));
+ Assert.assertNotNull(publicKey);
+ }
+
@Test
public void validateKey() {
PrivateKey privateKey = BCUtil.readPrivateKey(ResourceUtil.getStream("test_private_key.pem"));
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java
index 2ed2175e2..eabd71f5a 100644
--- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java
@@ -4,6 +4,7 @@ import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
+import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
@@ -47,6 +48,7 @@ public class SM2Test {
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
+ Console.log(HexUtil.encodeHexStr(publicKey));
SM2 sm2 = SmUtil.sm2(privateKey, publicKey);
sm2.setMode(SM2Mode.C1C3C2);