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

XOr encryption not supported

Error message

XOr encryption not supported

What it means

The Xls (BIFF) reader can decrypt workbooks using no encryption or RC4-based encryption, but deliberately refuses the legacy XOR 'weak encryption' obfuscation used by very old Excel versions and old third-party writers. When the workbook globals declare MS_BIFF_CRYPTO_XOR, record decryption throws this unsupported-feature exception mid-load.

Source

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

            $this->rc4Key->RC4(str_repeat("\0", $step));

            // Decrypt record data (re-keying at the end of every block)
            while ($block != $endBlock) {
                $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
                $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
                $data = substr($data, $step);
                $pos += $step;
                $len -= $step;
                ++$block;
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
            }
            $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));

            // Keep track of the position of this decryptor.
            // We'll try and re-use it later if we can to speed things up
            $this->rc4Pos = $pos + $len;
        } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
            throw new Exception('XOr encryption not supported');
        }

        return $recordData;
    }

    /**
     * Use OLE reader to extract the relevant data streams from the OLE file.
     */
    protected function loadOLE(string $filename): void
    {
        // OLE reader
        $ole = new OLERead();
        // get excel data,
        $ole->read($filename);
        // Get workbook data: workbook stream + sheet streams
        $this->data = $ole->getStream($ole->wrkbook) ?? '';
        // Get summary information data
        $this->summaryInformation = $ole->getStream($ole->summaryInformation);

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Open the file in Excel or LibreOffice and re-save it either unprotected or with modern (RC4/AES) encryption, then load the copy
  2. Strip protection upstream where lawful: LibreOffice headless conversion of the decrypted source (soffice --headless --convert-to xlsx) then use the Xlsx reader
  3. Pre-screen archives for encrypted workbooks and exclude them from automated ingestion with a clear report rather than a crash

Example fix

// before
$spreadsheet = IOFactory::load('legacy-protected.xls'); // XOR-encrypted -> 'XOr encryption not supported'

# after (shell): decrypt/re-save once with the password, then
soffice --headless --convert-to xlsx legacy-protected.xls
// then in PHP
$spreadsheet = IOFactory::load('legacy-protected.xlsx');
Defensive patterns

Strategy: fallback

Try / catch

try {
    $spreadsheet = IOFactory::load($path);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
    if (str_contains($e->getMessage(), 'XOr encryption not supported')) {
        // legacy weak encryption: convert with the password holder once, e.g.
        // soffice --headless --convert-to xlsx --outdir fixed "$path"
        // then IOFactory::load('fixed/...xlsx')
    }
    throw $e;
}

Prevention

When it happens

Trigger: Loading a password-protected .xls saved with legacy XOR/weak encryption (Excel 5/95 era or old Delphi/VB/PERL tooling); corporate archives containing pre-97 protected workbooks.

Common situations: Legacy document migration projects; compliance extracts of decades-old spreadsheets; files generated by ancient export libraries.

Related errors


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