diff --git a/hutool-crypto/src/main/java/cn/hutool/v7/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/v7/crypto/KeyUtil.java index d9952b91d5..9b32970259 100644 --- a/hutool-crypto/src/main/java/cn/hutool/v7/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/v7/crypto/KeyUtil.java @@ -23,6 +23,7 @@ import cn.hutool.v7.core.lang.Assert; import cn.hutool.v7.core.text.CharUtil; import cn.hutool.v7.core.text.StrUtil; import cn.hutool.v7.core.util.RandomUtil; +import cn.hutool.v7.core.xml.XmlUtil; import cn.hutool.v7.crypto.asymmetric.AsymmetricAlgorithm; import cn.hutool.v7.crypto.bc.ECKeyUtil; import cn.hutool.v7.crypto.bc.SM2Constant; @@ -234,6 +235,24 @@ public class KeyUtil { // endregion // region ----- generatePrivateKey + /** + * 生成RSA私钥,仅用于非对称加密 + * 算法见:... + * + * @param key 密钥,支持XML和Base64两种格式,XML为C#生成格式,见{@link SpecUtil#xmlToRSAPrivateCrtKeySpec(String)} + * @return RSA私钥 {@link PrivateKey} + * @since 7.0.0 + */ + public static PrivateKey generateRSAPrivateKey(String key) { + Assert.notBlank(key, "Key is blank!"); + key = StrUtil.trim(key); + if(StrUtil.startWith(key, XmlUtil.C_LT)){ + return generateRSAPrivateKey(SpecUtil.xmlToRSAPrivateCrtKeySpec(key)); + } + + return generatePrivateKey(AsymmetricAlgorithm.RSA.getValue(), Base64.decode(key)); + } + /** * 生成RSA私钥,仅用于非对称加密
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法
@@ -247,6 +266,18 @@ public class KeyUtil { return generatePrivateKey(AsymmetricAlgorithm.RSA.getValue(), key); } + /** + * 生成RSA私钥,仅用于非对称加密
+ * 算法见:... + * + * @param keySpec {@link KeySpec} + * @return RSA私钥 {@link PrivateKey} + * @since 5.8.41 + */ + public static PrivateKey generateRSAPrivateKey(final KeySpec keySpec) { + return generatePrivateKey(AsymmetricAlgorithm.RSA.getValue(), keySpec); + } + /** * 生成私钥,仅用于非对称加密
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法
@@ -362,8 +393,7 @@ public class KeyUtil { * @since 5.3.6 */ public static PublicKey getRSAPublicKey(final PrivateKey privateKey) { - if (privateKey instanceof RSAPrivateCrtKey) { - final RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey; + if (privateKey instanceof final RSAPrivateCrtKey privk) { return getRSAPublicKey(privk.getModulus(), privk.getPublicExponent()); } return null; diff --git a/hutool-crypto/src/main/java/cn/hutool/v7/crypto/SpecUtil.java b/hutool-crypto/src/main/java/cn/hutool/v7/crypto/SpecUtil.java index c8887ed70a..d293087890 100644 --- a/hutool-crypto/src/main/java/cn/hutool/v7/crypto/SpecUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/v7/crypto/SpecUtil.java @@ -16,11 +16,16 @@ package cn.hutool.v7.crypto; +import cn.hutool.v7.core.codec.binary.Base64; import cn.hutool.v7.core.util.RandomUtil; +import cn.hutool.v7.core.xml.XmlUtil; +import org.w3c.dom.Element; import javax.crypto.spec.*; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; /** * 规范相关工具类,用于生成密钥规范、参数规范等快捷方法。 @@ -91,4 +96,59 @@ public class SpecUtil { public static PBEParameterSpec createPBEParameterSpec(final byte[] salt, final int iterationCount) { return new PBEParameterSpec(salt, iterationCount); } + + /** + * 将XML格式的密钥参数转化为{@link RSAPrivateCrtKeySpec},XML为C#生成格式,类似于: + *
{@code
+	 * 
+	 *     xx
+	 *     xx
+	 *     

xxxxxxxxx

+ * xxxxxxxxx + * xxxxxxxx + * xxxxxxxx + * xx + * xxxxxxxxx + *
+ * }
+ * + * @param xml xml格式密钥字符串 + * @return {@link RSAPrivateCrtKeySpec} + */ + public static RSAPrivateCrtKeySpec xmlToRSAPrivateCrtKeySpec(final String xml) { + // 1. 解析XML + final Element rootElement = XmlUtil.getRootElement(XmlUtil.parseXml(xml)); + + // 2. 提取各个字段 + final String modulusB64 = XmlUtil.elementText(rootElement, "Modulus"); + final String exponentB64 = XmlUtil.elementText(rootElement, "Exponent"); + final String pB64 = XmlUtil.elementText(rootElement, "P"); + final String qB64 = XmlUtil.elementText(rootElement, "Q"); + final String dpB64 = XmlUtil.elementText(rootElement, "DP"); + final String dqB64 = XmlUtil.elementText(rootElement, "DQ"); + final String inverseQB64 = XmlUtil.elementText(rootElement, "InverseQ"); + final String dB64 = XmlUtil.elementText(rootElement, "D"); + + // 3. Base64解码 + final byte[] modulus = Base64.decode(modulusB64); + final byte[] publicExponent = Base64.decode(exponentB64); + final byte[] privateExponent = Base64.decode(dB64); + final byte[] primeP = Base64.decode(pB64); + final byte[] primeQ = Base64.decode(qB64); + final byte[] primeExponentP = Base64.decode(dpB64); + final byte[] primeExponentQ = Base64.decode(dqB64); + final byte[] crtCoefficient = Base64.decode(inverseQB64); + + // 4. 创建RSAPrivateCrtKeySpec + return new RSAPrivateCrtKeySpec( + new BigInteger(1, modulus), + new BigInteger(1, publicExponent), + new BigInteger(1, privateExponent), + new BigInteger(1, primeP), + new BigInteger(1, primeQ), + new BigInteger(1, primeExponentP), + new BigInteger(1, primeExponentQ), + new BigInteger(1, crtCoefficient) + ); + } } diff --git a/hutool-crypto/src/test/java/cn/hutool/v7/crypto/asymmetric/IssueID1EIKTest.java b/hutool-crypto/src/test/java/cn/hutool/v7/crypto/asymmetric/IssueID1EIKTest.java new file mode 100644 index 0000000000..d6cdaeecd8 --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/v7/crypto/asymmetric/IssueID1EIKTest.java @@ -0,0 +1,58 @@ +package cn.hutool.v7.crypto.asymmetric; + +import cn.hutool.v7.core.codec.binary.Base64; +import cn.hutool.v7.crypto.KeyUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.security.PrivateKey; + +public class IssueID1EIKTest { + @Test + void rsaTest(){ + // 1. Base64解码 + final String keyXmlBase64Str = "PFJTQUtleVZhbHVlPjxNb2R1bHVzPnVscHlkSXJydHJUMzJBSnFDV0FFMHQxNXdHYjBKUTJqSnpBUW1FakpRRzhkcnUr" + + "dDhyQUtzekVoNXRRL2x4eTdnMFVMR3dzWjNmekQrdm12d2lKWkx5d1dncmszMDdRbFpXSkU3dWIxM2ZtN2pUa0RLOXM0L294alNabm5JTHc" + + "rc0lwVGFoLzdlL2hLNkxEN0VFbzNuTHZZK0VjTzdHa21IYXVCUW5CZmhPaz08L01vZHVsdXM+PEV4cG9uZW50PkFRQUI8L0V4cG9uZW50Pj" + + "xQPjZlSFdVYUZNdWRTV0svODJPeWxxNHZ2Y0FDbmNHUHYvN1VKWVVETnY1elBZVGE5UFNXUTRzNUk3RHBDTWJYcExLK0VldE5mOUFCZ1ZwV" + + "jZERTJlMTR3PT08L1A+PFE+eS9uMkc3d2FYZVlGUnZXWjNROW96NVkyVEpHdUdaSXIzeis3QlVGOWZIckp1Nk9SU2V0YUVkdW5tcjgzSFVN" + + "N3E4TGIvWGxtdmVpS0p0OWh2NWx6d3c9PTwvUT48RFA+Sy9IdExTVmJuMGNjZUdQWnNzQVRmMWJIZlpoZjdLbmM2cDJlcm1NYjBadGlOeWF" + + "MaFVTNWlyUWRPSjFjWlcybkZqV1VhWEp6N1VLWlBwdEZrYTNZOVE9PTwvRFA+PERRPkJSbm9QTU5VaVhxaU1TY2RSUGtJcndCYnRVaURhU0" + + "pOdEpTY2NjSTBpRE50N2lKbUZNb3RBM3RSMHIzcmUvRGRnaXNxWTBsdzkxamtjNXBza0dVZkR3PT08L0RRPjxJbnZlcnNlUT5rVGpLTzBpc" + + "XU4M3pTZGpqbWNoT2lYQ0k0bm5veTg5c0JiOFFqMk92TXpnRnhOazhVV1hoT29ZdGVnUDNiVUFhZEJBT3VGSnRCcE1RMmdCemo2ekRWZz09" + + "PC9JbnZlcnNlUT48RD5COUhQeDdBa24vQU1EbFpibUxVY3ZyUm9iWGhrZWtHT1BSQzVRWXFjVjBYU1d3clhvNzFiVlpXVU5KbG5hYkhjOUc" + + "4clBpRkRIcHVDcGI5Z2JxYitVdmdKRXFrd0t5cU5HSmdnSm9yS1Irb2doWFh3czRuZVVTV1lENnpqbGQvN2U0QlNRM05ScTJGbEFPSEZnRn" + + "p3aElhazZwY1pOT2pwazlTUWdSY2ZaSGs9PC9EPjwvUlNBS2V5VmFsdWU+"; + final String keyXmlStr = Base64.decodeStr(keyXmlBase64Str); + + final PrivateKey privateKey = KeyUtil.generateRSAPrivateKey(keyXmlStr); + final RSA rsa = new RSA(privateKey, null); + + final String encryptStr = "tqmp7hGri5WYcZT8bJXJK3SKVlkAx1i1JSpOlOIGB+EAA5OoWS0PtCcWdwLou/qVM28e" + + "xXKGpmehYbx0Ez0Co8bLHMMnXU3bxp3PXstF2MvrODJoEz+nEzxQ92ngg2n/96Du1rCbwkletYFRO47HpkcEYSTKBsi6NtC98JhUsYSXG15" + + "hCJu/I8vOWDF9sB4FCFF9qScpEOUndhctDvAH/UvxBqvSix8mJdL9pyz6Er3zhhQ//4LnI3dQQM0saTq4rZITliTxalT32DRfz0Vj5hNj/S" + + "o54SspX6fbHjRu0jEaMAotebYZ1Tgpw4AHCYy1DIYoVeGSACd4kc+6ka67gI8jXD7H0tIhI2zyTU3MWQWm2tSOCj+WllELlmCn7ssDp37M6" + + "hNO9Imzzj32hWQrsvYsCFufAh+KqRQ1zoF1CQVK8wHRf2ppSFjfR9cCcunpqHqeRrJIpzhJ11dvGZ3JokcjOfDrTNKyXXr7+NVkmc9jPvByEGJXcgkJuX1EHyMv"; + final String decrypt = rsa.decryptStr(encryptStr, KeyType.PrivateKey); + + final String expectedDecodeStr = """ + cpu=178BFBFF00A50F00\r + baseBoard=MP242ML1\r + bios=MP242ML1\r + mac=00:FF:CB:EF:28:18|00:FF:03:A2:FC:D7|C8:94:02:F8:8A:83\r + cusname=123\r + serviceno=12121\r + kcliccount=1\r + cjliccount=1\r + venprintliccount=1\r + beginTime=2025-10-11 14:05:10\r + endTime=2026-10-11 14:05:10\r + lictype=租赁\r + serviceendtime=1\r + validate=1\r + validateunit=年\r + """; + + Assertions.assertEquals(expectedDecodeStr, decrypt); + } +}