{"record":{"id":"38e7394caa3066dd","repo":"PHPOffice/PhpSpreadsheet","slug":"unable-to-unpack-data","errorCode":null,"errorMessage":"unable to unpack data","messagePattern":"unable to unpack data","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Reader\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Reader/Xls/MD5.php","lineNumber":63,"sourceCode":"        foreach (['a', 'b', 'c', 'd'] as $i) {\n            $v = $this->{$i};\n            $s .= chr($v & 0xFF);\n            $s .= chr(($v >> 8) & 0xFF);\n            $s .= chr(($v >> 16) & 0xFF);\n            $s .= chr(($v >> 24) & 0xFF);\n        }\n\n        return $s;\n    }\n\n    /**\n     * Add data to context.\n     *\n     * @param string $data Data to add\n     */\n    public function add(string $data): void\n    {\n        $unpacked = unpack('V16', $data) ?: throw new ReaderException('unable to unpack data');\n        /** @var int[] */\n        $words = array_values($unpacked);\n\n        $A = $this->a;\n        $B = $this->b;\n        $C = $this->c;\n        $D = $this->d;\n\n        $F = [self::class, 'f'];\n        $G = [self::class, 'g'];\n        $H = [self::class, 'h'];\n        $I = [self::class, 'i'];\n\n        // ROUND 1\n        self::step($F, $A, $B, $C, $D, $words[0], 7, 0xD76AA478);\n        self::step($F, $D, $A, $B, $C, $words[1], 12, 0xE8C7B756);\n        self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070DB);\n        self::step($F, $B, $C, $D, $A, $words[3], 22, 0xC1BDCEEE);","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Reader/Xls/MD5.php#L45-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"Interrupted downloads of encrypted .xls files; encrypted files corrupted in mail transfer; encrypted files whose bytes were altered by encoding conversion.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    $spreadsheet = $reader->load($path);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (str_contains($e->getMessage(), 'unable to unpack data')) {\n        // encryption payload truncated: re-obtain the file from source\n    }\n}","preventionTips":["Verify size/checksum of encrypted uploads against the source","Keep encrypted transfers on binary-safe channels","Decrypt externally when in-library decryption keeps failing"],"tags":["xls","encryption","md5","corruption","phpspreadsheet"],"backgroundTag":"truncated-file","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}