usebruno/bruno · error · Error

AES256 decryption failed

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.

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.

Appendix: 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)