gradle/gradle · error · InvalidUserDataException

Could not read PGP secret key

Error message

Could not read PGP secret key

What it means

parseSecretKey wraps its parsing stream in try-with-resources; any IOException or PGPException while reading the in-memory key data is rethrown as InvalidUserDataException 'Could not read PGP secret key' with the original as cause. The bytes could not be decoded as an OpenPGP secret key at all.

Source

Thrown at platforms/software/signing/src/main/java/org/gradle/plugins/signing/signatory/internal/pgp/PgpSignatoryUtil.java:181

     *
     * @param keyId the id of the key, can be null to return the only master secret key
     * @param keyData the ASCII-armored representation of the key
     * @return the parsed secret key
     */
    public static PGPSecretKey parseSecretKey(@Nullable String keyId, String keyData) {
        try (InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(keyData.getBytes(UTF_8)))) {
            if (keyId == null) {
                return new JcaPGPSecretKeyRing(in).getSecretKey();
            } else {
                PgpKeyId expectedKeyId = new PgpKeyId(keyId);
                PGPSecretKey key = findSecretKey(new JcaPGPSecretKeyRingCollection(in), expectedKeyId);
                if (key != null) {
                    return key;
                }
                throw new InvalidUserDataException(String.format("Cannot find key with id '%s' in key data",  keyId));
            }
        } catch (IOException | PGPException e) {
            throw new InvalidUserDataException("Could not read PGP secret key", e);
        }
    }
}

View on GitHub (pinned to 534f27719b)

Solutions

  1. Ensure the secret contains the full armored block verbatim, including -----BEGIN PGP PRIVATE KEY BLOCK----- and -----END PGP PRIVATE KEY BLOCK----- lines
  2. Validate locally before injecting: echo "$KEY" | gpg --list-packets must succeed
  3. Use proper multiline secret storage (GitHub Actions secrets, Vault kv with multiline support) rather than joining lines manually
  4. Check the cause chain: the wrapped PGPException/IOException usually names the exact decode failure

Example fix

# before: CI collapses newlines -> "Could not read PGP secret key"

# after: store and pass the full armored block, then self-check before the build
- name: Verify signing key
  run: printf '%s' "$ORG_GRADLE_PROJECT_signingKey" | gpg --list-packets >/dev/null
- run: ./gradlew publish
Defensive patterns

Strategy: try-catch

Validate before calling

// fail early in CI if the secret is mangled
- run: printf '%s' "$SIGNING_KEY" | gpg --list-packets > /dev/null

Try / catch

try {
    def key = PgpSignatoryUtil.parseSecretKey(keyId, secretKeyString)
} catch (InvalidUserDataException e) {
    def cause = e.cause?.message ?: ''
    throw new GradleException("Injected PGP key is unreadable (${cause}); check multiline escaping of the secret", e)
}

Prevention

When it happens

Trigger: Secret key string with a mangled armored block (missing BEGIN/END lines, folded newlines in CI env vars, escaped \n instead of real newlines, base64 corruption, HTML-escaped output pasted from a web UI).

Common situations: CI secret variables that collapse multiline values; copying an armored key from a chat/wiki that wraps lines; double-encoding (base64 of base64); truncation at a size limit of the secrets manager.

Related errors


AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22). Data as JSON: /api/errors/4c6d0129cc25f894. Report an issue: GitHub.