通过运行时进程在Java中调用GnuPG以加密和解密文件 - 解密始终挂起

5

注意:我将稍后回来,因为我一直找不到可行的解决方案。手动排除输入流而不是使用BufferedReaders似乎没有帮助,因为inputStream.read()方法会永久阻塞程序。我将gpg调用放在批处理文件中,并从Java中调用批处理文件,结果仍然相同。一旦使用解密选项调用gpg,输入流似乎变得无法访问,阻塞整个程序。我必须在有更多时间专注于任务时再回来解决这个问题。与此同时,我必须通过其他方式使解密工作(可能是BouncyCastle)。

最后尝试的选择可能是调用cmd.exe,并通过该进程生成的输入流编写命令...

感谢您对此问题的协助。


我已经为这个问题工作了几天,但没有取得任何进展,所以我想向这里的专家寻求帮助。

我正在创建一个简单的程序,该程序将通过Java运行时进程调用GnuPG。它需要能够加密和解密文件。加密起作用,但是我在解密文件时遇到了一些问题。每当我尝试解密文件时,进程都会挂起。exitValue()总是抛出IllegalThreadStateException,程序会像仍在等待一样运行。这些方法的代码如下所示。程序的最终目标是解密该文件,并在Java中解析其内容。

我尝试了三种方法来使gpgDecrypt方法工作。第一种方法涉及删除passphrase-fd选项,并通过catch块中的gpgOutput流将passphrase写入gpg,假设它像通过命令行那样提示输入passphrase。这没用,所以我将passphrase放入文件中并添加了-passphrase-fd选项。在这种情况下,程序会无限重复。如果我通过gpgOutput流写任何内容,程序将完成。打印的Exit值将具有2的值,而结果变量将为空白。

第三个选择是BouncyCastle,但我无法让它识别我的私钥(这可能是一个单独的帖子)。

我用于加密和解密的密钥是由GnuPG生成的4096位RSA密钥。在使用passphrase和passphrase文件的两种情况下,我都尝试通过> myFile.txt将输出导向文件,但似乎没有任何区别。

这里是gpgEncrypt、gpgDecrypt和getStreamText方法。我发布了两个,因为加密起作用,我无法看到我在执行和处理加密和解密方法之间的进程时存在任何明显的差异。getStreamText只读取流的内容并返回字符串。

编辑:快速注意,Windows环境。如果我复制解密命令输出,它通过控制台可以正常工作。所以我知道命令是有效的。


public boolean gpgEncrypt(String file, String recipient, String outputFile){
    boolean success = true;
    StringBuilder gpgCommand = new StringBuilder("gpg --recipient \"");
    gpgCommand.append(recipient).append("\" --output \"").append(outputFile).append("\" --yes --encrypt \"");
    gpgCommand.append(file).append("\"");

    System.out.println("ENCRYPT COMMAND: " + gpgCommand);
    try {
        Process gpgProcess = Runtime.getRuntime().exec(gpgCommand.toString());

        BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
        BufferedWriter gpgInput = new BufferedWriter(new OutputStreamWriter(gpgProcess.getOutputStream()));
        BufferedReader gpgErrorOutput = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));

        boolean executing = true;

        while(executing){
            try{
                int exitValue = gpgProcess.exitValue();

                if(gpgErrorOutput.ready()){
                    String error = getStreamText(gpgErrorOutput);
                    System.err.println(error);
                    success = false;
                    break;
                }else if(gpgOutput.ready()){
                    System.out.println(getStreamText(gpgOutput));
                }

                executing = false;
            }catch(Exception e){
                //The process is not yet ready to exit.  Take a break and try again.
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    System.err.println("This thread has insomnia: " + e1.getMessage());
                }
            }
        }
    } catch (IOException e) {
        System.err.println("Error running GPG via runtime: " + e.getMessage());
        success = false;
    }

    return success;
}

public String gpgDecrypt(String file, String passphraseFile){
    String result = null;
    StringBuilder command = new StringBuilder("gpg --passphrase-fd 0 --decrypt \"");
    command.append(file).append("\" 0<\"").append(passphraseFile).append("\"");             
    System.out.println("DECRYPT COMMAND: " + command.toString());
    try {

        Process gpgProcess = Runtime.getRuntime().exec(command.toString());

        BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
        BufferedReader gpgErrorOutput = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));
        BufferedWriter gpgInput = new BufferedWriter(new OutputStreamWriter(gpgProcess.getOutputStream()));

        boolean executing = true;

        while(executing){
            try{
                if(gpgErrorOutput.ready()){
                    result = getStreamText(gpgErrorOutput);
                    System.err.println(result);
                    break;
                }else if(gpgOutput.ready()){
                    result = getStreamText(gpgOutput);
                }

                int exitValue = gpgProcess.exitValue();
                System.out.println("EXIT: " + exitValue);

                executing = false;
            }catch(IllegalThreadStateException e){
                System.out.println("Not yet ready.  Stream status: " + gpgOutput.ready() + ", error: " + gpgErrorOutput.ready());

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    System.err.println("This thread has insomnia: " + e1.getMessage());
                }
            }
        }
    } catch (IOException e) {
        System.err.println("Unable to execute GPG decrypt command via command line: " + e.getMessage());
    }

    return result;
}

