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");
}
	

27 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.

Leave a Reply

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