skip to content

How do you serialize an RSA public/private key to bytes and reconstruct it in Java using KeyFactory, X509EncodedKeySpec and PKCS8EncodedKeySpec?

level: middleimportance: should knowfreq 45%

answer

  1. getEncoded() -> X.509 (public) / PKCS#8 (private), DER binary
  2. X509EncodedKeySpec for public, PKCS8EncodedKeySpec for private
  3. KeyFactory.getInstance("RSA").generatePublic/generatePrivate
  4. PEM = Base64 of the DER bytes
  5. mismatched spec -> InvalidKeySpecException

basics

~10 s

Call key.getEncoded() to get the standard byte form. Wrap public-key bytes in X509EncodedKeySpec and private-key bytes in PKCS8EncodedKeySpec, then use KeyFactory.getInstance("RSA").generatePublic/generatePrivate to turn them back into Key objects.

solid answer

~30 s

A Java Key exposes getEncoded(), giving its standard binary encoding: public keys use the X.509 SubjectPublicKeyInfo format, private keys use PKCS#8. To rebuild a key you pick the matching EncodedKeySpec - X509EncodedKeySpec for the public key, PKCS8EncodedKeySpec for the private key - and feed it to a KeyFactory for the algorithm: KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pubBytes)) and .generatePrivate(new PKCS8EncodedKeySpec(privBytes)). These bytes are DER-encoded binary; for text transport (e.g. PEM) you Base64-encode them yourself. Mixing them up is the classic error: an X509 spec on private bytes (or vice versa) throws InvalidKeySpecException. Never log or commit private-key bytes, and store them encrypted at rest.

code

java · 8 lines
java
// Encode
byte[] pubBytes  = keyPair.getPublic().getEncoded();   // X.509 DER
byte[] privBytes = keyPair.getPrivate().getEncoded();  // PKCS#8 DER

// Decode
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey  pub  = kf.generatePublic(new X509EncodedKeySpec(pubBytes));
PrivateKey priv = kf.generatePrivate(new PKCS8EncodedKeySpec(privBytes));

go deeper

for a junior

Knows getEncoded() gives bytes and that KeyFactory with the right KeySpec turns bytes back into a key.

for a middle

Pairs X509EncodedKeySpec/generatePublic for public and PKCS8EncodedKeySpec/generatePrivate for private, and knows the bytes are DER (PEM = Base64 of DER).

for a senior

Explains the X.509 vs PKCS#8 formats, the InvalidKeySpecException on mismatch, KeyFactory vs KeyPairGenerator, and secure handling of private-key bytes.

for a principal

Defines key storage/transport policy (KMS/HSM, encrypted at rest), interop with OpenSSL/PEM, and authenticity guarantees (certificates) for distributed public keys.

## Why you need this at all Key objects (`PublicKey`, `PrivateKey`) live in memory. To send a key over the network, save it to a file, or store it in a database, you must turn it into **bytes** and later turn those bytes back into a usable key. JCA standardizes both directions. ## The two standard encodings Java keys carry their bytes in well-known, language-neutral formats so other systems (OpenSSL, etc.) can read them: - **Public key -> X.509 `SubjectPublicKeyInfo`**. `publicKey.getEncoded()` returns these bytes, and `publicKey.getFormat()` returns `"X.509"`. - **Private key -> PKCS#8 `PrivateKeyInfo`**. `privateKey.getEncoded()` returns these bytes, `getFormat()` returns `"PKCS#8"`. These byte blobs are **DER** (Distinguished Encoding Rules) - a compact *binary* format. They are **not** the text PEM you see in `.pem` files; PEM is just DER **Base64-encoded** and wrapped in `-----BEGIN ...-----` headers. So to make a key human/transport-friendly you Base64-encode the DER bytes yourself. ## Encoding (key -> bytes) ```java byte[] pubBytes = keyPair.getPublic().getEncoded(); // X.509 DER byte[] privBytes = keyPair.getPrivate().getEncoded(); // PKCS#8 DER String pubPem = Base64.getEncoder().encodeToString(pubBytes); // for text transport ``` ## Decoding (bytes -> key) with KeyFactory You cannot `new` a key. You describe it with an **EncodedKeySpec** and hand it to a **KeyFactory** - the engine class that translates between key specs and key objects: ```java KeyFactory kf = KeyFactory.getInstance("RSA"); PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(pubBytes)); PrivateKey priv = kf.generatePrivate(new PKCS8EncodedKeySpec(privBytes)); ``` The **pairing is strict and must match the encoding**: - public bytes (X.509) -> `X509EncodedKeySpec` -> `generatePublic` - private bytes (PKCS#8) -> `PKCS8EncodedKeySpec` -> `generatePrivate` Mix them up - e.g. an `X509EncodedKeySpec` over private bytes, or calling `generatePublic` with a PKCS#8 spec - and you get an **`InvalidKeySpecException`**. `KeyFactory.getInstance("RSA")` can throw `NoSuchAlgorithmException`. ## Generating a key pair (for completeness) ```java KeyPairGenerator g = KeyPairGenerator.getInstance("RSA"); g.initialize(2048); KeyPair kp = g.generateKeyPair(); ``` Note `KeyPairGenerator` (creates new pairs) is different from `KeyFactory` (converts existing encoded keys back and forth) - a common naming confusion. ## Security notes - **Private-key bytes are a secret.** Never log them, never commit them, and store them encrypted at rest (or in an HSM/KMS that exposes a handle, not the raw bytes). - After use, you cannot reliably zero a Java `byte[]` from GC, but you should still avoid holding private-key material longer than necessary and avoid copying it around. - Public-key bytes are safe to distribute, but verify their authenticity (e.g. via a certificate) before trusting them - swapping in an attacker's public key defeats encryption.

  • What's the difference between KeyFactory and KeyPairGenerator?
    KeyPairGenerator creates brand-new key pairs; KeyFactory converts between existing keys and their encoded byte specs (and vice versa). Different jobs, often confused.
  • How do you produce a PEM string from a Java key?
    Take key.getEncoded() (DER bytes), Base64-encode it, and wrap it with the -----BEGIN/END PUBLIC KEY----- headers. Java's getEncoded returns DER, not PEM.

saying these in an interview costs you the question

  • Using X509EncodedKeySpec for a private key (or PKCS8 for a public key)
  • Confusing KeyFactory (decode existing keys) with KeyPairGenerator (make new ones)
  • Thinking getEncoded() returns PEM text - it returns binary DER
  • Logging, printing, or committing private-key bytes
  • Trusting a received public key without verifying its authenticity

context