private String getStreamText(BufferedReader reader) throws IOException{
    StringBuilder result = new StringBuilder();
    try{
        while(reader.ready()){
            result.append(reader.readLine());
            if(reader.ready()){
                result.append("\n");
            }
        }
    }catch(IOException ioe){
        System.err.println("Error while reading the stream: " + ioe.getMessage());
        throw ioe;
    }
    return result.toString();
}

3
我已经从Java中做了很多批处理/脚本调用。 我可以告诉你一件事情:试图构建一个包含间隔字符的字符串是灾难的开始。 决不能调用*Runtime.getRuntime().exec(String)方法。 你应该将你的字符串拆分并调用Runtime.getRuntime().exec(String[])*方法。请注意,我不是说这将解决你在此处遇到的问题:我只是说,如果你继续按照当前的方式调用批处理/脚本,那么迟早会遇到麻烦。 - SyntaxT3rr0r
感谢您的输入。我会进行重构。无论它是否解决了问题,我都同意这样做是必要的。 - framauro13
你找到任何答案了吗?我也遇到了同样的问题!我开始编码,看到了你的例子,看到了stream gobbler问题,我也使用了那个。但是当文件已经存在且gpg.exe正在等待用户按'y<enter>'时,这个挂起的问题仍然存在。但让人烦恼的是,在控制台(Stream Gobbler)中没有提示选择y/N,因此我无法以编程方式处理它。如果gpg.exe成功完成,则stream gobbler会显示。 - samarjit samanta
只有在批处理模式下使用解密命令时,它才对我起作用。c:>gpg.exe --decrypt --batch --passphrase-file c:\keys\mypass.txt --output sample.html sample.html.gpg ........ 但我仍然不喜欢无法捕获错误或输出流的事实,我尝试在.bat文件.sh文件中运行完整的gpg解密命令,也尝试了plexus-utils,所有这些都无法捕获当输出文件已经存在且gpg.exe期望用户输入时弹出的流。我想便携式git附带的gpg.exe中一定有一些有趣的东西,而我正在使用它。 - samarjit samanta
7个回答

3

我忘记了Java中如何处理它,有100种方法。但是我卡在解密命令本身上,它非常有帮助,虽然你不需要所有这些引号,如果你想解密一个大文件,可以按照以下步骤进行:

gpg --passphrase-fd 0 --output yourfile.txt --decrypt /encryptedfile.txt.gpg/ 0</passwrdfile.txt

1

这可能是问题所在(在解密函数中)

    BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
    BufferedReader gpgErrorOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
    BufferedWriter gpgInput = new BufferedWriter(new OutputStreamWriter(gpgProcess.getOutputStream()));

您正在对getInputStream的结果进行两次包装。显然,gpgErrorOutput应该包装错误流,而不是输入流。


1
另外,你有没有读过 http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?可靠地使用Runtime.exec需要线程读取程序的输出。 - Matthew Wilson
1
你还需要像@Matthew提到的那样清空流。可能是stdout或stderr缓冲区正在填充,这会导致进程挂起。 - Cameron Skinner
@Matthew 我最终会使用多线程,但我想先让单个文件工作。将尝试生成一个新线程。 - framauro13
是的,在排空之前调用 exitValue() 会导致问题。Apache commons-io 包有一些类可以为您泵送流,因此您不需要自己进行线程处理。 - Cameron Skinner
你需要添加排水口。有可能你的代码在子进程启动之前就会同时触发两个“ready”调用,这种情况下它们将返回false,你就会遇到之前的同样问题。基本上,即使你认为正在进行简单操作,你也总是需要排空流。 - Cameron Skinner
显示剩余8条评论

1
你尝试过从命令行而不是Java代码运行该命令吗? 当GnuPG等待控制台输出时,“仅供您查看”选项可能会出现问题。

当我通过命令行运行应用程序生成的命令时,它可以正常运行。我可以尝试将其包装在批处理文件中,然后调用批处理文件。 - framauro13

