passbolt/passbolt_api · error · Cake\Core\Exception\CakeException
Decryption failed. Invalid signature. Expected
Error message
Decryption failed. Invalid signature. Expected {0} and got {1}. What it means
Thrown when decrypt($text, true) succeeds but the signature embedded in the message does not match the verify key fingerprint set via setVerifyKey. It reports 'Decryption failed. Invalid signature. Expected {0} and got {1}.' — the message decrypted, but was not signed by the expected key.
Solutions
- Compare the 'got' fingerprint in the message with your configured verify key; if the server key rotated, set the new/old key accordingly via setVerifyKey before decrypt
- If legitimate, re-sign or re-encrypt the data with the current server key
- If the sender should differ, pass the expected signer's fingerprint as verify key
- Treat as integrity failure — do not trust the decrypted content in security-sensitive flows
Example fix
// before $gpg->setVerifyKey($newServerKeyFingerprint); $plain = $gpg->decrypt($msgSignedWithOldKey, true); // Invalid signature. Expected new, got old // after $gpg->setVerifyKey($oldSigningKeyFingerprint); // fingerprint that actually signed $plain = $gpg->decrypt($msgSignedWithOldKey, true);
Defensive patterns
Strategy: try-catch
Validate before calling
// caller should know the expected signer fingerprint and compare
$expectedSigner = $this->getExpectedSignerFingerprint($context);
if ($expectedSigner !== $configuredVerifyFingerprint) {
$gpg->setVerifyKey($expectedSigner);
} Try / catch
try {
$plain = $gpg->decrypt($armored, true);
} catch (CakeException $e) {
if (str_contains($e->getMessage(), 'Invalid signature')) {
$this->log('Signature from unexpected key — possible key rotation or tampering');
}
throw $e;
} Prevention
- Track which key signed each record when server keys rotate
- Keep old server public keys in the keyring for verification of legacy data
- Always verify signatures on security-sensitive payloads and fail closed
- Monitor fingerprint mismatch logs for tampering signals
When it happens
Trigger: decrypt with $verifySignature = true where signatureInfo is empty (message unsigned) or signatureInfo[0]['fingerprint'] differs from $this->_verifyKeyFingerprint.
Common situations: Verifying that a token/message was signed by the server itself but the data was signed by a user key or a different server key (after key rotation); verifying data signed by an older server key not set as verify key; attacker-substituted or replayed payloads.
Related errors
- The message cannot be verified.
- The OpenPGP server key defined in the config cannot be used…
- Can not verify without a key. Set a verification key first.
- The key provided does not belong to given user.
- There is an issue with the OpenPGP server key. The…
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/ac084940a22c0da7.
Report an issue: GitHub.
Appendix: source
Thrown at src/Utility/OpenPGP/Backends/Gnupg.php:409
$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;
}
/**
* Verify a signed message.
*
* @param string $signedText The signed message to verify.
* @param string|null $plainText (optional) if this parameter is passed, it will be filled with the plain text.
* @return array signature information
* @throws \Cake\Core\Exception\CakeException If the armored signed message cannot be verified.
*/
public function verify(string $signedText, ?string &$plainText = null): array
{
$this->assertVerifyKey();
$msg = __('The message cannot be verified.');View on GitHub (pinned to 31c1bbc10f)