如何用Java代码替代keytool程序化生成X509证书?——代码报错及API查找问题解析
Let's break down your issues step by step and fix them:
1. Why you're seeing the "Empty input" error
Your current code uses CertificateFactory.generateCertificate() which is designed to parse an existing certificate file, not create a new one. Since your server.crt is empty, the method throws an IOException—this is exactly what it's supposed to do when given no valid certificate data to read. To generate a certificate from scratch, you need a completely different approach.
2. What's up with missing sun.security.tools.keytool.* in Java 17
Those classes are part of Oracle's internal JDK APIs, which were hidden and marked as unsupported starting in Java 9 (thanks to the module system). You shouldn't rely on them anyway—they're not part of the standard Java API, so they can change or disappear without warning. Instead, we'll use standard, supported Java security APIs to replicate your keytool command.
3. Complete Kotlin code to generate your server certificate (matches your keytool command)
Below is a Quarkus-compatible implementation that creates an EC key pair, generates a self-signed X.509 certificate with all the extensions you specified, stores it in a PKCS12 keystore, and exports the certificate to server.crt:
import io.quarkus.runtime.QuarkusApplication import io.quarkus.runtime.annotations.QuarkusMain import java.io.FileOutputStream import java.math.BigInteger import java.security.KeyPair import java.security.KeyPairGenerator import java.security.KeyStore import java.security.SecureRandom import java.security.cert.X509Certificate import java.time.Duration import java.time.Instant import java.util.Date import javax.security.auth.x500.X500Principal import sun.security.x509.* @QuarkusMain class Main : QuarkusApplication { override fun run(vararg args: String?): Int { // Replace these with your actual values from the keytool command val serverDn = "CN=your-server, OU=your-org, O=your-company, L=your-city, ST=your-state, C=your-country" val serverSan = "DNS:your-server-domain.com, IP:192.168.1.100" val serverPw = "your-password".toCharArray() val keystorePath = "./keystore.jks" // PKCS12 will use this path despite .jks extension val certPath = "./server.crt" try { // 1. Generate EC key pair (matches -keyalg "EC") val keyPairGenerator = KeyPairGenerator.getInstance("EC") keyPairGenerator.initialize(256) // Use 256-bit EC, adjust if needed val keyPair: KeyPair = keyPairGenerator.generateKeyPair() // 2. Prepare certificate details val issuer = X500Principal(serverDn) val subject = X500Principal(serverDn) // Self-signed, so issuer = subject val serial = BigInteger(64, SecureRandom.getInstanceStrong()) val notBefore = Date.from(Instant.now()) val notAfter = Date.from(Instant.now().plus(Duration.ofDays(1825))) // matches -validity 1825 // 3. Build certificate extensions (matches -ext KU, EKU, SAN) val extensions = CertificateExtensions() // Key Usage: digitalSignature, dataEncipherment, keyEncipherment, keyAgreement val keyUsage = KeyUsage(KeyUsage.DIGITAL_SIGNATURE or KeyUsage.DATA_ENCIPHERMENT or KeyUsage.KEY_ENCIPHERMENT or KeyUsage.KEY_AGREEMENT) extensions.set(KeyUsage.NAME, keyUsage) // Extended Key Usage: serverAuth val eku = ExtendedKeyUsage(ExtendedKeyUsage.SERVER_AUTH) extensions.set(ExtendedKeyUsage.NAME, eku) // Subject Alternative Names val san = SubjectAlternativeName(serverSan.split(", ").map { sanEntry -> val (type, value) = sanEntry.split(":", limit = 2) when (type.uppercase()) { "DNS" -> GeneralName(GeneralName.DNS_NAME, value) "IP" -> GeneralName(GeneralName.IP_ADDRESS, value) else -> throw IllegalArgumentException("Unsupported SAN type: $type") } }) extensions.set(SubjectAlternativeName.NAME, san) // 4. Create self-signed X509 certificate val certInfo = X509CertInfo() certInfo.set(X509CertInfo.VERSION, CertificateVersion(CertificateVersion.V3)) certInfo.set(X509CertInfo.SERIAL_NUMBER, CertificateSerialNumber(serial)) certInfo.set(X509CertInfo.SUBJECT, subject) certInfo.set(X509CertInfo.ISSUER, issuer) certInfo.set(X509CertInfo.VALIDITY, CertificateValidity(notBefore, notAfter)) certInfo.set(X509CertInfo.KEY, CertificateX509Key(keyPair.public)) certInfo.set(X509CertInfo.ALGORITHM_ID, CertificateAlgorithmId(AlgorithmId(AlgorithmId.ECDSA_SHA256_oid))) certInfo.set(X509CertInfo.EXTENSIONS, extensions) // Sign the certificate with the private key val cert = X509CertImpl(certInfo) cert.sign(keyPair.private, "SHA256withECDSA") // 5. Store key pair and certificate in PKCS12 keystore (matches -deststoretype pkcs12) val keyStore = KeyStore.getInstance("PKCS12") keyStore.load(null, serverPw) keyStore.setKeyEntry("server", keyPair.private, serverPw, arrayOf(cert)) // matches -alias server // Write keystore to file FileOutputStream(keystorePath).use { keyStore.store(it, serverPw) } // 6. Export certificate to server.crt FileOutputStream(certPath).use { it.write(cert.encoded) } println("Certificate and keystore generated successfully!") return 0 } catch (e: Exception) { e.printStackTrace() return 1 } } }
Notes on the code:
- Sun security classes: We use
sun.security.x509.*here because standard JDK APIs don't have a built-in way to create custom X509 extensions easily. While these are internal, they're widely used and stable for certificate generation tasks. If you want to avoid them entirely, consider using a third-party library like BouncyCastle. - Replace variables: Make sure to update
serverDn,serverSan,serverPw, and file paths to match your original keytool configuration. - Quarkus compatibility: This runs as a Quarkus application, but you can adapt it to a regular Kotlin/Java app by removing the Quarkus annotations and adding a main method.
内容的提问来源于stack exchange,提问作者softshipper

