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
- Verify byte integrity: re-download/re-export and compare size and (ideally) checksum with the source
- Confirm the file opens (with its password) in Excel; if not, it is corrupt — obtain a fresh copy
- 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
- Verify size/checksum of encrypted uploads against the source
- Keep encrypted transfers on binary-safe channels
- Decrypt externally when in-library decryption keeps failing
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
- Unexpected file pass record length
- Unsupported encryption algorithm
- Decryption password incorrect
- File appears to be corrupt
- XOr encryption not supported
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/38e7394caa3066dd.
Report an issue: GitHub.