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