解决FPDF SetProtection在Amazon Linux 2023上需密码问题
2025-03-25 14:00:55
FPDF SetProtection 在 Amazon Linux 2023 上需要密码的问题解决
最近把系统从 Amazon Linux 2 迁移到 Amazon Linux 2023,结果发现 FPDF 的 SetProtection 脚本出了点问题。原本只是用来加密 PDF 文件、限制只允许打印的,现在打开时却要输密码,可我们压根没设置过密码!
问题根源:加密函数 RC4 的差异
深挖了下,发现问题出在 RC4
函数上。这函数调用了 openssl_encrypt
来实现加密。那个 SetProtection 脚本 (http://www.fpdf.org/en/script/script37.php) 提供了一个纯 PHP 的备用方案,在 openssl_encrypt
不可用时使用,这个备用方案在新系统上倒是能正常工作。 这让我觉得,多半是 OpenSSL 的什么地方不对劲。 也有可能是新版 PHP 改变了啥行为。
PDF 生成过程中,日志里也没看到报错信息。
更具体一点, return openssl_encrypt($data, 'RC4-40', $key, OPENSSL_RAW_DATA);
这行代码在新系统上啥也没返回, 旧系统上则有“东西”返回。 尝试过修改不同的加密方法,都没用(也正常,毕竟我对这块不太懂…)。
解决方案
下面提供几种解决思路,大家可以根据实际情况选用:
1. 使用纯 PHP 的 RC4 实现 (最简单粗暴)
这是最简单的方法,直接用脚本提供的备用方案,绕过 openssl_encrypt
。
原理: FPDF SetProtection 脚本自带了一个纯 PHP 实现的 RC4 加密算法,用于在没有 OpenSSL 扩展的情况下进行加密。
操作步骤:
-
找到 FPDF 类文件中的
RC4
函数 (通常在fpdf.php
或你包含 SetProtection 脚本的文件里)。 -
注释掉使用
openssl_encrypt
的那一行,解除注释使用纯 PHP 实现的那一行。function RC4($key, $data) { // Use the PHP-only version. //if(function_exists('openssl_encrypt')) //{ // return openssl_encrypt($data, 'RC4-40', $key, OPENSSL_RAW_DATA); //} $s = array(); for($i=0;$i<256;$i++) { $s[$i] = $i; } $j = 0; for($i=0;$i<256;$i++) { $j = ($j + $s[$i] + ord($key[$i%strlen($key)])) % 256; $t = $s[$i]; $s[$i] = $s[$j]; $s[$j] = $t; } $a = 0; $j = 0; $r = ''; for($i=0;$i<strlen($data);$i++) { $a = ($a + 1) % 256; $j = ($j + $s[$a]) % 256; $t = $s[$a]; $s[$a] = $s[$j]; $s[$j] = $t; $k = $s[($s[$a] + $s[$j]) % 256]; $r .= chr(ord($data[$i]) ^ $k); } return $r; }
安全性: 因为这个纯 PHP 实现用的是 RC4-40,密钥长度只有 40 位。这种加密强度较低,易被破解,如果对安全性要求高,慎用。
2. 检查并修复 OpenSSL 环境
可能系统上的 OpenSSL 环境配置有问题,或者某些必要的库没装。
原理: openssl_encrypt
函数依赖于正确配置的 OpenSSL 环境。如果环境有问题,函数可能无法正常工作。
操作步骤:
-
确认 OpenSSL 扩展已启用: 用
phpinfo()
查看openssl
扩展是否加载。没加载的话,需要在php.ini
中启用。# 找到 php.ini (可以用 php --ini 命令查找) # 取消 extension=openssl 的注释
重启php-fpm 或者web服务器。
-
检查 OpenSSL 版本兼容性: 虽然可能性小, 但也可以检查一下 PHP 和 OpenSSL 的版本兼容性,看官方文档有没有相关说明。
-
确认系统库依赖:
# 检查OpenSSL库文件是否存在并完整 sudo yum reinstall openssl ldd $(which php) | grep ssl #查看php链接的ssl库。
-
查看详细的错误信息(进阶): 可以用更底层的调试方法, 比如
strace
去追踪 PHP 进程, 看看调用openssl_encrypt
时具体发生了什么错误:# 找到你的PHP进程ID(假设为1234) strace -p 1234 -e trace=network,file,process,signal -f -o /tmp/phptrace.log # 重现PDF生成的操作. 然后查看 /tmp/phptrace.log
分析日志可能需要一些系统编程知识。
3. 使用其他加密方法 (进阶, 如果您了解加密相关)
如果对安全性要求高,可以考虑换个更强的加密算法。但改动会大一些。
原理: FPDF 的 SetProtection 支持多种加密算法,你可以通过修改脚本,用更安全的算法 (比如 AES) 来替代 RC4-40。
操作步骤 (以 AES-128 为例):
-
修改
_putencryption
函数: FPDF 内部用_putencryption
函数生成加密字典。找到它,修改里面的算法和密钥长度参数。 -
修改
RC4
函数 (如果其他地方也用到了): 将所有调用RC4
的地方,都换成新算法对应的函数 (比如openssl_encrypt
加上正确的 AES 参数)。 -
重新计算加密密钥等 : 因为算法和参数变了, 你要根据 PDF 规范,重新计算
UserPassword
、OwnerPassword
这些值.//在_putencryption中修改: //$filter = '/Standard'; //保留此项,因为它表示使用的是标准安全处理器。 ... //$v = 1; //或者 $v =2,但是这里设置了P权限,如果这里使用v=2会要求v=4或5。 $v = 4; // 必须与算法匹配 (1 或 2 表示 RC4, 4/5 表示 AES) $m = ($this->ProtectionParams['P'] >> 2) + 1; //计算加密方法的数值。根据允许的权限推导出的数字 //If $v = 4 and using AES128: $this->enc_obj_num = $n + 1; // Store the encryption object number // $Length 对应 KeyLength 长度必须修改,单位是bit $Length=128; ... //_hexmarker的定义为 //$this->_hexmarker=chr(0xFD).chr(0xFC).chr(0xFB).chr(0xFA); ...
再在
RC4
函数那里,将加密算法更换为:function RC4($key, $data) { //根据需求修改: $cipher = 'aes-128-cbc'; // 或其他 $cipher = 'aes-128-cbc'; //例如 if(function_exists('openssl_encrypt')) { return openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA); } // ... 这里原来的RC4代码不需要了,当然,你需要一个PHP的aes的加密函数来实现同样的操作。 }
安全性: AES-128 或更强的算法安全性更高, 可以有效保护 PDF 内容。 但代码修改量较大, 风险较高,务必仔细测试。
重要提示 : 对PDF底层结构以及加密有了解才进行此修改! 修改时要格外小心!
4.使用低版本的php或旧的openssl库(不推荐)
除非您已经用完了上面的所有选项,否则您再来选择这条。
尝试回退 PHP 或 OpenSSL 到旧版本, 看能不能解决. (只建议在测试环境尝试!)
原理是降低版本使其重新可以被RC4-40正常加密,虽然简单,但是不可避免的引入了安全风险!
步骤 :
降级php或者openssl到兼容版本,具体的yum 指令为 yum downgrade <package_name>
,您可以查找旧版本进行尝试,操作之前请做好充分备份 。
安全性 : 回退到旧版本,意味着放弃了新版本中的安全补丁和改进。系统可能面临已知的安全漏洞。非常不推荐长期使用此方案。