PGP Encryption and Decryption in C#

A while ago I knocked up a NuGet package called PgpCore which uses the BouncyCastle library to easily encrypt and decrypt files and streams.

The code for the project is here and this post is mainly here to point anyone in need towards it as I had to fix a bug in it recently and was once again reminded of how many confusing StackOverflow answers exist about this subject.

	
using (PGP pgp = new PGP())
{
	// Generate keys
	pgp.GenerateKey(@"C:\TEMP\keys\public.asc", @"C:\TEMP\keys\private.asc", "email@email.com", "password");
	// Encrypt file
	pgp.EncryptFile(@"C:\TEMP\keys\content.txt", @"C:\TEMP\keys\content__encrypted.pgp", @"C:\TEMP\keys\public.asc", true, true);
	// Encrypt and sign file
	pgp.EncryptFileAndSign(@"C:\TEMP\keys\content.txt", @"C:\TEMP\keys\content__encrypted_signed.pgp", @"C:\TEMP\keys\public.asc", @"C:\TEMP\keys\private.asc", "password", true, true);
	// Decrypt file
	pgp.DecryptFile(@"C:\TEMP\keys\content__encrypted.pgp", @"C:\TEMP\keys\content__decrypted.txt", @"C:\TEMP\keys\private.asc", "password");
	// Decrypt signed file
	pgp.DecryptFile(@"C:\TEMP\keys\content__encrypted_signed.pgp", @"C:\TEMP\keys\content__decrypted_signed.txt", @"C:\TEMP\keys\private.asc", "password");

	// Encrypt stream
	using (FileStream inputFileStream = new FileStream(@"C:\TEMP\keys\content.txt", FileMode.Open))
	using (Stream outputFileStream = File.Create(@"C:\TEMP\keys\content__encrypted2.pgp"))
	using (Stream publicKeyStream = new FileStream(@"C:\TEMP\keys\public.asc", FileMode.Open))
		pgp.EncryptStream(inputFileStream, outputFileStream, publicKeyStream, true, true);

	// Decrypt stream
	using (FileStream inputFileStream = new FileStream(@"C:\TEMP\keys\content__encrypted2.pgp", FileMode.Open))
	using (Stream outputFileStream = File.Create(@"C:\TEMP\keys\content__decrypted2.txt"))
	using (Stream privateKeyStream = new FileStream(@"C:\TEMP\keys\private.asc", FileMode.Open))
		pgp.DecryptStream(inputFileStream, outputFileStream, privateKeyStream, "password");
}
	

45 Comments

Bob Loftus · 8th October 2018 at 6:48 pm

Thanks a lot, can’t wait to try it out 🙂

    sandeep · 6th December 2018 at 6:51 am

    not able to decrypte data by signed key.

    Santosh · 25th January 2019 at 9:41 am

    Not able to decrypte data by signed key getting below issue.

    Org.BouncyCastle.Bcpg.OpenPgp.PgpPublicKeyRing found where PgpSecretKeyRing expected

Nico · 14th February 2020 at 4:24 am

Works great, thanks man !

Manasa · 9th April 2020 at 3:13 pm

It worked and fantastic way of explanation…. Thank you!

landry · 20th April 2020 at 3:34 pm

Seeking some help with this error during file decryption, thank you so much.
I did see that this block of code returns the “message” object as NULL

Stream clear = pbe.GetDataStream(sKey);

PgpObjectFactory plainFact = new PgpObjectFactory(clear);

PgpObject message = plainFact.NextPgpObject();

if (message is PgpCompressedData)
{
PgpCompressedData cData = (PgpCompressedData)message;
PgpObjectFactory pgpFact = new PgpObjectFactory(cData.GetDataStream());

message = pgpFact.NextPgpObject();
}

The logic then returns False and falls into this reason:

string.Format(“Message is not a simple encrypted file. THe type unknown for file {0}”, outputFileName),

    Shinigami · 21st April 2020 at 9:53 am

    Hi, are you using my PGPCore library? If not I’m not sure I can help.

      Landry · 27th April 2020 at 3:32 pm

      Hi Shinigami, using bouncy castle.

        Shinigami · 27th April 2020 at 4:02 pm

        Sorry, don’t think I can help then.

        You could try asking on their GitHub page.

        https://github.com/bcgit/bc-csharp

          Landry · 29th April 2020 at 4:53 pm

          Thank you.

