
DOMPDF生成带密码保护的PDF时,若先调用stream()再调用output(),会导致加密参数(尤其是p值)被意外修改,造成后续下载的文件无法用原密码打开。本文提供安全可靠的双输出(浏览器流+服务器保存)实现方案。
dompdf生成带密码保护的pdf时,若先调用`stream()`再调用`output()`,会导致加密参数(尤其是p值)被意外修改,造成后续下载的文件无法用原密码打开。本文提供安全可靠的双输出(浏览器流+服务器保存)实现方案。
在使用 DOMPDF 为 PDF 添加密码保护时,一个常见却隐蔽的问题是:同一 PDF 实例先后调用 stream() 和 output() 会导致加密信息损坏,表现为——首次下载可正常输入密码打开,但后续从服务器读取该文件时却提示“密码错误”或“权限不足”。
根本原因在于 DOMPDF 底层基于 TCPDF 的 CPDF 引擎在执行 stream() 时会动态重写加密对象(arc4_objnum 对应的 /Encrypt 字典),其中关键字段 p(权限掩码值)会被从 252 错误地更改为 -4(因二进制补码转换逻辑触发)。而 output() 复用已被污染的对象结构,最终写入磁盘的 PDF 包含无效的加密元数据,导致 Adobe Reader 等解析器拒绝解密。
✅ 正确做法:在 stream() 后手动恢复原始加密信息,确保 output() 使用未被篡改的加密配置:
$dompdf = new Dompdf(['enable_remote' => true]);
$dompdf->loadHtml($html_print);
$dompdf->setPaper('A4', 'landscape');
$dompdf->render();
// 1. 获取底层 CPDF 实例并设置加密
$cpdf = $dompdf->getCanvas()->get_cpdf();
$cpdf->setEncryption('1234', '5678', ['print', 'modify', 'copy', 'add']);
// 2. 保存加密前的原始 info 数据(关键!)
$encryptInfo = $cpdf->objects[$cpdf->arc4_objnum]['info'];
// 3. 输出到浏览器(此时 p 值会被修改)
$dompdf->stream($filename);
// 4. 【修复步骤】强制还原加密 info,避免 output() 写入错误值
$cpdf->objects[$cpdf->arc4_objnum]['info'] = $encryptInfo;
// 5. 安全生成文件内容并保存
$output = $dompdf->output();
$serverFilename = '../documents/' . $filename . '-' . time() . '.pdf';
file_put_contents($serverFilename, $output);⚠️ 注意事项:
- 不要交换 stream() 和 output() 的顺序——output() 必须在 stream() 之后且加密信息已恢复的前提下调用;
- 密码建议使用字符串而非纯数字(如 '1234' 而非 1234),避免类型隐式转换风险;
- 权限数组 ['print', 'modify', ...] 中的值必须严格匹配 DOMPDF 支持的权限标识,非法项将导致加密失败;
- 若需长期存档,推荐采用「先 output() 保存文件 → 再 readfile() 流式响应」的单次输出模式,彻底规避双输出状态污染问题。
通过精准捕获并重置加密元数据,即可稳定实现“即时预览 + 服务端持久化”的双重需求,确保所有副本均能使用统一密码正确解密与操作。


















