You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过Web界面使用Linux GPG库解密PGP文件遇零长度问题求助

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'
            ];
        }
    }
}

解决方向提示

  1. 调试命令输出:去掉--quiet参数,捕获gpg的错误输出,用exec()代替system()获取完整命令输出,比如:

    exec($cmd . ' 2>&1', $output, $result);
    var_dump($output);
    

    这样能看到gpg实际返回的错误信息,比如密钥不可用、权限问题或密码错误。

  2. 文件路径问题:确保加密文件的路径正确,当前工作目录可能不是文件所在目录,建议使用绝对路径指定加密和解密文件。

  3. 权限与密钥访问:确认执行gpg的系统用户(即使改了文件所有者,PHP进程运行的用户环境可能没有访问密钥环的权限),可以在命令中指定--homedir参数指向包含密钥的目录,比如:

    gpg --homedir /path/to/key/dir --batch --passphrase-fd 0 --decrypt ...
    
  4. 密码短语传递问题:如果密码包含特殊字符(如单引号、反斜杠),直接用echo传递会被shell解析出错,建议用printf替代,或者将密码写入临时文件再传递:

    printf '%s' "$passphrase" | gpg --passphrase-fd 0 ...
    
  5. 文件上传完整性:验证上传后的加密文件和原文件是否一致,计算MD5或SHA256哈希值对比,确认上传过程中没有文件损坏。


内容的提问来源于stack exchange,提问作者William N. Irwin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 17:54:31