elastic/elasticsearch · error · IOException

cannot read encrypted key without a password

Error message

cannot read encrypted key without a password

What it means

Thrown by possiblyDecryptPKCS1Key when the PEM is marked 'Proc-Type: 4,ENCRYPTED' and a DEK-Info header exists, but the password supplier returns null. The library cannot decrypt an OpenSSL-format encrypted key without a password, so it aborts rather than attempting a no-op decrypt.

Source

Thrown at libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java:480

     * @param passwordSupplier A password supplier for the encrypted (password protected) key
     * @return the decrypted key bytes
     * @throws GeneralSecurityException if the key can't be decrypted
     * @throws IOException              if the PEM headers are missing or malformed
     */
    private static byte[] possiblyDecryptPKCS1Key(Map<String, String> pemHeaders, String keyContents, Supplier<char[]> passwordSupplier)
        throws GeneralSecurityException, IOException {
        byte[] keyBytes = Base64.getDecoder().decode(keyContents);
        String procType = pemHeaders.get("Proc-Type");
        if ("4,ENCRYPTED".equals(procType)) {
            // We only handle PEM encryption
            String encryptionParameters = pemHeaders.get("DEK-Info");
            if (null == encryptionParameters) {
                // malformed pem
                throw new IOException("Malformed PEM File, DEK-Info header is missing");
            }
            char[] password = passwordSupplier.get();
            if (password == null) {
                throw new IOException("cannot read encrypted key without a password");
            }
            Cipher cipher = getCipherFromParameters(encryptionParameters, password);
            byte[] decryptedKeyBytes = cipher.doFinal(keyBytes);
            return decryptedKeyBytes;
        }
        return keyBytes;
    }

    /**
     * Creates a {@link Cipher} from the contents of the DEK-Info header of a PEM file. RFC 1421 indicates that supported algorithms are
     * defined in RFC 1423. RFC 1423 only defines DES-CBS and triple DES (EDE) in CBC mode. AES in CBC mode is also widely used though ( 3
     * different variants of 128, 192, 256 bit keys )
     *
     * @param dekHeaderValue The value of the DEK-Info PEM header
     * @param password       The password with which the key is encrypted
     * @return a cipher of the appropriate algorithm and parameters to be used for decryption
     * @throws GeneralSecurityException if the algorithm is not available in the used security provider, or if the key is inappropriate
     * for the cipher

View on GitHub (pinned to db6a809a66)

Solutions

  1. Configure the password: in Elasticsearch, store it with 'bin/elasticsearch-keystore add <setting>.secure_key_pass' (e.g. xpack.security.transport.ssl.secure_key_pass).
  2. Ensure the password supplier returns a non-null char[] for encrypted keys.
  3. If the key is not meant to be encrypted, regenerate it without a passphrase: 'openssl rsa -in enc.key -out plain.key'.
  4. Verify the running process has access to whatever source the password supplier reads (env var, secret store, file permissions).

Example fix

// before: supplier returns null for an encrypted key
Supplier<char[]> pw = () -> null;
PemUtils.readPrivateKey(keyPath, pw);
// after: supplier resolves the password (e.g. from a secure keystore)
Supplier<char[]> pw = () -> keystore.getCharArray("xpack.security.transport.ssl.secure_key_pass");
PemUtils.readPrivateKey(keyPath, pw);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the password supplier returns a non-null char[] before reading an encrypted key
char[] pw = passwordSupplier.get();
if (pw == null || pw.length == 0) {
    throw new IllegalStateException("Encrypted key requires a non-empty password; configure xpack.*.secure_key_pass in the keystore");
}

Try / catch

try { PemUtils.readPrivateKey(path, passwordSupplier); }
catch (IOException e) { if (e.getMessage().contains("cannot read encrypted key without a password")) { /* provision password */ } else throw e; }

Prevention

When it happens

Trigger: Calling PemUtils.readPrivateKey(path, passwordSupplier) where passwordSupplier.get() returns null for an encrypted OpenSSL-format key; the Elasticsearch SSL keystore password setting is empty or unset while the key file is encrypted.

Common situations: Misconfigured xpack.ssl.secure_key_pass / keystore.password; the password was added to the wrong keystore entry; the password supplier reads from an environment variable that is unset in the running process; deploying an encrypted key but forgetting to provision the secret.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/5c8406897c64af8f. Report an issue: GitHub.