passbolt/passbolt_api · critical · Cake\Core\Exception\CakeException
Decryption failed.
Error message
Decryption failed.
What it means
Thrown when the gnupg extension's decrypt() or decryptverify() call throws while decrypting an armored message with the server private key. The backend clears the decrypt keys and rethrows as 'Decryption failed.' plus the gnupg error message.
Solutions
- Confirm the private key matching the recipient of the message is in the keyring and passphrase-less: gpg --list-secret-keys
- Restore/rotate: re-import the old private key or decrypt legacy data before rotating passbolt.gpg.serverKey
- Validate the armored input (no HTML entity mangling/truncation) before calling decrypt
- Check GNUPGHOME permissions and that gpg-agent is running for the PHP user
Example fix
// before $decrypted = $gpg->decrypt($corruptedOrWrongKeyMessage); // CakeException: Decryption failed. ... // after $gpg->setDecryptKey($correctServerKeyFingerprint); $decrypted = $gpg->decrypt($validArmoredMessage);
Defensive patterns
Strategy: try-catch
Validate before calling
if (!str_contains($armored, '-----BEGIN PGP MESSAGE-----')) {
throw new InvalidArgumentException('Payload is not an armored PGP message');
}
$hasSecret = str_contains((string) shell_exec('gpg --list-secret-keys --with-colons'), $serverFingerprint); Try / catch
try {
$plain = $gpg->decrypt($armored);
} catch (CakeException $e) {
$this->log('Decrypt failed: ' . $e->getMessage());
throw new DecryptionException('Stored secret cannot be decrypted with current server key.', 0, $e);
} Prevention
- Never delete old server private keys during rotation without re-encrypting legacy data
- Keep unpassphrased server private keys in the keyring
- Preserve armored payloads verbatim (beware of encoding/HTML mangling in transport)
- Run `passbolt healthcheck` to detect keyring/key config drift
When it happens
Trigger: decrypt($text) where the ciphertext is corrupt/not valid OpenPGP, was encrypted for a different key, or the private key in the keyring is missing, passphrase-protected, or inaccessible.
Common situations: Data encrypted with a previous/rotated server key while the old private key was deleted; server key file re-imported with a passphrase; truncated or modified armored payload; keyring on read-only/incorrect-permission homedir; POST body corruption (encoding issues).
Related errors
- Can not upgrade. Please upgrade to the latest 1.x version…
- Can not upgrade. Some tables are missing.
- Could not sign the text.
- The OpenPGP server key defined in the config cannot be used…
- A value for the theme should be provided.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/4a1d68fe3c99ea16.
Report an issue: GitHub.
Appendix: source
Thrown at src/Utility/OpenPGP/Backends/Gnupg.php:398
$decrypted = false;
$fingerprint = null;
$signatureInfo = null;
$this->assertDecryptKey();
if ($verifySignature) {
$this->assertVerifyKey();
$fingerprint = $this->_verifyKeyFingerprint;
$this->clearVerifyKeys();
}
try {
if ($verifySignature === false) {
$decrypted = $this->_gpg->decrypt($text);
} else {
/** @psalm-suppress InvalidArgument */
$signatureInfo = $this->_gpg->decryptverify($text, $decrypted);
}
} catch (Exception $e) {
$this->clearDecryptKeys();
throw new CakeException(__('Decryption failed.') . ' ' . $e->getMessage(), null, $e);
}
$this->clearDecryptKeys();
if ($decrypted === false) {
throw new CakeException(__('Decryption failed.'));
}
if ($verifySignature) {
if (empty($signatureInfo) || $signatureInfo[0]['fingerprint'] !== $fingerprint) {
$msg = __('Expected {0} and got {1}.', $fingerprint, $signatureInfo[0]['fingerprint']);
$msg = __('Decryption failed. Invalid signature.') . ' ' . $msg;
throw new CakeException($msg);
}
}
return $decrypted;
}
/**View on GitHub (pinned to 31c1bbc10f)