Java keystores (JKS) demystified: aliases, keytool and moving keys to PFX or PEM
You have a file called keystore.jks, a password someone left in a wiki page, and a thing that is not Java — Nginx, a load balancer, a cloud console — asking for a certificate and a private key. keytoolwill list the keystore for you but refuses to hand over the key, and every answer on the web starts with “install a JDK”. By the end you will know what is inside the file, why a .jks is often not JKS at all, how the aliases and two passwords fit together, and how to get a certificate and key out — with or without Java — and prove they belong together.
Three formats hide behind one extension
JKS is Sun’s proprietary keystore format from the JDK 1.2 era. The file starts with the magic bytes FE ED FE ED, then a version number (2 on any JDK you will meet) and an entry count. Private keys inside are “protected” by a home-grown scheme: a SHA-1 keystream, seeded from a 20-byte salt and the password, is XORed over the key bytes (algorithm OID 1.3.6.1.4.1.42.2.17.1.1). That is not a standard cipher, and nothing outside Java reads it natively. JCEKS is the same layout with magic CE CE CE CE, Triple-DES key protection and room for symmetric secret keys. PKCS#12 is the industry-standard archive (.p12/.pfx): the first byte is 0x30, an ASN.1 SEQUENCE, and everything from OpenSSL to Windows opens it.
The confusion starts with JEP 229. From Java 9, keystore.type=pkcs12 in $JAVA_HOME/conf/security/java.security, so keytool -genkeypair without an explicit -storetype writes a PKCS#12 file whatever you name it. Nobody noticed because JDK 8 had already added keystore.type.compat=true: KeyStore.getInstance("JKS") silently loads a PKCS#12 file and vice versa. The file works in Java, so it keeps the .jks name for years — until someone points OpenSSL at it. Meanwhile any real JKS triggers this on every keytool run:
Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12
which is an industry standard format using "keytool -importkeystore -srckeystore
keystore.jks -destkeystore keystore.jks -deststoretype pkcs12".| Format | First bytes | Key protection | Read by | keytool flag |
|---|---|---|---|---|
| JKS | FE ED FE ED | SHA-1 keystream XOR (Sun proprietary) | Java; tools that reimplement it | -storetype JKS |
| JCEKS | CE CE CE CE | PBEWithMD5AndTripleDES | Java only | -storetype JCEKS |
| PKCS#12 | 30 82 | Standard PBE (3DES or AES; RC2-40 in old files) | Everything | -storetype PKCS12 (default since Java 9) |
Two commands settle which one you hold, whatever the extension says:
head -c 4 keystore.jks | xxd # feed feed = JKS, cece cece = JCEKS, 3082 .... = PKCS#12
keytool -list -keystore keystore.jks | head -2 # "Keystore type: JKS" or "Keystore type: PKCS12"Inside a keystore: aliases, entry types and two passwords
Each record has an alias — a label you chose, or that a tool chose for you (1, mykey, tomcat); JKS lowercases them, so Server and server are the same entry. A PrivateKeyEntry holds a private key andits certificate chain, leaf first: a CA-signed certificate imported under the same alias as the key becomes that entry’s chain. A trustedCertEntry holds one certificate and no key; the JDK’s own truststore, lib/security/cacerts (password changeit), is nothing but trustedCertEntries. keytool -list shows both kinds:
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 2 entries
server, 6 Sep 2026, PrivateKeyEntry,
rootca, 6 Sep 2026, trustedCertEntry,Now the passwords. The keystore password in a JKS does not encrypt anything. Certificates sit in the file in the clear; the password feeds an integrity hash — SHA-1 over the password (as UTF-16), the literal string Mighty Aphrodite, and the file contents — stored as the final 20 bytes. The key passwordis separate: each PrivateKeyEntry is protected with its own, which keytool asks for as “Enter key password for <server> (RETURN if same as keystore password)”. Both must be at least 6 characters (Keystore password must be at least 6 characters). Press RETURN and they match; anything else is the source of the “Cannot recover key” error below. A PKCS#12 keystore does not support the split at all:
Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value.keytool cheat sheet
keytool takes one verb per invocation, and the verbs mislead: -importcert both installs a CA reply and adds a trusted certificate, depending only on whether the alias already holds a key.
| Task | Command | Notes |
|---|---|---|
| Create key + self-signed cert | keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -validity 365 -keystore keystore.jks | PKCS#12 on Java 9+; add -storetype JKS to force the old format |
| List entries | keytool -list -v -keystore keystore.jks | -v adds subject, issuer and Valid from … until: |
| Make a CSR | keytool -certreq -alias server -file server.csr -keystore keystore.jks | Key stays in the store — see the CSR guide |
| Install the CA reply | keytool -importcert -trustcacerts -alias server -file server.crt -keystore keystore.jks | Same alias as the key → “Certificate reply was installed in keystore” |
| Add a trusted CA | keytool -importcert -alias rootca -file ca.crt -keystore truststore.jks | New alias → trustedCertEntry; asks Trust this certificate? [no]: |
| Export a certificate | keytool -exportcert -rfc -alias server -file server.pem -keystore keystore.jks | DER without -rfc; never the key |
| Convert JKS → PKCS#12 | keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype PKCS12 | -srcalias server moves one entry; prompts per key if key passwords differ |
The verb that does not exist is export private key. The supported route is two steps — migrate the entry into a PKCS#12, then let OpenSSL unpack that:
keytool -importkeystore -srckeystore keystore.jks -srcalias server \
-destkeystore server.p12 -deststoretype PKCS12
openssl pkcs12 -in server.p12 -nocerts -nodes -out server-key.pem # unencrypted PKCS#8 key
openssl pkcs12 -in server.p12 -nokeys -out server-chain.pem # leaf + chainIf step two fails on OpenSSL 3 with error:0308010C:digital envelope routines::unsupported, the .p12 came from an older keytool that used RC2-40 for the certificate bags; add -legacy to the openssl pkcs12 command.
Getting the key out without a JDK
Both steps need software you may not have to hand. The JKS to PFX / P12 / PEM converter implements the Sun JKS format itself, in the browser. Open or drop a .jks or .keystore (it also accepts .p12/.pfx). It reads the first byte: 0x30 means PKCS#12-inside and the file is parsed as such; FE ED FE ED takes the native JKS path; CE CE CE CE stops with This is a JCEKS keystore, which is not supported in-browser. Convert it first: keytool -importkeystore -srcstoretype JCEKS -deststoretype PKCS12 — JCEKS is the one variant it cannot open. A blank password is tried first, so a truststore that holds only certificates opens without asking; a store with a key entry prompts for the keystore password.
Once open, every alias is listed as a chip: server — 3 certificates + key for a PrivateKeyEntry, rootca — trusted certificatefor a trustedCertEntry. Selecting one shows the subject CN, the issuer (or “self-signed”) and the expiry date of each certificate inside it. A key alias offers three outputs: Create .pfx / Create .p12 after you choose a password for the new file (required — Windows and most importers reject an empty one), producing server.pfx with the alias as its friendlyName; server-chain.pem (or server.pem when the entry holds a single certificate); and server-key.pem, an unencrypted PKCS#8 BEGIN PRIVATE KEY block that Nginx and Apache load directly. A trusted alias offers only its rootca.pem, and a store with several aliases adds all-certificates.pem. The PFX uses a 3DES key bag, so old Windows builds, old keytools and OpenSSL 3 (without -legacy) all import it. The SHA-1 keystream that unlocks the key runs on your device, so the keystore and its password never leave the machine.
If you already hold a .p12 and only need PEM, PFX to PEM tries a blank and an absent password before prompting, then offers certificate.pem, ca-chain.pem and private-key.pem as separate downloads — the key unencrypted, so treat it like a password.
One case neither tool can rescue: a JKS whose key password differs from the store password. The converter uses the password you type for both roles and reports Wrong password for this keystore. when the key check fails. Fix it once with keytool -keypasswd -alias server -keystore keystore.jks on any machine with a JDK so the two match, then convert.
Where JKS still shows up, and what to change
The settings that matter are the type key and, where a store has several key entries, the alias key; set the type to match the bytes, not the extension.
Tomcat
Tomcat 8.5+ configures TLS per <Certificate> inside <SSLHostConfig>: certificateKeystoreFile, certificateKeystorePassword, certificateKeystoreType (documented default JKS; set PKCS12), certificateKeyAlias and certificateKeyPassword. It also takes PEM directly via certificateFile, certificateKeyFile and certificateChainFile. Without certificateKeyAlias, Tomcat uses the first key entry it reads.
Kafka
Brokers and clients read ssl.keystore.location, ssl.keystore.password, ssl.key.password (the key password) and ssl.keystore.type: default JKS, accepting PKCS12 and, since Kafka 2.7 (KIP-651), PEM. The truststore mirrors them as ssl.truststore.*.
Elasticsearch
xpack.security.http.ssl.keystore.path and its transport twin infer the type from the extension: .p12, .pfx or .pkcs12 is read as PKCS#12, anything else as JKS, unless …keystore.type says otherwise. It is the one place where a PKCS#12 file named .jks genuinely fails.
Spring Boot
server.ssl.key-store, server.ssl.key-store-password, server.ssl.key-store-type (JKS or PKCS12), server.ssl.key-alias and server.ssl.key-password. Spring Boot 3.1 added server.ssl.certificate and server.ssl.certificate-private-key for PEM, so new services need no keystore at all.
Android signing keys
Android Studio’s signing wizard writes a file it calls .jks; whether the bytes are JKS or PKCS#12 depends on the JDK that created it, so check the first four bytes rather than assume. apksigner (--ks, --ks-key-alias, --ks-type) and Gradle’s signingConfigs (storeFile, keyAlias, storeType) accept either. The alias is the thing not to lose.
The error messages, decoded
Keystore was tampered with, or password was incorrect — java.io.IOException from the JKS loader when the 20-byte integrity hash does not match. Almost always the store password; corrupt files are rare. The PKCS#12 loader says it differently: keystore password was incorrect, with a cause mentioning BadPaddingException: Given final block not properly padded.
Invalid keystore format — the magic bytes do not match the type the loader expected: a PEM file passed as -keystore, or a real JKS opened with -storetype PKCS12 on a JDK without compat mode. Run the xxd check and set the type to match.
Alias <server> does not exist — keytool -list shows the real names; JKS lowercases them, and keytool’s default alias is mykey.
Cannot recover key — java.security.UnrecoverableKeyException at server start-up (Tomcat, Kafka, Jetty) or from keytool. The store opened; the key password is wrong. Supply it (certificateKeyPassword, ssl.key.password, server.ssl.key-password) or run keytool -keypasswd so it matches the store password.
Failed to establish chain from reply — -importcert could not link the CA-signed certificate to a trusted root because the intermediate is in neither the keystore nor cacerts. Import the intermediate as a trusted entry first, then repeat the reply import under the key’s alias. The incomplete-chain guide covers the client-side symptoms.
No error, but clients reject the certificate — keytool never refuses to store an expired certificate, and neither do the converters. The JKS and PFX tools print a validity warning above the downloads, of the form "www.example.com" EXPIRED 41 days ago (on 27/07/2026)., or expires in 12 days when under 30 days remain. The expired-certificate guide covers the renewal path.
Common mistakes
- Importing the CA’s reply under a new alias.
-importcert -alias newcertmakes a trustedCertEntry; the key alias keeps its self-signed certificate, and that is what the server presents. The reply must go in under the alias that holds the key. - Renaming
.jksto.p12(or back). Java’s compat mode hides the mismatch; OpenSSL, Elasticsearch’s extension sniffing and Windows do not. Renaming is only right when the bytes already match. - Several PrivateKeyEntries and no alias setting. Tomcat serves the first key it reads, which is how a forgotten test alias ends up in production. After migrating, delete stale entries or set the alias explicitly.
- Splitting the passwords.Typing anything other than RETURN at “Enter key password” creates the one JKS that no converter and no default server config can open.
- Expecting the chain in the trusted entries.Intermediates imported as trustedCertEntries are not sent to clients; only the chain attached to the PrivateKeyEntry is. The converter’s
server-chain.pemis exactly that attached chain, leaf first.
Verify after migration
Before touching the server, drop server-chain.pem and server-key.pem on the certificate ↔ key matcher. Each file lands in its own box by its BEGIN line; the tool derives the public key from both sides — RSA or EC, reading the first certificate block of a chain file — compares the DER bytes and prints Everything matches — these belong to the same key pair. or a red MISMATCH naming the pair that disagrees. Paste the CSR too if you have it: a certificate that matches the CSR but not the key means you exported the wrong alias. The matcher accepts PEM only; a .pfx dropped on it answers binary formats (.pfx/.der) need converting to PEM first.
Then check dates: keytool -list -v -keystore keystore.jks | grep untilprints every certificate’s expiry, including trusted entries you forgot; the converters warn only about the entry you exported. Finally delete the unencrypted -key.pem copies you no longer need and chmod 600 the one the server reads. The PFX guide covers the archive itself, and the formats guide the rest of the zoo.
Do this
- Check the first four bytes before anything else:
FE ED FE EDis JKS,CE CE CE CEis JCEKS,30 82is PKCS#12 in a.jkscostume. - Run
keytool -list(or open the file in the JKS converter) to learn the aliases and which are PrivateKeyEntry versus trustedCertEntry. - Extract with the JKS converter — per-alias PFX/P12 or PEM — or with
-importkeystore -deststoretype PKCS12followed byopenssl pkcs12 -nodes; keytool alone will never give you the key. - If you hit
Cannot recover keyorWrong password for this keystore., align the passwords withkeytool -keypasswdand try again. - Prove the exported certificate and key match, read the expiry warning, then update the type and alias settings to match the new file.
Frequently asked questions
How do I convert a JKS file to PFX without installing Java?
Open the .jks in the JKS to PFX converter on this site. It reads the Sun JKS format natively in your browser, lists every alias, and repacks the key entry you pick — private key plus its chain — into a .pfx or .p12 with a password you choose. Nothing is uploaded.
Can keytool export a private key from a keystore?
Not directly — keytool -exportcert only writes the certificate. The supported route is keytool -importkeystore -deststoretype PKCS12 to make a .p12, then openssl pkcs12 -nocerts -nodes to write the key as PEM. The JKS converter does both steps in one go.
Why does keytool say my JKS keystore uses a proprietary format?
Since Java 9 the default keystore type is PKCS#12 and keytool warns whenever it touches a real JKS file. The warning is advisory: the file still works. Convert it with keytool -importkeystore -deststoretype pkcs12 when convenient, or leave it if the software reading it is older.
What is the difference between the keystore password and the key password?
The keystore password protects the integrity of the whole file (and, in PKCS#12, encrypts it); the key password encrypts one PrivateKeyEntry. keytool lets them differ in JKS, which produces the "Cannot recover key" error later. In a PKCS#12 keystore Java uses one password for both roles.
Is JKS deprecated?
Not removed, but discouraged: JDK 9 made PKCS#12 the default (JEP 229) and keytool prints a migration warning for JKS. Current JDKs still read and write JKS, so old keystores keep working.
Tools used in this guide
Every one of these runs in your browser — the files you work on never leave your device.