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

  1. Verify the metadata key exists in the keyring (gpg --list-keys <fingerprint>); re-import the metadata private key if missing
  2. Enable debug and rerun the migration — the wrapped exception message is prepended and shows the root cause
  3. Run MetadataKeysHealthCheck / re-import the metadata key via the server key import command
  4. 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

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


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)