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加密私钥:
- 实现自定义私钥解析类:
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); } }
- 自定义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
相关产品推荐
相关产品推荐

