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
- Convert the key to unencrypted PKCS#8: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key8.pem
- Verify the file is the private key, not the certificate chain, and is complete PEM
- Regenerate or re-export the key in PEM format from the source keystore
- 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
- Always ship unencrypted PKCS#8 PEM keys
- Check PEM headers: 'BEGIN PRIVATE KEY' vs 'BEGIN RSA PRIVATE KEY'
- Open and inspect the key file before passing its path
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- TLS Provider failure
- TLS ALPN negotiation failed with protocols: ${protocols}
- We can not do TLS handshake on this Android version, please
- Can't set TLS settings for ALTS
- Unable to load OkHttpChannelProvider
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/7bc644ff309c3b6f.
Report an issue: GitHub.