5

NOTE: Coming back to this later as I've been unable to find a working solution. Draining the input streams manually instead of using BufferedReaders doesn't seem to help as the inputStream.read() method permanently blocks the program. I placed the gpg call in a batch file, and called the batch file from Java to only get the same result. Once gpg is called with the decrypt option, the input stream seems to become inaccessible, blocking the entire program. I'll have to come back to this when I have more time to focus on the task. In the mean time, I'll have to get decryption working by some other means (probably BouncyCastle).

The last option to probably try is to call cmd.exe, and write the command through the input stream generated by that process...

I appreciate the assistance on this issue.


I've been working on this problem for a couple days and haven't made any progress, so I thought I'd turn to the exeprtise here for some help.

I am creating a simple program that will call GnuPG via a Java runtime process. It needs to be able to encrypt and decrypt files. Encryption works, but I'm having some problems decrypting files. Whenever I try to decrypt a file, the process hangs.exitValue() always throws it's IllegalThreadStateException and the program chugs along as if it's still waiting. The code for these methods is attached below. The ultimate goal of the program is to decrypt the file, and parse it's contents in Java.

I've tried three approaches to getting the gpgDecrypt method to work. The first approach involved removing the passphrase-fd option and writing the passphrase to gpg via the gpgOutput stream in the catch block, assuming it was prompting for the passphrase like it would via the command line. This didn't work, so I put the passphrase in a file and added the -passphrase-fd option. In this case, the program repeats infinitely. If I write anything via the gpgOutput stream the program will complete. The Exit value printed will have a value of 2, and the result variable will be blank.

The third option is BouncyCastle, but I'm having problems getting it to recognize my private key (which is probably a separate post all together).

The keys I'm using to encrypt and decrypt are 4096-bit RSA keys, generated by GnuPG. In both cases using the passphrase and the passphrase file, I've tried piping the output to a file via > myFile.txt, but it doesn't seem to make any difference.

Here are the gpgEncrypt, gpgDecrypt and getStreamText methods. I posted both since the encrypt works, and I can't see any glaring differences between how I'm executing and handling the process between the encrypt and decrypt methods. getStreamText just reads the contents of the streams and returns a string.

