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