gchq/CyberChef · error · OperationError

Could not import private key: ${err}

Error message

Could not import private key: ${err}

What it means

importPrivateKey wraps its entire body in try/catch and rethrows any failure as `Could not import private key: ${err}`. This catches: (a) kbpgp failing to parse the armored key (corrupt armor, bad base64, wrong format), (b) unlock_pgp failing because the passphrase is wrong, and (c) the inner 'Did not provide passphrase' OperationError being re-caught and rewrapped - so the no-passphrase message appears nested inside this one.

Source

Thrown at src/core/lib/PGP.mjs:96

    try {
        const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
            armored: privateKey,
            opts: {
                "no_check_keys": true
            }
        });
        if (key.is_pgp_locked()) {
            if (passphrase) {
                await promisify(key.unlock_pgp.bind(key))({
                    passphrase
                });
            } else {
                throw new OperationError("Did not provide passphrase with locked private key.");
            }
        }
        return key;
    } catch (err) {
        throw new OperationError(`Could not import private key: ${err}`);
    }
}

/**
 * Import public key
 *
 * @param {string} publicKey
 * @returns {Object}
 */
export async function importPublicKey (publicKey) {
    try {
        const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
            armored: publicKey,
            opts: {
                "no_check_keys": true
            }
        });
        return key;

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Inspect the wrapped `err` in the caught exception - it distinguishes parse failure from wrong passphrase.
  2. Confirm the input is a valid OpenPGP ASCII-armored private key (BEGIN PGP PRIVATE KEY BLOCK).
  3. Verify the passphrase against the same key with gpg --list-packets or gpg --import.
  4. Trim stray whitespace/newlines from the armored text before importing.
  5. If using a locked key, ensure the passphrase is supplied (see error 107).

Example fix

// before
try {
  const key = await PGP.importPrivateKey(maybeCorruptedArmor, pw);
} catch (e) {
  console.error(e.message); // opaque wrapped message
}

// after - surface the underlying cause
try {
  const key = await PGP.importPrivateKey(armor.trim(), pw);
} catch (e) {
  const cause = e.message.replace(/^Could not import private key: /, '');
  if (/passphrase/i.test(cause)) showUser('Wrong passphrase');
  else if (/parse|armor|base64/i.test(cause)) showUser('Key is malformed');
}
Defensive patterns

Strategy: try-catch

Validate before calling

function looksLikeArmoredPrivateKey(s) {
  return /-----BEGIN PGP PRIVATE KEY BLOCK-----/.test(s) &&
         /-----END PGP PRIVATE KEY BLOCK-----/.test(s);
}

if (!looksLikeArmoredPrivateKey(armor.trim())) {
  throw new Error('Input is not an OpenPGP armored private key');
}

Try / catch

try {
  return await PGP.importPrivateKey(armor.trim(), passphrase);
} catch (e) {
  const cause = String(e.message).replace(/^Could not import private key: /, '');
  if (/passphrase/i.test(cause)) throw new Error('Wrong passphrase');
  if (/parse|armor|base64|crc/i.test(cause)) throw new Error('Malformed private key armor');
  throw new Error(cause);
}

Prevention

When it happens

Trigger: Calling importPrivateKey with a malformed armored block; with a non-OpenPGP key (OpenSSL/SSH format); with the wrong passphrase; or omitting the passphrase on a locked key (the inner throw at line 91 is caught here).

Common situations: Wrong key format (PEM/OpenSSL vs OpenPGP armor); copy-paste stripped the armor headers or checksum; typo in the passphrase; key generated by a newer/older kbpgp version with incompatible packaging; trailing whitespace/newline corruption in the armored blob.

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/b376e85681430d55. Report an issue: GitHub.