PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception
Unexpected file pass record length
Error message
Unexpected file pass record length
What it means
Thrown while PhpSpreadsheet's Xls reader parses the FILEPASS record in the workbook-globals stream of a binary .xls (BIFF) file. FILEPASS marks the workbook as encrypted; the built-in RC4 decryptor expects at least 54 bytes of payload (2+2 version words, 16-byte document id, 16-byte salt, 16-byte hashed salt). A shorter record means the file uses a different encryption layout or is damaged.
Source
Thrown at src/PhpSpreadsheet/Reader/Xls.php:868
*
* This record is part of the File Protection Block. It
* contains information about the read/write password of the
* file. All record contents following this record will be
* encrypted.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*
* The decryption functions and objects used from here on in
* are based on the source of Spreadsheet-ParseExcel:
* https://metacpan.org/release/Spreadsheet-ParseExcel
*/
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);View on GitHub (pinned to 65b080eef4)
Solutions
- Open the file in Excel or LibreOffice, remove the password (File > Info > Protect Workbook / Save As without encryption) and load it again
- Re-save the workbook as unencrypted .xlsx and read that instead
- Verify the file actually opens in Excel; if not, it is corrupt and must be re-exported or repaired
- Decrypt externally (e.g. LibreOffice headless convert or an MS-OFFICE-CRYPTO tool) and point the reader at the decrypted copy
Example fix
// before
$spreadsheet = IOFactory::load('legacy-protected.xls'); // Unexpected file pass record length
// after: strip the password with LibreOffice, then read the clear copy
shell_exec('soffice --headless --convert-to xlsx --outdir /tmp legacy-protected.xls');
$spreadsheet = IOFactory::load('/tmp/legacy-protected.xlsx'); Defensive patterns
Strategy: try-catch
Try / catch
try {
$spreadsheet = $reader->load($path);
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
if (str_contains($e->getMessage(), 'Unexpected file pass record length')) {
// encrypted with an unsupported layout or corrupt: ask for a clean/re-saved file
}
} Prevention
- Reject encrypted .xls uploads up front and request unencrypted or .xlsx files
- Round-trip incoming legacy files through LibreOffice conversion as a normalization step
- Test your pipeline once with a password-protected file so the failure path is known
When it happens
Trigger: Loading an .xls saved with legacy XOR obfuscation (its FILEPASS payload is only 4 bytes), a FILEPASS record truncated by an interrupted download/transfer, or a file emitted by a non-Microsoft writer that lays out the record incorrectly.
Common situations: Password-protected files exported from old Excel 5/95-era ERP systems, Excel '97 files re-saved by LibreOffice with XOR encryption, corrupted email attachments, or files whose bytes were mangled by a text-mode FTP transfer.
Related errors
- XOr encryption not supported
- Unsupported encryption algorithm
- Decryption password incorrect
- Unrecognized space type in tAttrSpace token
- Unrecognized attribute flag in tAttr token
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/5747f6e5bc4f442d.
Report an issue: GitHub.