The JKS format has no published specification; it is defined by the code that writes
it, sun.security.provider.JavaKeyStore and
sun.security.provider.KeyProtector in the OpenJDK source, which this page
follows (the JCEKS section follows com.sun.crypto.provider.JceKeyStore and
com.sun.crypto.provider.KeyProtector). Numbers are big-endian (“high byte
first”), as Java’s DataOutputStream writes them; strings are written with
writeUTF: a 2-byte length then modified UTF-8 (java.io.DataOutputStream
documentation).
File
| Field | Type | Description |
|---|---|---|
| Magic | int | FE ED FE ED. |
| Version | int | 2 (engineStore always writes the latest version); 1 is still read. Version 1 has no certificate type strings, and engineLoad reads every certificate with an X.509 certificate factory. |
| Count | int | Number of entries. |
| Entries | – | See entries. |
| Integrity hash | 20 bytes | See integrity. |
Entries
Every entry starts:
| Field | Type | Description |
|---|---|---|
| Tag | int | 1 private key entry; 2 trusted certificate entry. |
| Alias | UTF | The entry’s name, unique in the keystore. The JKS keystore type lower-cases aliases (alias.toLowerCase(Locale.ENGLISH) in convertAlias), so they are stored in lower case; the CaseExactJKS variant in the same file keeps them as given. |
| Date | long | The entry creation date, in milliseconds since 1970 (Instant.toEpochMilli()). |
A private key entry (tag 1) continues:
| Field | Type | Description |
|---|---|---|
| Key length | int | – |
| Protected key | bytes | A DER EncryptedPrivateKeyInfo; see key protection. |
| Chain length | int | Certificates in the chain, 0 if none. |
| Certificates | – | For each: the type (UTF, such as X.509, which is what java.security.cert.X509Certificate reports; version 2 only), the length (int) and the encoded certificate (getEncoded(), DER for X.509). |
A trusted certificate entry (tag 2) continues with one certificate: type (version 2 only), length and bytes, with no chain count.
Key protection
The private key, encoded as PKCS #8, is protected by Sun’s own scheme
(KeyProtector.protect) and wrapped in an EncryptedPrivateKeyInfo whose
algorithm is the OID KnownOIDs.JAVASOFT_JDKKeyProtector,
1.3.6.1.4.1.42.2.17.1.1 (sun.security.util.KnownOIDs). The
encrypted data is:
| Part | Size | Description |
|---|---|---|
| Salt | 20 | Random. |
| Encrypted key | key length | The PKCS #8 key XORed with a key stream: SHA-1(password ‖ salt), then SHA-1(password ‖ previous digest), and so on, cut to the key’s length. |
| Check | 20 | SHA-1(password ‖ plaintext key); a wrong password gives a different value. |
The password is taken as its UTF-16 characters, each as two bytes, high byte first
(convertToBytes in JavaKeyStore). The scheme uses one SHA-1
per 20 bytes of key and no iteration count; JavaKeyStore itself warns that
“JKS uses outdated cryptographic algorithms”. Java changed its default keystore type
from JKS to PKCS #12 in Java 9 (JEP 229, Create PKCS12 Keystores by
Default).
Integrity
The last 20 bytes are SHA-1 of: the password (UTF-16 big-endian, as above), the
UTF-8 bytes of the text Mighty Aphrodite, then every byte of the file
before the hash (getPreKeyedHash and engineStore). When a
password is given, engineLoad checks it and fails with “Keystore was
tampered with, or password was incorrect”.
JCEKS
The related JCEKS keystore (com.sun.crypto.provider.JceKeyStore) uses
the magic CE CE CE CE and the same layout, and its loader also accepts the
JKS magic. Aliases are lower-cased the same way. It adds a third entry type, tag 3, a
secret key entry: alias, date, then a SealedObject written with Java
object serialisation. Private keys are protected by
com.sun.crypto.provider.KeyProtector with Sun’s
PBEWithMD5AndTripleDES, an 8-byte random salt and an iteration count of
200,000 by default (set by the jdk.jceks.iterationCount system property),
and wrapped in an EncryptedPrivateKeyInfo with the OID
1.3.6.1.4.1.42.2.19.1 (KnownOIDs.JAVASOFT_JCEKeyProtector).
The closing keyed SHA-1 hash is made the same way as in JKS.
Example
00000000 FE ED FE ED 00 00 00 02 00 00 00 01 00 00 00 02 ................
00000010 00 06 6D 79 63 65 72 74 00 00 01 8B CF E5 68 7B ..mycert......h{
00000020 00 05 58 2E 35 30 39 00 00 03 5A 30 82 03 56 … ..X.509...Z0..V
Reading it: magic, version 2, one entry; tag 2, a trusted certificate; alias
mycert; date 0x18BCFE5687B, 1,700,000,000,123 ms, which is
14 November 2023 22:13:20.123 UTC; type X.509; length 858; then the DER
certificate, starting 30 82 03 56.