apache/pulsar · error · IOException

Failed to decode private key

Error message

Failed to decode private key

What it means

AuthTokenUtils.decodePrivateKey wraps any exception from building a PKCS8EncodedKeySpec and calling KeyFactory.generatePrivate (for the algorithm matching the JWT signature algorithm) into an IOException('Failed to decode private key'). It means the supplied byte[] is not a valid PKCS#8 encoded private key of the expected type.

Source

Thrown at pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/utils/AuthTokenUtils.java:64

@SuppressWarnings("deprecation")
@UtilityClass
public class AuthTokenUtils {

    public static SecretKey createSecretKey(SignatureAlgorithm signatureAlgorithm) {
        return Keys.secretKeyFor(signatureAlgorithm);
    }

    public static SecretKey decodeSecretKey(byte[] secretKey) {
        return Keys.hmacShaKeyFor(secretKey);
    }

    public static PrivateKey decodePrivateKey(byte[] key, SignatureAlgorithm algType) throws IOException {
        try {
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
            KeyFactory kf = KeyFactory.getInstance(keyTypeForSignatureAlgorithm(algType));
            return kf.generatePrivate(spec);
        } catch (Exception e) {
            throw new IOException("Failed to decode private key", e);
        }
    }


    public static PublicKey decodePublicKey(byte[] key, SignatureAlgorithm algType) throws IOException {
        try {
            X509EncodedKeySpec spec = new X509EncodedKeySpec(key);
            KeyFactory kf = KeyFactory.getInstance(keyTypeForSignatureAlgorithm(algType));
            return kf.generatePublic(spec);
        } catch (Exception e) {
            throw new IOException("Failed to decode public key", e);
        }
    }

    private static String keyTypeForSignatureAlgorithm(SignatureAlgorithm alg) {
        if (alg.getFamilyName().equals("RSA")) {
            return "RSA";
        } else if (alg.getFamilyName().equals("ECDSA")) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Convert the key to PKCS#8: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key.pkcs8.pem, then strip PEM armor and Base64-decode before calling
  2. Ensure the byte[] passed is the DER bytes, not the raw PEM text (decode Base64 body between BEGIN/END lines)
  3. Match algType to the key: RSA key for RS256/RS384/RS512, EC key for ES256/ES384/ES512
  4. Regenerate the key if corrupt/truncated and re-encode; inspect the wrapped cause 'e' for the exact crypto error

Example fix

// before
byte[] key = Files.readAllBytes(Paths.get("private.pem")); // PEM text, not DER
PrivateKey pk = AuthTokenUtils.decodePrivateKey(key, SignatureAlgorithm.RS256); // IOException
// after
String pem = Files.readString(Paths.get("private.pkcs8.pem"));
String b64 = pem.replace("-----BEGIN PRIVATE KEY-----", "")
    .replace("-----END PRIVATE KEY-----", "").replaceAll("\\s", "");
PrivateKey pk = AuthTokenUtils.decodePrivateKey(Base64.getDecoder().decode(b64), SignatureAlgorithm.RS256);
Defensive patterns

Strategy: validation

Validate before calling

// verify PKCS#8 DER before calling decodePrivateKey
static boolean isPkcs8(byte[] key) {
    // DER SEQUENCE (0x30) with PKCS#8 PrivateKeyInfo version 0 INTEGER
    return key != null && key.length > 2 && key[0] == 0x30
        && key.length > 6 && key[4] == 0x02 && key[5] == 0x01 && key[6] == 0x00;
}

Try / catch

try {
    PrivateKey pk = AuthTokenUtils.decodePrivateKey(keyBytes, alg);
} catch (IOException e) {
    if (e.getMessage().equals("Failed to decode private key")) {
        // inspect e.getCause(): InvalidKeySpecException/NoSuchAlgorithmException -> re-encode key to PKCS#8
    }
    throw new IllegalArgumentException("Key must be PKCS#8 DER for " + alg, e);
}

Prevention

When it happens

Trigger: Calling decodePrivateKey with bytes that are not PKCS#8 DER (e.g. PEM text with headers, Base64 string not decoded, or PKCS#1 'RSA PRIVATE KEY' format); algorithm mismatch between algType and the key (e.g. EC key with RS256); truncated/corrupt key file.

Common situations: Passing the raw contents of a PEM file instead of stripping the header/footer and Base64-decoding; using an openssl key generated in 'BEGIN RSA PRIVATE KEY' (PKCS#1) form; copying the secret key string where an encoded private key is expected; wrong SignatureAlgorithm configured for the token provider.

Understand the failure class

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/6ba79eae93a1d964. Report an issue: GitHub.