Search code examples
c#windowsgnupgpassphrase

GPG automatic decryption password passing


We receive GPG encrypted files from a third party. I'm modifying a C# program that finds the encrypted files, decrypts them, and deletes the encrypted ones. It all works except during the decryption part it prompts for a phassphrase; I know the passphrase and it works when entered. I need to pass the passphrase in the command so the prompt never appears.

string CommandText = string.Format("echo {0}|gpg.exe --keyring {1} --secret-keyring {2} --batch --yes --passphrase-fd 0 -o {3} -d {4}",
                passPhrase, publicKeyRingPath, secretKeyRingPath, outputFullPath, encryptedFilePath);

I have also tried:

    string CommandText = string.Format("gpg.exe --keyring {1} --secret-keyring {2} --batch --yes --passphrase {0} -o {3} -d {4}",
    string CommandText = string.Format("gpg.exe --keyring {1} --secret-keyring {2} --batch --yes --passphrase-fd {0} -o {3} -d {4}",

As well as several other variations.

This is running GnuPG for Windows 2.1.0.57899

In case the issues is elsewhere here is a bunch of code primarily written by my predecessor:

public bool decryptInputFile(string encryptedFilePath, string outputFullPath, out string message)
{
    message = "decryptInputFile: Started";
    try
    {
        ProcessStartInfo psi = new ProcessStartInfo("cmd.exe")
        {
            CreateNoWindow = true,
            UseShellExecute = true,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            WorkingDirectory = decryptPath,
        };

        message = "decryptInputFile: PSI Initialized";
        using (Process process = Process.Start(psi))
        {
            string CommandText = string.Format("echo {0}|gpg.exe --keyring {1} --secret-keyring {2} --batch --yes --passphrase-fd 0 -o {3} -d {4}",
                                passPhrase, publicKeyRingPath, secretKeyRingPath, outputFullPath, encryptedFilePath);
            process.StandardInput.WriteLine(CommandText);
            process.StandardInput.Flush();
            process.StandardInput.Close();
            process.WaitForExit();
            process.Close();
            process.Dispose();
            message = "decryptInputFile: Success";


            //These processes don't close and it keeps the file from being deleted.
            foreach (Process P in Process.GetProcessesByName("gpg")) { P.Kill(); }
            foreach (Process P in Process.GetProcessesByName("gpg2")) { P.Kill(); }

        }
    }
    catch (Exception x)
    {
        // If there was an error, we're going to eat it and just let the user know we failed.
        message = "decryptInputFile: Error: " + x.Message;
        string errMessage = "ERROR: could not decrypt. " + x.Message + "\r\n";
        File.AppendAllText(System.Configuration.ConfigurationSettings.AppSettings["LogPath"], errMessage);

        return false;
    }

    if (File.Exists(outputFullPath) && File.Exists(encryptedFilePath))
    {
        File.Delete(encryptedFilePath);
    }
    return File.Exists(outputFullPath);
}

Solution

  • The Problem

    You're using GnuPG 2, which only allows the --passphrase* options together with --batch.

    Using --batch

    The --passphrase* options are meant to be used for scripting. GnuPG 2 limits them (probably for slowly deprecating them out) to the --batch mode, where GnuPG does not perform any interaction (eg., asking for your passphrase or other "dialogues").

    While this is still possible, it might be preferable to use the password presetting in gpg-agent instead, which allows you to remove the passphrase completely from your application code. Note the implications of --passphrase (all users on your system can read it, as long as GnuPG is running!) and --passphrase-file (the passphrase is stored on the hard disk, watch out for permissions).

    Presetting the Passphrase

    Preferred method with GnuPG 2 is to preset the passphrase in gpg-agent, which GnuPG heavily relies on; in case of GnuPG 2.1 the even handles private key and passphrase operations completely on its own.

    But, to your rescue, GnuPG 2 brings a new tool, gpg-preset-passphrase. On Debian Linux, it hides in /usr/lib/gnupg2/, I don't know where it is stored in Windows.

    From man gpg-preset-passphrase:

    The gpg-preset-passphrase is a utility to seed the internal cache of a running gpg-agent with passphrases. It is mainly useful for unattended machines, where the usual pinentry tool may not be used and the passphrases for the to be used keys are given at machine startup.

    [...]

    gpg-preset-passphrase is invoked this way:

    gpg-preset-passphrase [options] [command] cacheid
    

    cacheid is either a 40 character keygrip of hexadecimal characters identifying the key for which the passphrase should be set or cleared. [...]

    One of the following command options must be given:

    --preset
        Preset a passphrase. This is what you usually will use.
        gpg-preset-passphrase will then read the passphrase from stdin.
    

    To wrap up, when initialising GnuPG for your application (and in intervalls corresponding to the configured cache time) run gpg-preset-passphrase --preset [fingerprint], which will read the passphrase from stdin, or additionally use a --passphrase passphrase option to directly set it in your query. Be aware that when using both the echo or --passphrase approach, other system users might get hold of the passphrase by listing processes; better directly write to the process' stdin from C#.