{"record":{"id":"0bcf3f07271ded71","repo":"PHPOffice/PhpSpreadsheet","slug":"decryption-password-incorrect","errorCode":null,"errorMessage":"Decryption password incorrect","messagePattern":"Decryption password incorrect","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Reader\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Reader/Xls.php","lineNumber":880,"sourceCode":"     */\n    protected function readFilepass(): void\n    {\n        $length = self::getUInt2d($this->data, $this->pos + 2);\n\n        if ($length < 54) {\n            throw new Exception('Unexpected file pass record length');\n        }\n\n        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);\n\n        // move stream pointer to next record\n        $this->pos += 4 + $length;\n\n        if (substr($recordData, 0, 2) !== \"\\x01\\x00\" || substr($recordData, 4, 2) !== \"\\x01\\x00\") {\n            throw new Exception('Unsupported encryption algorithm');\n        }\n        if (!$this->verifyPassword($this->encryptionPassword, substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {\n            throw new Exception('Decryption password incorrect');\n        }\n\n        $this->encryption = self::MS_BIFF_CRYPTO_RC4;\n\n        // Decryption required from the record after next onwards\n        $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2);\n    }\n\n    /**\n     * Make an RC4 decryptor for the given block.\n     *\n     * @param int $block Block for which to create decrypto\n     * @param string $valContext MD5 context state\n     */\n    private function makeKey(int $block, string $valContext): Xls\\RC4\n    {\n        $pwarray = str_repeat(\"\\0\", 64);\n","sourceCodeStart":862,"sourceCodeEnd":898,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Reader/Xls.php#L862-L898","documentation":"The RC4 password-verification step (document-id + salt + hashed-salt check ported from Spreadsheet-ParseExcel) failed for the configured password. PhpSpreadsheet defaults to 'VelvetSweatshop', the password Excel itself uses when a file is flagged encrypted without a user-supplied password; a file with a real password rejects that default.","triggerScenarios":"Calling load() on a password-protected .xls without first calling setEncryptionPassword(), or passing a wrong/typo'd password; also a corrupt salt can make even the correct password fail verification.","commonSituations":"Uploading password-protected workbooks from users while the import pipeline assumes no password; password changed by the file owner; copy-paste of the password with trailing whitespace.","solutions":["Call setEncryptionPassword('the-real-password') on the Xls reader instance before load()","Trim the password string and confirm it in Excel (open + 'File > Info > Protect Workbook') to rule out typos","If the file opens in Excel without prompting, let the 'VelvetSweatshop' default apply — but if it still throws, the salt bytes are damaged: re-save the file from Excel"],"exampleFix":"// before\n$reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls();\n$spreadsheet = $reader->load('protected.xls'); // Decryption password incorrect\n\n// after\n$reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls();\n$reader->setEncryptionPassword(trim($userSuppliedPassword));\n$spreadsheet = $reader->load('protected.xls');","handlingStrategy":"validation","validationCode":"$reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls();\nif ($knownPassword !== null && $knownPassword !== '') {\n    $reader->setEncryptionPassword(trim($knownPassword));\n}\n$spreadsheet = $reader->load($path);","typeGuard":null,"tryCatchPattern":"try {\n    $spreadsheet = $reader->load($path);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (str_contains($e->getMessage(), 'Decryption password incorrect')) {\n        // re-prompt the user for the password and retry once\n    }\n}","preventionTips":["Collect the password from the user/config before load, not after the failure","Trim passwords and reject empty strings so the VelvetSweatshop default is a deliberate choice","Store per-file passwords keyed by upload, not a single global one"],"tags":["xls","encryption","password","rc4","phpspreadsheet"],"backgroundTag":"wrong-password","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}