grpc/grpc-java · error · GeneralSecurityException

Unable to decode private key

Error message

Unable to decode private key

What it means

createKeyManager wraps IOException from CertificateUtils.getPrivateKey in a GeneralSecurityException 'Unable to decode private key'. The supplied private key file could not be parsed into a PrivateKey (bad format, wrong PEM type, encrypted key without password support).

Source

Thrown at okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java:720

      throws GeneralSecurityException {
    InputStream certChainStream = new ByteArrayInputStream(certChain);
    InputStream privateKeyStream = new ByteArrayInputStream(privateKey);
    try {
      return createKeyManager(certChainStream, privateKeyStream);
    } finally {
      GrpcUtil.closeQuietly(certChainStream);
      GrpcUtil.closeQuietly(privateKeyStream);
    }
  }

  static KeyManager[] createKeyManager(InputStream certChain, InputStream privateKey)
      throws GeneralSecurityException {
    X509Certificate[] chain = CertificateUtils.getX509Certificates(certChain);
    PrivateKey key;
    try {
      key = CertificateUtils.getPrivateKey(privateKey);
    } catch (IOException uee) {
      throw new GeneralSecurityException("Unable to decode private key", uee);
    }
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    try {
      ks.load(null, null);
    } catch (IOException ex) {
      // Shouldn't really happen, as we're not loading any data.
      throw new GeneralSecurityException(ex);
    }
    ks.setKeyEntry("key", key, new char[0], chain);

    KeyManagerFactory keyManagerFactory =
        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(ks, new char[0]);
    return keyManagerFactory.getKeyManagers();
  }

  static TrustManager[] createTrustManager(byte[] rootCerts) throws GeneralSecurityException {
    InputStream rootCertsStream = new ByteArrayInputStream(rootCerts);

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Convert the key to unencrypted PKCS#8: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key8.pem
  2. Verify the file is the private key, not the certificate chain, and is complete PEM
  3. Regenerate or re-export the key in PEM format from the source keystore
  4. If encrypted, decrypt it first or use a credentials API that accepts a password

Example fix

// before
TlsChannelCredentials.newBuilder().keyManager(certChain, privateKeyPkcs1Encrypted)... // decode fails
// after
// convert: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key_pkcs8.pem
TlsChannelCredentials.newBuilder().keyManager(certChain, keyPkcs8)...
Defensive patterns

Strategy: validation

Validate before calling

if (!pemContains(privateKeyPem, "PRIVATE KEY")) throw new IllegalArgumentException("file is not a PEM private key");

Try / catch

try { creds = TlsChannelCredentials.newBuilder().keyManager(cert, key).build(); } catch (GeneralSecurityException e) { if (e.getMessage().contains("Unable to decode private key")) { convertKeyToPkcs8(); creds = rebuild(); } else { throw e; } }

Prevention

When it happens

Trigger: Passing a private key to TlsChannelCredentials keyManager / createKeyManager that is DER vs PEM mismatched, encrypted (PKCS#8 with passphrase), corrupted, or actually a certificate rather than a key.

Common situations: Using PKCS#1 'BEGIN RSA PRIVATE KEY' where the parser expects PKCS#8, encrypted keys, copy-paste truncating the PEM, swapping cert and key arguments.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/7bc644ff309c3b6f. Report an issue: GitHub.