PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception

Decryption password incorrect

Error message

Decryption password incorrect

What it means

The RC4 password-verification step (document-id + salt + hashed-salt check ported from Spreadsheet-ParseExcel) failed for the configured password. PhpSpreadsheet defaults to 'VelvetSweatshop', the password Excel itself uses when a file is flagged encrypted without a user-supplied password; a file with a real password rejects that default.

Source

Thrown at src/PhpSpreadsheet/Reader/Xls.php:880

     */
    protected function readFilepass(): void
    {
        $length = self::getUInt2d($this->data, $this->pos + 2);

        if ($length < 54) {
            throw new Exception('Unexpected file pass record length');
        }

        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);

        // move stream pointer to next record
        $this->pos += 4 + $length;

        if (substr($recordData, 0, 2) !== "\x01\x00" || substr($recordData, 4, 2) !== "\x01\x00") {
            throw new Exception('Unsupported encryption algorithm');
        }
        if (!$this->verifyPassword($this->encryptionPassword, substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
            throw new Exception('Decryption password incorrect');
        }

        $this->encryption = self::MS_BIFF_CRYPTO_RC4;

        // Decryption required from the record after next onwards
        $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2);
    }

    /**
     * Make an RC4 decryptor for the given block.
     *
     * @param int $block Block for which to create decrypto
     * @param string $valContext MD5 context state
     */
    private function makeKey(int $block, string $valContext): Xls\RC4
    {
        $pwarray = str_repeat("\0", 64);

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Call setEncryptionPassword('the-real-password') on the Xls reader instance before load()
  2. Trim the password string and confirm it in Excel (open + 'File > Info > Protect Workbook') to rule out typos
  3. If the file opens in Excel without prompting, let the 'VelvetSweatshop' default apply — but if it still throws, the salt bytes are damaged: re-save the file from Excel

Example fix

// before
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
$spreadsheet = $reader->load('protected.xls'); // Decryption password incorrect

// after
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
$reader->setEncryptionPassword(trim($userSuppliedPassword));
$spreadsheet = $reader->load('protected.xls');
Defensive patterns

Strategy: validation

Validate before calling

$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
if ($knownPassword !== null && $knownPassword !== '') {
    $reader->setEncryptionPassword(trim($knownPassword));
}
$spreadsheet = $reader->load($path);

Try / catch

try {
    $spreadsheet = $reader->load($path);
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    if (str_contains($e->getMessage(), 'Decryption password incorrect')) {
        // re-prompt the user for the password and retry once
    }
}

Prevention

When it happens

Trigger: Calling load() on a password-protected .xls without first calling setEncryptionPassword(), or passing a wrong/typo'd password; also a corrupt salt can make even the correct password fail verification.

Common situations: Uploading password-protected workbooks from users while the import pipeline assumes no password; password changed by the file owner; copy-paste of the password with trailing whitespace.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/0bcf3f07271ded71. Report an issue: GitHub.