EDIT: Quick note, Windows environment. If I copy the decrypt command output, it works via the console just fine. So I know the command is valid.


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();
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
framauro13
  • 797
  • 2
  • 8
  • 18
  • 3
    I've done a **lot** of batch/script calls from Java. I can tell you one thing: trying to build a String containing spacing characters is a recipe for disaster. Do **NEVER EVER** call the *Runtime.getRuntime().exec(String)* method. What you **SHOULD** do is split your String and call the *Runtime.getRuntime().exec(String[])* method. Note that I'm not saying it shall solve the issue you have here: all I'm saying is you will suffer at one point or another if you keep invoking batch/scripts the way you currently do. – SyntaxT3rr0r Dec 06 '10 at 14:20
  • Thanks for the input. I will refactor this. Regardless of whether or not it fixes the issue, I agree that it does need to be done. – framauro13 Dec 06 '10 at 15:19
  • Did you find any answers, I also encountered the same thing! I started coding seeing your example seeing the [stream gobbler](http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4#codewrap144) problem I used that one too. But this hanging problem remained when there was file already existing and i gpg.exe was waiting for user to hit 'y'. But the irritating part was the prompt to select y/N never comes up in console (Stream Gobbler), so I cannot handle that programmatically. The stream gobbler does show if gpg.exe successfully completes. – samarjit samanta Nov 25 '11 at 18:27
  • It only worked for me when in the decrypt command I used in batch mode .. c:\>gpg.exe --decrypt --batch --passphrase-file c:\keys\mypass.txt --output sample.html sample.html.gpg ........ But i still don't like the fact that error or output stream is not getting captured, I tried running in the full gpg decrypt command in .bat file .sh file, I tried plexus-utils too, all of them fails to capture stream popped out of gpg when the output file already exists and gpg.exe expects user input. I wonder something funny must be there in gpg.exe that came with portable git, which i am using. – samarjit samanta Nov 25 '11 at 19:17

7 Answers7

3

I forget how you handle it in Java, there are 100 methods for that. But I was stuck with decrypt command itself, it was very helpful, though you didn't need all those quotes and if you wish to decrypt a large file, it goes like this:

gpg --passphrase-fd 0 --output yourfile.txt --decrypt /encryptedfile.txt.gpg/ 0</passwrdfile.txt
Chadwick
  • 12,555
  • 7
  • 49
  • 66
sat
  • 31
  • 2
1

I stumbled upon this thread today because I was having the exact same issue as far as the program hanging. Cameron's thread from above contains the solution, which is that you have to be draining the inputStream from your process. If you don't, the stream fills up and hangs. Simply adding

String line = null;
while ( (line = gpgOutput.readLine()) != null ) {
     System.out.println(line);
}

Before checking the exitValue fixed it for me.

Craig
  • 1,295
  • 3
  • 16
  • 29
1

Have you tried to run that command from command-line, not from Java code? There can be an issue with 'for your eyes only' option, when GnuPG will wait for console output.

Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48
  • The command generated by the application does run fine when I run it via the command line. I may try wrapping it in a batch file and calling the batch file instead. – framauro13 Dec 06 '10 at 14:55
1

This may or may not be the problem (in the decrypt function)

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

You are wrapping the result of getInputStream twice. Obviously gpgErrorOutput should be wrapping the error stream, not the input stream.

Cameron Skinner
  • 51,692
  • 2
  • 65
  • 86
  • 1
    Also have you read http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html ? A reliable use of Runtime.exec needs Threads reading the output of the program. – Matthew Wilson Dec 06 '10 at 14:34
  • Thanks, I totally missed that. Funny how you can spend hours upon hours reading up on API's, designing processes and worrying about efficiencies and make a dumb error like this :) – framauro13 Dec 06 '10 at 14:56
  • I updated the code to wrap the proper stream... unfortunately, the hanging persists. – framauro13 Dec 06 '10 at 15:00
  • 1
    You also need to drain the streams as @Matthew mentioned. It could be that the stdout or stderr buffers are filling up which will cause the process to hang. – Cameron Skinner Dec 06 '10 at 15:03
  • @Matthew I will multithread this eventually, but I wanted to get a single file to work first. Will try spawning a new thread. – framauro13 Dec 06 '10 at 15:07
  • @Cameron The getStreamText method should read from the streams if/when they are ready. However, I am calling exitValue() first before checking... I'll try moving the output stream checks in front immediately after executing the process to see if that is causing a problem. The files I am decrypting are only a few kilobytes, but it could still be a problem. I'll try moving that block of code in front of the exit value check... maybe they're filling up too fast. – framauro13 Dec 06 '10 at 15:10
  • Yes, it's the `exitValue()` call before the drain that will cause problems. The Apache commons-io package has classes that will pump streams for you so you don't need to do your own threading. – Cameron Skinner Dec 06 '10 at 15:14
  • Updated the code to check the streams for content before calling exitValue(). Added a break in the error check to end the loop if an error was detected. Both ready() methods still return false, and the process continues to loop indefinitely. – framauro13 Dec 06 '10 at 15:25
  • Try adding logging from the threads which are draining stdout and stderr, to see if you are getting unexpected errors. – Matthew Wilson Dec 06 '10 at 16:04
  • You're going to need to add the drains. It's possible that your code can hit both the `ready` calls before the sub-process has even started in which case they'll return false and you have the same problem as before. Basically, you *always* need to drain the streams even when you think you're doing something simple. – Cameron Skinner Dec 06 '10 at 16:08
  • I hate to do it, but I think I've got to abandon this approach and look at doing this by another means (BouncyCastle) as it's eaten up way too much time already. I added a method that didn't use a buffered reader, just read the bytes from the input stream. When inputStream.read(bytes) is called, the program stops responding. The read call blocks the program and keeps it from continuing to execute. I even tried wrapping the gpg call in a batch file, and calling that from Java, only to get the same results. Encrypt works fine, but decrypt just doesn't seem to want to work. – framauro13 Dec 07 '10 at 17:33
  • The commands generated by the Java application run fine when copied from the console and pasted onto the command line, but calling gpg with the decrypt option seems to completely "plug up" the input stream. I'll have to dig into it further when I have more time to focus on this task. – framauro13 Dec 07 '10 at 17:37
  • You should also be aware that `Reader.ready()` does not do what it looks like you think it does. In your `getStreamText` method you check `Reader.ready()` then call `readLine()` - the `ready` call does not guarantee that `readLine` will not block. It only guarantees that there is at least one byte to be read, but `readLine()` may attempt to read an arbitrary number of bytes which could still result in blocking. – Cameron Skinner Dec 07 '10 at 19:59
0
int exitValue = gpgProcess.exitValue();

// it gives process has not exited exception

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276
Mahipal
  • 1
  • 1
0

It worked for me when i replace the decrypt command with below command

gpg --output decrypted_file --batch --passphrase "passphrase goes here" --decrypt encrypted_file
Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276
0

The following code works with 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();
}
SJN
  • 377
  • 2
  • 8
  • 18