Ryan · 21st April 2020 at 9:49 pm

Hi, thanks for the work on this library. It works fine when running on Windows, but after deploying to kubernetes with dotnet core (linux), I get a “read only” file system error. Does this library use any temporary files? I know that the GnuPG library uses a path in the home directory for a keystore – does this library use something like that? I can add writable mounts but the error does not tell me which path is read-only so I’m not sure how to fix it. I already verified that the other paths are fine (input file & public key file are readable, output file is writeable). I even tested the same streams opening that I found in the code from github.

Basically I’m just wondering what file system paths are required to be writable for this library to work. Thanks!

Unhandled Exception: System.AggregateException: One or more errors occurred. (Read-only file system) —> System.IO.IOException: Read-only file system
at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
at PgpCore.PGP.EncryptFile(String inputFilePath, String outputFilePath, IEnumerable`1 publicKeyFilePaths, Boolean armor, Boolean withIntegrityCheck, String name)

    Shinigami · 22nd April 2020 at 9:48 am

    Hi, the library shouldn’t be doing any read/writes except for the files provided so this is a bit weird. If you use EncryptStream instead of EncryptFile does that also give the same error?

    Also, is this a very large file? I believe for Windows that if you try and hold something in memory that’s larger than the system RAM then it can get cached to disk, not sure if this is the case with Linux or not though.

    Feel free to open an issue on the project GitHub page, it’s probably easier to troubleshoot there.

    https://github.com/mattosaurus/PgpCore/issues

      Ryan · 22nd April 2020 at 8:24 pm

      Hi,
      Using EncryptStream() worked! This issue had me stressed out all night as my brain would not let it go and I had tried so many things. Having this work now makes all that stress go away. Thanks for making this code available with examples and thanks for helping a stranger like me on the internet so quickly. It motivates me to help others too.
      Thanks again,
      Ryan

Assil · 22nd April 2020 at 2:19 pm

Hi:
Thank you for the good work and greate sample code.
I noticed that EncryptFileAndSign does require the same private key for both decryption and signing, is that how it works? Is there any overload?
what is your take on that?

    Shinigami · 22nd April 2020 at 3:35 pm

    Hi, EncryptFileAndSign can use one public key to encrypt the file and then a different private key to sign the file.

    // Arrange
    await ArrangeAsync(keyType);
    PGP pgp = new PGP();

    // Act
    await pgp.EncryptFileAndSignAsync(contentFilePath, encryptedContentFilePath, publicKeyFilePath2, privateKeyFilePath1, password1);
    await pgp.DecryptFileAsync(encryptedContentFilePath, decryptedContentFilePath1, privateKeyFilePath2, password2);
    string decryptedContent = await File.ReadAllTextAsync(decryptedContentFilePath1);
    bool verified = await pgp.VerifyFileAsync(encryptedContentFilePath, publicKeyFilePath1);

    // Assert
    Assert.True(File.Exists(encryptedContentFilePath));
    Assert.True(File.Exists(decryptedContentFilePath1));
    Assert.Equal(content, decryptedContent.Trim());
    Assert.True(verified);

    // Teardown
    Teardown();

    The partner private key to the public key used to encrypt the file is required to decrypt the file, this can’t be done with a different key.

    Is this what you mean?

    If you think there’s a better way of doing this or you want to add an overload feel free to submit a PR.

    https://github.com/mattosaurus/PgpCore

      ASSIL ABDULRAHIM · 25th April 2020 at 1:40 am

      I think you answered me perfectly.
      As long as the private key for signing at End1, is different that the private key used for decryption at End2 then we are good.
      You confirm that right??
      I believe your package is the best even. Thank you for your hardwork and swift responses..

Peter Hansen · 27th April 2020 at 10:46 am

Hi this a super implementation that helped me.. but i’m getting this error “Secret key for message not found.” when i try to decrypt.. What i’m do is that a have a 2 console app one that encrypt and sign with public and private key and my second console app is decrypt the file. but the second fail with the mentioned error.. but when a run the encrypt-sign and decrypt in one flow veryting works fine, i only get the error when i run the decrypt alone with the inputfile and private key.. anyone know why this error?

    Shinigami · 27th April 2020 at 11:03 am

    Hi, I’m guessing it’s because either the path to your private key used for decrytpion is incorrect, your password is or you’re using the wrong key to decrypt.

    If it’s none of the above feel free to put together a sample project and raise an issue and I’ll have a quick look over it.

    https://github.com/mattosaurus/PgpCore/issues

      Bala S · 28th April 2020 at 10:38 am

      Hi,
      It’s seems that decrypt works fine if I remove the following line: pgpEncrypt.GenerateKey(…)
      So you don’t need to generatekey for decrypt, just the private key is that correct?

        Shinigami · 28th April 2020 at 11:28 am

        That’s correct, the GenerateKey line is only there to generate a key for you if you don’t already have one.

          Bala S · 28th April 2020 at 4:47 pm

          Hi
          I was wrong it didn’t work i created a issue as you mentioned : https://github.com/mattosaurus/PgpCore/issues/62

          Prakash S · 26th September 2020 at 9:46 am

          Hi, Do GenerateKey method is used to generate PGP encrypted private/public keys or it has some other purpose. Please explain, have confusion about usage of GenerateKey() method.

Mohan · 10th July 2020 at 12:19 am

Hi Shinigami, Thanks for sharing these details. I am trying to decrypt a file using an already existing Secret ring file with has extension skr and passphrase. Can this be done using your sample code?

    Mohan · 10th July 2020 at 2:11 am

    It worked like a charm!! Thanks a lot, Shinigami. Appreciate your effort.
    Let me share my experience with this code.
    I have a requirement only to decrypt the file as it was already encrypted. I don’t have much experience in Dotnet so initially was not sure how to use your sample code. So I am writing below steps which will help someone like me.
    1. Install the NuGet package called PGPCore. Right-click on the project in your solution and then select ‘manage NuGet packages’. Type in PGP core in the search button and install it. You may have some issues with the dot net framework so try to install the lower version of the package. In my case, I installed 2.2.0 instead of 2.4.1 and my dot net framework is 4.6.1
    2. Use the below code to decrypt the already encrypted file.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.IO;
    using PgpCore;

    namespace PGPDecrypt
    {
    class Program
    {
    static void Main(string[] args)
    {
    using (PGP pgp = new PGP())
    {
    pgp.DecryptFile(@”C:\Mohan\ADPDecryption\files\TEPLOYEES_20200331.CSV”, @”C:\Mohan\ADPDecryption\files\file__decrypted.CSV”, @”C:\Mohan\ADPDecryption\secri.skr”, “password”);
    using (FileStream inputFileStream = new FileStream(@”C:\Mohan\ADPDecryption\files\TEPLOYEES_20200331.CSV”, FileMode.Open))
    using (Stream outputFileStream = File.Create(@”C:\Mohan\ADPDecryption\files\file__decrypted.CSV”))
    using (Stream privateKeyStream = new FileStream(@”C:\Mohan\ADPDecryption\secri.skr”, FileMode.Open))
    pgp.DecryptStream(inputFileStream, outputFileStream, privateKeyStream, “password”);
    }
    }
    }
    }

    Shinigami · 10th July 2020 at 10:14 am

    Hi Mohan, thanks for sharing your code. I’m glad my package came in useful for you.

Jag · 17th March 2021 at 8:56 pm

using (var _ = new PgpCore.PGP())
{
// Decrypt file
_.DecryptFile(file1In, file1Out, key, passphrase); //This one works.

_.DecryptFile(file2In, file2Out, key, passphrase); //This one throws Exception. ‘Secret key for message not found.’

}

Any idea.

    Shinigami · 18th March 2021 at 10:23 am

    This is probably because you’re re-using the key stream and this doesn’t get disposed of after use.

    I recently updated the library to hold the keys in memory which makes it easier to re-use the PGP object for performing multiple operations.

    https://github.com/mattosaurus/PgpCore#decryptfileasync

      Jag · 18th March 2021 at 3:49 pm

      Thanks for your help. That would help a lot.

Jag · 18th March 2021 at 5:15 pm

// Load keys
var privateKey = new FileInfo(key);
var encryptionKeys = new EncryptionKeys(privateKey, passphrase);

// Reference input/output files
var input1 = new FileInfo(file1In);
var decrypted1 = new FileInfo(file1Out);

// Reference input/output files
var input2 = new FileInfo(file2In);
var decrypted2 = new FileInfo(file2Out);

// Decrypt
PGP pgp = new PGP(encryptionKeys);
await pgp.DecryptFileAsync(input1, decrypted1).ConfigureAwait(false);
await pgp.DecryptFileAsync(input2, decrypted2).ConfigureAwait(false); // I get the same ‘Secret key for message not found.’

Am I missing something.

    Shinigami · 19th March 2021 at 9:22 am

    I assume the same public key was used to encrypt file1In and file2In?

    If so do you want to raise an issue in the project and I’ll take a look at it there.

    https://github.com/mattosaurus/PgpCore/issues

    If you can provide a full reproducable example that would be great.

      Jag · 19th March 2021 at 3:02 pm

      Same public key was used. I will try to create an example.

        Jag · 19th March 2021 at 7:59 pm

        Sorry, I goofed up. The public keys are different.

yytan · 25th March 2021 at 8:55 am

Hi,

I have a specific requirement in terms of implementing the pgp encryption:

1. Sign the payload (JSON string) using SHA 256 and above with the private key.
2. Encrypt the signed payload with the public key using AES256 and above

The requirement specifically says sign first, then encrypt. The closest I could find in the library is EncryptArmoredStringAndSignAsync, is there a way to fulfill the requirement which is sign first, encrypt next? Or it’s the same? Appreciate if you could advice on this.

    Shinigami · 25th March 2021 at 11:07 am

    Hi, the EncryptArmoredStringAndSignAsync encrypts and then signs the input string so wouldn’t be suitable for your requirements. If you wanted to sign and then encrypt you’d need to make 2 seperate calls, first to SignArmoredStringAsync and then using the output from that as the input for EncryptArmoredStringAsync.

      yytan · 25th March 2021 at 12:41 pm

      Thanks for the prompt response! However, if the other end wants to read the original string, need to use ClearSignArmoredStringAsync?

      So the receiving side needs to do the following:
      1. DecryptArmoredStringAsync
      2. VerifyArmoredStringAsync
      3. ClearSignArmoredStringAsync

      Am I correct? Please advice. Thanks!

        Shinigami · 25th March 2021 at 4:15 pm

        So step 1 and 2 are correct, however I don’t actually have a method for outputting the signed content…

        I think I just assumed people would be encrypting and then signing which is taken care of by the DecryptAndVerify methods.

        The signed content is just base64 encoded so should be easy enough for you to extract but there would ideally be an optional parameter as part of my verify methods that outputs the original unsigned message.

        Feel free to submit a PR if this is something you’re able to do. If not I’ll take a look at adding it in when I get a chance.

        https://github.com/mattosaurus/PgpCore/pulls

Sri · 21st April 2021 at 4:17 pm

Hi,
When I tried decrypting, i’m getting “Secret key for message not found.” error

    Shinigami · 21st April 2021 at 5:08 pm

    Hi, are you using your private key to decrypt as specified here?

    https://github.com/mattosaurus/PgpCore#decrypt

      Sri · 21st April 2021 at 7:37 pm

      Yes used the private key to decrypt

        Sri · 21st April 2021 at 7:45 pm

        Basically the file was encrypted with TIBCO and they provided the private key to decrypt from my side (C#)

      sri · 21st April 2021 at 8:05 pm

      Used the below code for decryption…now I didnt receive the error message but the decrypted file is empty. Thanks for the help!
      FileInfo privateKey = new FileInfo(@”C:\sri\development\BEC\FPParse job changes\RSA keys\pgpTstFdle2-private.pub”);
      EncryptionKeys encryptionKeys = new EncryptionKeys(privateKey, “password”);

      // Reference input/output files
      FileInfo inputFile = new FileInfo(@”C:\sri\development\BEC\FPParse job changes\RSA keys\70C67A0000078568-1912021310121_balakeys.tmp”);
      FileInfo decryptedFile = new FileInfo(@”C:\sriduggirala\development\BEC\FPParse job changes\RSA keys\decrypted\decrypted.tmp”);

      // Decrypt
      PGP pgp = new PGP(encryptionKeys);
      pgp.DecryptFileAsync(inputFile, decryptedFile);

Mark · 18th June 2021 at 5:27 am

What is the function of using (PGP pgp = new PGP())?

    Shinigami · 18th June 2021 at 9:24 am

    This is creating a new instance of the PGP class.

Leave a Reply to Nico Cancel reply

Avatar placeholder

Your email address will not be published. Required fields are marked *