通过Web界面使用Linux GPG库解密PGP文件遇零长度问题求助
我们的客户端使用我方提供的PGP公钥加密GDPR敏感数据后发送加密文件,需实现一个HTML/AJAX/PHP单页应用(SPA),允许上传加密文件,校验后解密并提供下载。
终端中执行以下命令可正常解密文件:
gpg --decrypt encrypted_file.ext > decrypted_file.ext
执行时输入密码短语即可生成正常解密文件。
目前开发的SPA可完成文件上传,解密后通过AJAX返回包含download.php路径的对象,该文件接收加密和解密文件名的编码URL参数。但解密后的文件始终为零长度,无任何报错信息。
尝试过的最新方案是用inotify-tools监听上传目录,执行以下操作:
- 将加密文件所有者从www-data改为生成密钥的系统用户
- 执行命令:
echo 'passphrase' | gpg --batch --quiet --yes --passphrase-fd 0 --decrypt $encryptedFilename > $decryptedFilename - 将解密后文件所有者改回www-data以便下载
- 通过download.php的Content-Disposition头强制下载解密文件
但解密文件仍为零长度,无报错。以下是相关代码:
上传验证类
<?php class UploadValidationProd { protected $uploadedForm; protected $buttonName; protected $uploadedFile; protected $inputFileName; protected $maximumFileSize; protected $validated; /** * @param $post * @param $buttonName * @param $files * @param $inputFileName */ public function __construct($post, $buttonName, $files, $inputFileName) { $this->uploadedForm = $post; $this->buttonName = $buttonName; $this->uploadedFile = $files; $this->inputFileName = $inputFileName; $this->maximumFileSize = 20000000; $this->validated = FALSE; } /** * @return bool */ public function CheckFormPosted(): bool { if (isset($this->uploadedForm[$this->buttonName])) { return TRUE; } return FALSE; } /** * @return bool */ public function CheckFilePosted(): bool { if (!empty($this->uploadedFile[$this->inputFileName]['name'])) { return TRUE; } return FALSE; } /** * @return bool */ public function CheckFileTypeAllowed(): bool { $allowedTypes = [ 'csv', 'xls', 'xlsx' ]; $fileType = strtolower(pathinfo($this->uploadedFile[$this->inputFileName]['name'], PATHINFO_EXTENSION)); if (!in_array($fileType, $allowedTypes)) { return FALSE; } return TRUE; } /** * @return bool */ public function CheckFileSize(): bool { if ($this->uploadedFile[$this->inputFileName]['size'] > $this->maximumFileSize) { return FALSE; } return TRUE; } /** * @return array */ public function DoValidation(): array { $checkFormPosted = $this->CheckFormPosted(); if ($checkFormPosted) { $checkFilePosted = $this->CheckFilePosted(); $checkFileTypeAllowed = $this->CheckFileTypeAllowed(); $checkFileSize = $this->CheckFileSize(); if (!$checkFilePosted) { return [ 'result' => 'error', 'message' => 'No file was selected' ]; } if (!$checkFileTypeAllowed) { return [ 'result' => 'error', 'message' => 'The selected file type is not allowed' ]; } if (!$checkFileSize) { return [ 'result' => 'error', 'message' => 'The selected file size exceeds the maximum allowed size (' . $this->maximumFileSize / 1000000 . 'MB)' ]; } if (move_uploaded_file($this->uploadedFile[$this->inputFileName]['tmp_name'], $this->uploadedFile[$this->inputFileName]['name'])) { return [ 'result' => 'success', 'message' => 'The selected file was successfully uploaded' ]; } } return [ 'result' => NULL, 'message' => NULL ]; } }
解密类
<?php class UploadDecryptionProd { protected $encryptedFile; protected $decryptedFile; protected $pgpPassphrase; /** * @param $encryptedFile */ public function __construct($encryptedFile) { $this->encryptedFile = $encryptedFile; $this->decryptedFile = 'decrypted_' . $encryptedFile; $this->pgpPassphrase = 'theOriginalPassphrase'; } /** * @return array */ public function Decrypt(): array { $cmd = "echo '$this->pgpPassphrase' | gpg --batch --quiet --yes --passphrase-fd 0 --decrypt $this->encryptedFile > " . $this->decryptedFile; $result = 0; system($cmd, $result); if ($result == '0') { return [ 'result' => 'success', 'message' => $this->decryptedFile ]; } else { return [ 'result' => 'error', 'message' => $this->encryptedFile . ' could not be decrypted' ]; } } }
解决方向提示
调试命令输出:去掉
--quiet参数,捕获gpg的错误输出,用exec()代替system()获取完整命令输出,比如:exec($cmd . ' 2>&1', $output, $result); var_dump($output);这样能看到gpg实际返回的错误信息,比如密钥不可用、权限问题或密码错误。
文件路径问题:确保加密文件的路径正确,当前工作目录可能不是文件所在目录,建议使用绝对路径指定加密和解密文件。
权限与密钥访问:确认执行gpg的系统用户(即使改了文件所有者,PHP进程运行的用户环境可能没有访问密钥环的权限),可以在命令中指定
--homedir参数指向包含密钥的目录,比如:gpg --homedir /path/to/key/dir --batch --passphrase-fd 0 --decrypt ...密码短语传递问题:如果密码包含特殊字符(如单引号、反斜杠),直接用
echo传递会被shell解析出错,建议用printf替代,或者将密码写入临时文件再传递:printf '%s' "$passphrase" | gpg --passphrase-fd 0 ...文件上传完整性:验证上传后的加密文件和原文件是否一致,计算MD5或SHA256哈希值对比,确认上传过程中没有文件损坏。
内容的提问来源于stack exchange,提问作者William N. Irwin