1
我今天偶然发现了这个帖子,因为我遇到了与程序挂起完全相同的问题。卡梅伦上面的帖子包含解决方案,即您必须从进程中排空inputStream。如果不这样做,流会充满并挂起。只需添加...
String line = null;
while ( (line = gpgOutput.readLine()) != null ) {
     System.out.println(line);
}

在检查 exitValue 之前,它为我解决了问题。

0
int exitValue = gpgProcess.exitValue();

// 这会导致进程未退出异常


0

当我使用下面的命令替换解密命令时,它对我起作用了

gpg --output decrypted_file --batch --passphrase "passphrase goes here" --decrypt encrypted_file

0

以下代码适用于GNUPG 2.1.X

public static boolean gpgEncrypt(String file, String recipient,
        String outputFile) {
    boolean success = true;
    StringBuilder gpgCommand = new StringBuilder("gpg --recipient \"");
    gpgCommand.append(recipient).append("\" --output \"")
            .append(outputFile).append("\" --yes --encrypt \"");
    gpgCommand.append(file).append("\"");

    System.out.println("ENCRYPT COMMAND: " + gpgCommand);
    try {
        Process gpgProcess = Runtime.getRuntime().exec(
                gpgCommand.toString());

        BufferedReader gpgOutput = new BufferedReader(
                new InputStreamReader(gpgProcess.getInputStream()));
        BufferedWriter gpgInput = new BufferedWriter(
                new OutputStreamWriter(gpgProcess.getOutputStream()));
        BufferedReader gpgErrorOutput = new BufferedReader(
                new InputStreamReader(gpgProcess.getErrorStream()));

        boolean executing = true;

        while (executing) {
            try {
                int exitValue = gpgProcess.exitValue();

                if (gpgErrorOutput.ready()) {
                    String error = getStreamText(gpgErrorOutput);
                    System.err.println(error);
                    success = false;
                    break;
                } else if (gpgOutput.ready()) {
                    System.out.println(getStreamText(gpgOutput));
                }

                executing = false;
            } catch (Exception e) {
                // The process is not yet ready to exit. Take a break and
                // try again.
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    System.err.println("This thread has insomnia: "
                            + e1.getMessage());
                }
            }
        }
    } catch (IOException e) {
        System.err.println("Error running GPG via runtime: "
                + e.getMessage());
        success = false;
    }

    return success;
}

// gpg --pinentry-mode=loopback --passphrase "siv_test" -d -o
// "sample_enc_data_op.txt" "sample_enc_data_input.gpg"

public static String gpgDecrypt(String file, String passphrase,
        String outputfile) {
    String result = null;
    StringBuilder command = new StringBuilder(
            "gpg --pinentry-mode=loopback --passphrase \"");
    command.append(passphrase).append("\" -d -o \"").append(outputfile)

    .append("\" --yes \"").append(file)

    .append("\"");
    System.out.println("DECRYPT COMMAND: " + command.toString());
    try {

        Process gpgProcess = Runtime.getRuntime().exec(command.toString());

        BufferedReader gpgOutput = new BufferedReader(
                new InputStreamReader(gpgProcess.getInputStream()));
        BufferedReader gpgErrorOutput = new BufferedReader(
                new InputStreamReader(gpgProcess.getErrorStream()));
        BufferedWriter gpgInput = new BufferedWriter(
                new OutputStreamWriter(gpgProcess.getOutputStream()));

        boolean executing = true;

        while (executing) {
            try {
                if (gpgErrorOutput.ready()) {
                    result = getStreamText(gpgErrorOutput);
                    System.err.println(result);
                    break;
                } else if (gpgOutput.ready()) {
                    result = getStreamText(gpgOutput);
                }
                String line = null;
                while ((line = gpgOutput.readLine()) != null) {
                    System.out.println(line);
                }
                int exitValue = gpgProcess.exitValue();
                System.out.println("EXIT: " + exitValue);

                executing = false;
            } catch (IllegalThreadStateException e) {
                System.out.println("Not yet ready.  Stream status: "
                        + gpgOutput.ready() + ", error: "
                        + gpgErrorOutput.ready());

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    System.err.println("This thread has insomnia: "
                            + e1.getMessage());
                }
            }
        }
    } catch (IOException e) {
        System.err
                .println("Unable to execute GPG decrypt command via command line: "
                        + e.getMessage());
    }

    return result;
}

private static String getStreamText(BufferedReader reader)
        throws IOException {
    StringBuilder result = new StringBuilder();
    try {
        while (reader.ready()) {
            result.append(reader.readLine());
            if (reader.ready()) {
                result.append("\n");
            }
        }
    } catch (IOException ioe) {
        System.err.println("Error while reading the stream: "
                + ioe.getMessage());
        throw ioe;
    }
    return result.toString();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接