passbolt/passbolt_api · error · InternalErrorException
The metadata could not be encrypted with the metadata key id
Error message
The metadata could not be encrypted with the metadata key id: {0}. What it means
The shared-resource migration path encrypts metadata with the server metadata key. If setEncryptKeyWithMetadataKey or encrypt() fails (key missing from keyring, invalid key data), the underlying exception is rethrown as InternalErrorException with this message identifying the failing metadata key id.
Solutions
- Verify the metadata key exists in the keyring (gpg --list-keys <fingerprint>); re-import the metadata private key if missing
- Enable debug and rerun the migration — the wrapped exception message is prepended and shows the root cause
- Run MetadataKeysHealthCheck / re-import the metadata key via the server key import command
- Validate the metadata_keys row (fingerprint, armored_key) is intact; restore from backup if corrupted
Example fix
// before $gpg = $this->setEncryptKeyWithMetadataKey($gpg, $metadataKey); // throws if key not importable // after: ensure key is in keyring first $this->getMetadataKeysService()->importMetadataKeyInKeyring($metadataKey); $gpg = $this->setEncryptKeyWithMetadataKey($gpg, $metadataKey);
Defensive patterns
Strategy: try-catch
Validate before calling
$key = $this->MetadataKeys->find()
->where(['id' => $metadataKey->id, 'deleted' => false])
->firstOrFail();
if (!is_string($key->fingerprint) || strlen($key->fingerprint) !== 40) {
throw new \LogicException('Metadata key fingerprint missing');
}
// ensure import
$this->getMetadataKeysService()->importMetadataKeyInKeyring($key); Type guard
function isUsableMetadataKey(\Passbolt\Metadata\Model\Entity\MetadataKey $k): bool {
return $k->deleted === false && $k->expired === null
&& is_string($k->fingerprint) && strlen($k->fingerprint) === 40
&& is_string($k->armoredKey);
} Try / catch
try {
$gpg = $this->setEncryptKeyWithMetadataKey($gpg, $metadataKey);
$metadataEncrypted = $gpg->encrypt($metadataClearText, true);
} catch (\Exception $e) {
// re-import key into keyring, then retry once
} Prevention
- Back up and restore the GnuPG keyring together with the database during server migrations
- Run metadata key health checks before bulk v5 migrations
- Never delete/expire the active metadata key while migrations are pending
When it happens
Trigger: migrate() -> migrateShared() loads the active metadata key, calls setEncryptKeyWithMetadataKey() then encrypt(); occurs when the metadata private key is absent from the server keyring, the key data fails assertMetadataKey validation, or the key is expired/revoked.
Common situations: Server restored without the metadata private key in its keyring; metadata key rotated and old fingerprint no longer resolvable; GnuPG keyring permission problems under the web-server user; corrupted metadata_keys row.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- The metadata could not be encrypted with the metadata key…
- 500
- Could not use the key to encrypt.
- Could not validate metadata key data.
- The metadata could not be encrypted with the user id: .
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/f1c704e76dbf6722.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/Metadata/src/Service/Migration/MigrateAllV4ResourcesToV5Service.php:216
* @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no metadata key record.
* @throws \Cake\Http\Exception\InternalErrorException When resource type mapping is does not exist.
*/
private function migrateShared(MetadataResourceDto $dto, Resource $resource): void
{
$metadataArray = $dto->getClearTextMetadata();
$metadataKey = $this->getMetadataKeyForEncryption();
try {
$gpg = OpenPGPBackendFactory::get();
$gpg->clearKeys();
$gpg = $this->setSignKeyWithServerKey($gpg);
$gpg = $this->setEncryptKeyWithMetadataKey($gpg, $metadataKey);
$metadataClearText = json_encode($metadataArray, JSON_THROW_ON_ERROR);
$metadataEncrypted = $gpg->encrypt($metadataClearText, true);
} catch (Exception $exception) {
$msg = $exception->getMessage() . ' ';
$msg .= __('The metadata could not be encrypted with the metadata key id: {0}.', $metadataKey->id);
throw new InternalErrorException($msg, 500, $exception);
}
$this->updateResource($resource, [
'name' => null,
'username' => null,
'uri' => null,
'description' => null,
'resource_type_id' => $this->getV5ResourceType($resource->resource_type_id),
'metadata' => $metadataEncrypted,
'metadata_key_id' => $metadataKey->id,
'metadata_key_type' => 'shared_key',
//TODO support nullable resource.modified_by to allow server side modification
//'modified_by' => null,
]);
}
/**
* @param string $v4ResourceTypeId V4 Resource type identifier to get mapping from.View on GitHub (pinned to 31c1bbc10f)