usebruno/bruno · error · Error

AES256 decryption failed: ${fallbackErr.message}

Error message

AES256 decryption failed: ${fallbackErr.message}

What it means

Thrown by aes256Decrypt when both the new SHA256-key decryption and the legacy MD5-based deriveKeyAndIv decryption fail. The two-step attempt exists to read blobs encrypted before the key-derivation changed. Failure means the data could not be unwrapped with either the current or the legacy scheme using the supplied key.

Source

Thrown at packages/bruno-electron/src/utils/encryption.js:65

  const iv = Buffer.alloc(16, 0); // Default IV for new encryption
  const key = crypto.createHash('sha256').update(rawKey).digest(); // Derive a 32-byte key

  try {
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let decrypted = decipher.update(data, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  } catch (err) {
    // If decryption fails, fall back to old key derivation
    try {
      const { key: oldKey, iv: oldIv } = deriveKeyAndIv(rawKey, 32, 16);
      const decipher = crypto.createDecipheriv('aes-256-cbc', oldKey, oldIv);
      const decrypted = decipher.update(data, 'hex', 'utf8');
      decrypted += decipher.final('utf8');
      return decrypted;
    } catch (fallbackErr) {
      console.error('AES256 decryption failed with both methods:', err, fallbackErr);
      throw new Error('AES256 decryption failed: ' + fallbackErr.message);
    }
  }
}

// electron safe storage encryption and decryption functions
function safeStorageEncrypt(str) {
  let encryptedStringBuffer = safeStorage.encryptString(str);

  // Convert the encrypted buffer to a hexadecimal string
  const encryptedString = encryptedStringBuffer.toString('hex');

  return encryptedString;
}
function safeStorageDecrypt(str) {
  try {
    // Convert the hexadecimal string to a buffer
    const encryptedStringBuffer = Buffer.from(str, 'hex');

View on GitHub (pinned to 9bdd81c7bd)

Solutions

  1. Ensure the same passkey used to encrypt is supplied to decrypt (for AES256_ALGO passkey-protected values).
  2. For machine-bound secrets, decrypt on the originating machine or re-encrypt with an explicit passkey before migrating.
  3. If the value is irrecoverable, treat it as lost and re-enter the secret through the UI.
  4. Verify the stored hex is intact (even length, no whitespace).

Example fix

// before
try {
  // new method
} catch (err) {
  try {
    // legacy method
  } catch (fallbackErr) {
    throw new Error('AES256 decryption failed: ' + fallbackErr.message);
  }
}

// after: prefer decryptStringSafe at call sites
const { success, value, error } = decryptStringSafe(stored);
if (!success) return { recoverable: false, reason: error };
Defensive patterns

Strategy: fallback

Validate before calling

const { success, value, error } = decryptStringSafe(stored);
if (!success) {
  // wrong passkey or wrong machine; re-prompt the user
  return await promptUserToReenterSecret();
}

Type guard

function looksLikeAes256Cipher(str) {
  return typeof str === 'string' && str.startsWith('$01:') && str.length > 4 && str.length % 2 === 0;
}

Try / catch

try {
  return aes256Decrypt(data, passkey);
} catch (err) {
  if (err.message.startsWith('AES256 decryption failed')) {
    // value is irrecoverable on this machine/passkey; re-create the secret
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing the wrong passkey (or the wrong machineIdSync-derived key) for an AES256_ALGO ('$01:...') blob; data corrupted/truncated; value migrated across machines whose machine-id differs and no passkey was supplied.

Common situations: Copying encrypted secrets between machines without the originating machine-id; passkey mismatch after a credentials reset; partial hex corruption from a bad copy/paste; version downgrade trying to read data encrypted by a newer scheme.

Related errors


AI-assisted analysis of usebruno/bruno@9bdd81c7bd (2026-08-13). Data as JSON: /api/errors/ac37757c99a2e751. Report an issue: GitHub.