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
- Ensure the secret contains the full armored block verbatim, including -----BEGIN PGP PRIVATE KEY BLOCK----- and -----END PGP PRIVATE KEY BLOCK----- lines
- Validate locally before injecting: echo "$KEY" | gpg --list-packets must succeed
- Use proper multiline secret storage (GitHub Actions secrets, Vault kv with multiline support) rather than joining lines manually
- 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
- Store armored keys as true multiline secrets; never join lines manually
- Self-check every signing secret with gpg --list-packets before the build step
- Always inspect the cause (PGPException/IOException) to pinpoint armor vs base64 corruption
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
- Unable to read secret key from {} (it may not be a PGP secre
- property '{}' could not be found on project and is needed fo
- Unable to generate signature for '%s' as no signatory is ava
- Unable to generate signature for '%s' as no signature type h
- Invalid args ({}: {})
AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22).
Data as JSON: /api/errors/4c6d0129cc25f894.
Report an issue: GitHub.