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

unable to unpack data

Error message

unable to unpack data

What it means

During RC4 password verification the reader feeds 64-byte blocks into a pure-PHP MD5 implementation; unpack('V16', ...) returned false because a block was shorter than 64 bytes. That only happens when the FILEPASS-derived material is truncated, i.e. the encryption payload did not really contain the promised 54+ bytes of usable data.

Source

Thrown at src/PhpSpreadsheet/Reader/Xls/MD5.php:63

        foreach (['a', 'b', 'c', 'd'] as $i) {
            $v = $this->{$i};
            $s .= chr($v & 0xFF);
            $s .= chr(($v >> 8) & 0xFF);
            $s .= chr(($v >> 16) & 0xFF);
            $s .= chr(($v >> 24) & 0xFF);
        }

        return $s;
    }

    /**
     * Add data to context.
     *
     * @param string $data Data to add
     */
    public function add(string $data): void
    {
        $unpacked = unpack('V16', $data) ?: throw new ReaderException('unable to unpack data');
        /** @var int[] */
        $words = array_values($unpacked);

        $A = $this->a;
        $B = $this->b;
        $C = $this->c;
        $D = $this->d;

        $F = [self::class, 'f'];
        $G = [self::class, 'g'];
        $H = [self::class, 'h'];
        $I = [self::class, 'i'];

        // ROUND 1
        self::step($F, $A, $B, $C, $D, $words[0], 7, 0xD76AA478);
        self::step($F, $D, $A, $B, $C, $words[1], 12, 0xE8C7B756);
        self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070DB);
        self::step($F, $B, $C, $D, $A, $words[3], 22, 0xC1BDCEEE);

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Verify byte integrity: re-download/re-export and compare size and (ideally) checksum with the source
  2. Confirm the file opens (with its password) in Excel; if not, it is corrupt — obtain a fresh copy
  3. Decrypt with an external tool to sidestep the in-library verification path
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $spreadsheet = $reader->load($path);
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    if (str_contains($e->getMessage(), 'unable to unpack data')) {
        // encryption payload truncated: re-obtain the file from source
    }
}

Prevention

When it happens

Trigger: A FILEPASS record long enough to pass the length check but with a damaged/truncated body, or a file cut off mid-stream so substr() reads run past real data.

Common situations: Interrupted downloads of encrypted .xls files; encrypted files corrupted in mail transfer; encrypted files whose bytes were altered by encoding conversion.

Related errors


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