在Java环境中部署国密证书时遇到`InvalidKeySpecException`,核心原因通常是JDK原生安全提供者不支持国密算法,导致在解析国密密钥(尤其是SM2)时失败。
要解决这个问题,核心思路是引入并正确配置BouncyCastle安全提供者。以下是系统化的处理方案:
三步排查与解决
1. 检查并统一BouncyCastle依赖版本
版本不一致或冲突是常见诱因。请检查并确保项目中的BouncyCastle依赖版本统一且兼容。
JDK 8用户:应使用 `bcprov-jdk15to18`。
版本推荐:建议使用 `1.66` 或更高版本。
检查冲突:使用 `mvn dependency:tree` 命令检查是否存在多个版本的BouncyCastle,如有则进行排除。
Maven依赖示例:
```xml
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.66</version>
</dependency>
```
特别注意:若使用Hutool工具包,请确保其版本不低于 `5.5.9`,以避免已知的兼容性问题。
2. 确保BouncyCastle提供者被正确且优先注册
注册BouncyCastle提供者,并确保其优先级高于JDK自带的`SunEC`提供者,否则JDK会抢先用不支持的解析器去处理国密密钥。
方法一:代码动态注册(推荐用于测试和快速验证)
在程序启动的最早阶段(如`main`方法第一行)添加:
```java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// ...
Security.addProvider(new BouncyCastleProvider());
```
可通过以下代码验证注册是否成功:
```java
Security.getProviders().forEach(p -> System.out.println(p.getName()));
```
方法二:修改 `java.security` 文件(推荐用于生产环境)
1. 找到JDK安装目录下的 `jre/lib/security/java.security` 文件。
2. 在安全提供者列表中添加一行,**确保其序号在`sun.security.ec.SunEC`之前**,例如:
```
security.provider.1=org.bouncycastle.jce.provider.BouncyCastleProvider
security.provider.2=sun.security.provider.Sun
// ... 其他提供者
```
注意,如果你的文件中`security.provider.1`已被占用,请使用一个未被占用的序号。
3. 检查并转换密钥格式
密钥格式不正确也会导致解析失败。
私钥:Java的`PKCS8EncodedKeySpec`要求私钥必须是PKCS#8格式。如果你的私钥是PKCS#1格式(以`-----BEGIN RSA PRIVATE KEY-----`开头),需要先转换为PKCS#8格式。
公钥:通常使用`X509EncodedKeySpec`。
密钥文件:确保你提供给Java加载的,是去除头尾标记和换行符后的纯Base64编码内容**,而非完整的PEM格式字符串。
代码实现示例
以下是一个使用BouncyCastle加载SM2公钥的示例,关键点在于指定Provider为`BC`。
```java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class SM2Util {
// 在静态代码块中确保Provider被注册
static {
Security.addProvider(new BouncyCastleProvider());
}
public static PublicKey getPublicKey(String base64PubKey) throws Exception {
// 1. 解码Base64
byte[] keyBytes = Base64.getDecoder().decode(base64PubKey);
// 2. 创建X509EncodedKeySpec
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// 3. 使用BouncyCastle提供者生成公钥,算法名指定为"EC"
KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
return keyFactory.generatePublic(keySpec);
}
}
```
特殊情况:Linux环境
如果在Linux服务器上问题依旧,还需检查以下几点:
熵池不足:SM2等算法需要足够的随机数。可以尝试安装 `haveged` 或 `rng-tools` 来补充熵。
文件权限:确保Java进程对证书和密钥文件有读取权限。
JDK版本:建议使用 JDK 8u292 或更高版本。
调试启动:可添加JVM参数 `-Djavax.net.debug=ssl:handshake` 来获取更详细的握手日志,帮助定位问题。
如果问题仍未解决,请提供完整的错误堆栈信息、JDK版本、项目构建工具及依赖(如Maven的`pom.xml`),以便进一步分析。