You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring SSL Bundle如何兼容加密RSA私钥?

解决Spring SSL Bundle无法识别带Proc-Type/DEK-Info的加密RSA私钥问题

方案一:将PKCS#1加密私钥转换为PKCS#8格式

Spring SSL Bundle对PKCS#8格式的加密私钥支持更完善,可通过OpenSSL将现有PKCS#1格式私钥转换为PKCS#8:

openssl pkcs8 -topk8 -in private.key -out private-pkcs8.key -v2 aes-256-cbc

执行命令时需输入原私钥密码,之后可设置与原密码一致的新加密密码。

修改Spring配置文件,替换私钥路径为转换后的文件:

spring:
  ssl:
    bundle:
      pem:
        server:
          key:
            alias: "my-alias"
          keystore:
            private-key-password: password # 填写转换时设置的密码
            private-key: private-pkcs8.key
            certificate: cert.pem

方案二:自定义私钥解析逻辑扩展Spring支持

若不想转换私钥格式,可自定义解析器处理带Proc-Type和DEK-Info头的PKCS#1加密私钥:

  1. 实现自定义私钥解析类:
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import java.io.BufferedReader;
import java.io.StringReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import java.util.HexFormat;

public class EncryptedPkcs1PemPrivateKeyParser {
    private static final Pattern PKCS1_ENCRYPTED_HEADER = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----\\s*Proc-Type: 4,ENCRYPTED\\s*DEK-Info: ([A-Za-z0-9-]+),([A-Fa-f0-9]+)\\s*");
    private static final Pattern PKCS1_FOOTER = Pattern.compile("-----END RSA PRIVATE KEY-----");

    public static PrivateKey parse(String pemContent, char[] password) throws Exception {
        BufferedReader reader = new BufferedReader(new StringReader(pemContent));
        StringBuilder keyContent = new StringBuilder();
        String line;
        boolean headerFound = false;
        String dekAlgorithm = null;
        String iv = null;

        while ((line = reader.readLine()) != null) {
            if (!headerFound) {
                Matcher headerMatcher = PKCS1_ENCRYPTED_HEADER.matcher(keyContent.append(line).append("\n").toString());
                if (headerMatcher.find()) {
                    dekAlgorithm = headerMatcher.group(1);
                    iv = headerMatcher.group(2);
                    headerFound = true;
                    keyContent.setLength(0);
                }
            } else if (!PKCS1_FOOTER.matcher(line).matches()) {
                keyContent.append(line.trim());
            }
        }

        Assert.isTrue(headerFound, "Unrecognized encrypted PKCS#1 private key format");
        byte[] encryptedKey = Base64Utils.decodeFromString(keyContent.toString());

        // 按DEK-Info指定的算法解密私钥
        Cipher cipher = Cipher.getInstance(dekAlgorithm);
        IvParameterSpec ivSpec = new IvParameterSpec(HexFormat.of().parseHex(iv));
        PBEKeySpec keySpec = new PBEKeySpec(password);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        SecretKey secretKey = keyFactory.generateSecret(keySpec);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
        byte[] decryptedKey = cipher.doFinal(encryptedKey);

        // 将解密后的PKCS#1私钥转换为PrivateKey实例
        KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateCrtKeySpec pkcs1Spec = rsaKeyFactory.getKeySpec(new PKCS8EncodedKeySpec(decryptedKey), RSAPrivateCrtKeySpec.class);
        return rsaKeyFactory.generatePrivate(pkcs1Spec);
    }
}
  1. 自定义SslBundle配置替换默认逻辑:
import org.springframework.boot.autoconfigure.ssl.SslBundle;
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties;
import org.springframework.boot.autoconfigure.ssl.SslBundles;
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;

@Configuration
public class CustomSslBundleConfig {

    @Bean
    public SslBundle customServerSslBundle(SslBundleProperties properties) throws Exception {
        // 读取私钥文件内容
        String privateKeyContent = Files.readString(Paths.get(properties.getPem().getServer().getKeystore().getPrivateKey()));
        char[] password = properties.getPem().getServer().getKeystore().getPrivateKeyPassword().toCharArray();
        // 使用自定义解析器解析私钥
        java.security.PrivateKey privateKey = EncryptedPkcs1PemPrivateKeyParser.parse(privateKeyContent, password);

        // 读取并加载证书
        List<X509Certificate> certificates = CertificateFactory.getInstance("X.509")
                .generateCertificates(Files.newInputStream(Paths.get(properties.getPem().getServer().getKeystore().getCertificate())))
                .stream()
                .map(X509Certificate.class::cast)
                .toList();

        // 构建自定义KeyStore
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null);
        keyStore.setKeyEntry(properties.getPem().getServer().getKey().getAlias(), privateKey, password, certificates.toArray(new java.security.cert.Certificate[0]));

        return PemSslStoreBundle.of(keyStore, password, null);
    }

    @Bean
    public SslBundles customSslBundles(SslBundle customServerSslBundle) {
        return new SslBundles() {
            @Override
            public SslBundle getBundle(String bundleName) {
                if ("server".equals(bundleName)) {
                    return customServerSslBundle;
                }
                throw new IllegalArgumentException("Unknown bundle: " + bundleName);
            }
        };
    }
}

问题原因分析

Spring默认的PemPrivateKeyParser中,PKCS1_RSA_HEADER正则仅匹配-----BEGIN RSA PRIVATE KEY-----后直接跟Base64密钥内容的格式,而加密的PKCS#1私钥会插入Proc-Type和DEK-Info头信息,导致正则匹配失败,触发Missing private key or unrecognized format异常。而PKCS#8格式的加密私钥使用统一的-----BEGIN ENCRYPTED PRIVATE KEY-----头,Spring解析器原生支持该格式。

内容的提问来源于stack exchange,提问作者Noam Gershi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:35:55