Skip to content

Instantly share code, notes, and snippets.

@dieseltravis
Last active October 31, 2023 07:26
Show Gist options
  • Save dieseltravis/8323431 to your computer and use it in GitHub Desktop.
Save dieseltravis/8323431 to your computer and use it in GitHub Desktop.
sample app that uses PGP Encryption using Bouncy Castle's C# API
// Assembly BouncyCastle.Crypto, Version=1.8.1.0
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;
namespace EncryptionSample
{
public static class CryptoHelper
{
public static string DecryptPgpDataFile(string inputFilePath, byte[] decryptKey, string password)
{
// reading in the file as byte[] prevents a "unknown object in stream 47" IO exception thrown by bouncy castle
var inputData = File.ReadAllBytes(inputFilePath);
return DecryptPgpBytes(inputData, decryptKey, password);
}
public static string DecryptPgpData(string inputData, byte[] decryptKey, string password, Encoding encoding = null)
{
if (encoding == null)
{
encoding = Encoding.UTF8;
}
byte[] bytes = encoding.GetBytes(inputData);
return DecryptPgpBytes(bytes, decryptKey, password);
}
public static string DecryptPgpBytes(byte[] inputData, byte[] keyData, string pass)
{
string output;
using (var inputStream = new MemoryStream(inputData))
{
using (var keyIn = new MemoryStream(keyData))
{
output = DecryptPgpData(inputStream, keyIn, pass);
}
}
return output;
}
public static string DecryptPgpData(Stream inputStream, Stream privateKeyStream, string passPhrase)
{
string output;
PgpObjectFactory pgpFactory = new PgpObjectFactory(PgpUtilities.GetDecoderStream(inputStream));
// find secret key
PgpSecretKeyRingBundle pgpKeyRing = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(privateKeyStream));
PgpObject pgp = pgpFactory?.NextPgpObject();
if (pgp == null)
{
var ex0 = new PgpException("The PGP Object is null");
throw ex0;
}
// the first object might be a PGP marker packet.
PgpEncryptedDataList encryptedData;
if (pgp is PgpEncryptedDataList list)
{
encryptedData = list;
}
else
{
encryptedData = (PgpEncryptedDataList)pgpFactory.NextPgpObject();
}
if (encryptedData == null)
{
var ex1 = new PgpException("The PGP Encrypted Data is null");
throw ex1;
}
// decrypt
PgpPrivateKey privateKey = null;
PgpPublicKeyEncryptedData pubKeyData = null;
foreach (PgpPublicKeyEncryptedData pubKeyDataItem in encryptedData.GetEncryptedDataObjects())
{
privateKey = FindSecretKey(pgpKeyRing, pubKeyDataItem.KeyId, passPhrase.ToCharArray());
if (privateKey != null)
{
pubKeyData = pubKeyDataItem;
break;
}
}
if (privateKey == null)
{
var ex2 = new ArgumentException("Secret key for message not found.");
throw ex2;
}
PgpObjectFactory plainFact = null;
using (Stream clear = pubKeyData.GetDataStream(privateKey))
{
plainFact = new PgpObjectFactory(clear);
}
PgpObject message = plainFact.NextPgpObject();
if (message is PgpCompressedData compressedData)
{
PgpObjectFactory pgpCompressedFactory = null;
using (Stream compDataIn = compressedData.GetDataStream())
{
pgpCompressedFactory = new PgpObjectFactory(compDataIn);
}
message = pgpCompressedFactory.NextPgpObject();
if (message is PgpOnePassSignatureList)
{
message = pgpCompressedFactory.NextPgpObject();
}
PgpLiteralData literalData = (PgpLiteralData)message;
using (Stream unc = literalData.GetInputStream())
{
output = GetString(unc);
}
}
else if (message is PgpLiteralData literalData)
{
using (Stream unc = literalData.GetInputStream())
{
output = GetString(unc);
}
}
else if (message is PgpOnePassSignatureList)
{
var ex3 = new PgpException("Encrypted message contains a signed message - not literal data.");
throw ex3;
}
else
{
var ex4 = new PgpException("Message is not a simple encrypted file - type unknown.");
throw ex4;
}
return output;
}
public static void EncryptPgpFile(string inputFilePath, string outputFilePath, byte[] publicKeyData, bool armor = true, bool withIntegrityCheck = true)
{
using (Stream publicKeyStream = new MemoryStream(publicKeyData))
{
PgpPublicKey pubKey = ReadPublicKey(publicKeyStream);
using (MemoryStream outputBytes = new MemoryStream())
{
PgpCompressedDataGenerator dataCompressor = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
PgpUtilities.WriteFileToLiteralData(dataCompressor.Open(outputBytes), PgpLiteralData.Binary, new FileInfo(inputFilePath));
dataCompressor.Close();
PgpEncryptedDataGenerator dataGenerator = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
dataGenerator.AddMethod(pubKey);
byte[] dataBytes = outputBytes.ToArray();
using (Stream outputStream = File.Create(outputFilePath))
{
if (armor)
{
using (ArmoredOutputStream armoredStream = new ArmoredOutputStream(outputStream))
{
WriteStream(dataGenerator.Open(armoredStream, dataBytes.Length), ref dataBytes);
}
}
else
{
WriteStream(dataGenerator.Open(outputStream, dataBytes.Length), ref dataBytes);
}
}
}
}
}
private static PgpPrivateKey FindSecretKey(PgpSecretKeyRingBundle pgpSec, long keyId, char[] pass)
{
PgpSecretKey pgpSecKey = pgpSec.GetSecretKey(keyId);
if (pgpSecKey == null)
{
return null;
}
return pgpSecKey.ExtractPrivateKey(pass);
}
private static PgpPublicKey ReadPublicKey(Stream inputStream)
{
inputStream = PgpUtilities.GetDecoderStream(inputStream);
PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(inputStream);
foreach (PgpPublicKeyRing keyRing in pgpPub.GetKeyRings())
{
foreach (PgpPublicKey key in keyRing.GetPublicKeys())
{
if (key.IsEncryptionKey)
{
return key;
}
}
}
var ex = new ArgumentException("Can't find encryption key in key ring.");
throw ex;
}
// IO functions:
private static string GetString(Stream inputStream)
{
string output;
using (StreamReader reader = new StreamReader(inputStream))
{
output = reader.ReadToEnd();
}
return output;
}
private static void WriteStream(Stream inputStream, ref byte[] dataBytes)
{
using (Stream outputStream = inputStream)
{
outputStream.Write(dataBytes, 0, dataBytes.Length);
}
}
}
}
namespace EncryptionSample
{
class Program
{
// load keys and password from a secure place, like Azure Key Vault
private static readonly byte[] DECRYPT_KEY = Encoding.UTF8.GetBytes("private key");
private static readonly string PASSWORD = "secret password";
private static readonly byte[] ENCRYPT_KEY = Encoding.UTF8.GetBytes("public key");
static void Main(string[] args)
{
// pass in a string encrypted data to decrypt
string decrypted = CryptoHelper.DecryptPgpData("-----BEGIN PGP MESSAGE----- some pgp-wrapped encrypted string that the private key and password will open");
// pass in 2 file paths to generate the encrypted file
CryptoHelper.EncryptPgpFile(@"\plain-text.txt", @"\pgp-encrypted.asc", ENCRYPT_KEY);
}
}
}
@simonebernasconi
Copy link

Hi dieseltravis,
thanks for posting your code. I'm trying to encrypt a txt file using PGP but I don't understand how to do this. I saw in your code that you declare in App.config 3 variables. Two of them are .asc files. Can you explain me what I have to do in order to encrypt a message. How can i generate the public and private keys needed for the encryption. Thanks in advance for your help =)

@ankitsablok89
Copy link

This approach of encryption and decryption using bouncy castle API has definitely helped me a lot while solving the same problem at my end. Thanks for the script, worked beautifully, its almost the same thing I need but I need to add something more to solve the purpose.

@nadeemkhoury
Copy link

Hello @dieseltravis, I would like to ask you if you have an idea how we can reconstruct the msg (MAPI Object) after decrypting the Pgp Message. Thank you.

@YashwanthChundru
Copy link

Could someone please explain the process of encryption using these codes

@dieseltravis
Copy link
Author

I'm really sorry folks, I didn't see any comments on this gist until just now! I'll try to update the code to be more clear,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment