PGP Using Bouncy Castle Libraries

I went a bit crazy trying to find useful code for using the Bouncy Castle libraries for PGP and so here is the result of combing the examples and the web and cleaning and rewriting what I was able to find into ultimately a working model that can do everything that I needed it to do (and a little more).

Files for pgpClassLib which contains the core functions that the last file listed uses.

PgpKeyRingGenerator.cs
[csharp]
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;

namespace pgpClassLib
{
internal class pgpKeyRingParams
{
public pgpKeyRingParams()
{
RsaParams = new RsaKeyGenerationParameters(BigInteger.ValueOf(0x10001), new SecureRandom(), 2048, 12);
}

public SymmetricKeyAlgorithmTag? PrivateKeyEncryptionAlgorithm { get; set; }
public SymmetricKeyAlgorithmTag[] SymmetricAlgorithms { get; set; }
public HashAlgorithmTag[] HashAlgorithms { get; set; }
public RsaKeyGenerationParameters RsaParams { get; private set; }
public string Identity { get; set; }
public string Password { private get; set; }

public char[] GetPassword()
{
return Password.ToCharArray();
}
}
}
[/csharp]

pgpGenerateKeyRingGenerator.cs
[csharp]
using System;
using System.Diagnostics;
using System.Linq;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Security;

namespace pgpClassLib
{
public static class pgpGenerateKeyRingGenerator
{
public static PgpKeyRingGenerator generateKeyRingGenerator(String identity, String password)
{
var pgpKeyRingParams = new pgpKeyRingParams();
pgpKeyRingParams.Password = password;
pgpKeyRingParams.Identity = identity;
pgpKeyRingParams.PrivateKeyEncryptionAlgorithm = SymmetricKeyAlgorithmTag.Aes128;
pgpKeyRingParams.SymmetricAlgorithms = new[]
{
SymmetricKeyAlgorithmTag.Aes256,
SymmetricKeyAlgorithmTag.Aes192,
SymmetricKeyAlgorithmTag.Aes128
};

pgpKeyRingParams.HashAlgorithms = new[]
{
HashAlgorithmTag.Sha256,
HashAlgorithmTag.Sha1,
HashAlgorithmTag.Sha384,
HashAlgorithmTag.Sha512,
HashAlgorithmTag.Sha224
};

var generator = GeneratorUtilities.GetKeyPairGenerator("RSA");
generator.Init(pgpKeyRingParams.RsaParams);

/* Create the master (signing-only) key. */
var masterKeyPair = new PgpKeyPair(
PublicKeyAlgorithmTag.RsaSign, generator.GenerateKeyPair(), DateTime.UtcNow);

Debug.WriteLine("Generated master key with ID " + masterKeyPair.KeyId.ToString("X"));

var masterSubpckGen = new PgpSignatureSubpacketGenerator();
masterSubpckGen.SetKeyFlags(
false, PgpKeyFlags.CanSign | PgpKeyFlags.CanCertify);
masterSubpckGen.SetPreferredSymmetricAlgorithms(false,
(from a in pgpKeyRingParams.SymmetricAlgorithms select (int) a).ToArray());
masterSubpckGen.SetPreferredHashAlgorithms(false,
(from a in pgpKeyRingParams.HashAlgorithms select (int) a).ToArray());

/* Create a signing and encryption key for daily use. */
var encKeyPair = new PgpKeyPair(
PublicKeyAlgorithmTag.RsaGeneral, generator.GenerateKeyPair(), DateTime.UtcNow);

Debug.WriteLine("Generated encryption key with ID " + encKeyPair.KeyId.ToString("X"));

var encSubpckGen = new PgpSignatureSubpacketGenerator();
encSubpckGen.SetKeyFlags(
false, PgpKeyFlags.CanEncryptCommunications | PgpKeyFlags.CanEncryptStorage);

masterSubpckGen.SetPreferredSymmetricAlgorithms(false,
(from a in pgpKeyRingParams.SymmetricAlgorithms select (int) a).ToArray());

masterSubpckGen.SetPreferredHashAlgorithms(false,
(from a in pgpKeyRingParams.HashAlgorithms select (int) a).ToArray());

/* Create the key ring. */
var keyRingGen = new PgpKeyRingGenerator(
PgpSignature.DefaultCertification,
masterKeyPair,
pgpKeyRingParams.Identity,
pgpKeyRingParams.PrivateKeyEncryptionAlgorithm.Value,
pgpKeyRingParams.GetPassword(),
true,
masterSubpckGen.Generate(),
null,
new SecureRandom());

/* Add encryption subkey. */
keyRingGen.AddSubKey(encKeyPair, encSubpckGen.Generate(), null);

return keyRingGen;
}
}
}
[/csharp]

pgpEncryptionKeys.cs
[csharp]
using System;
using System.IO;
using System.Linq;
using Org.BouncyCastle.Bcpg.OpenPgp;

namespace pgpClassLib
{
public class pgpEncryptionKeys
{
/// <summary>
/// Initializes a new instance of the EncryptionKeys class.
/// Two keys are required to encrypt and sign data. Your private key and the recipients public key.
/// The data is encrypted with the recipients public key and signed with your private key.
/// </summary>
/// <param name="publicKeyPath">The key used to encrypt the data</param>
/// <param name="privateKeyPath">The key used to sign the data.</param>
/// <param name="passPhrase">The (your) password required to access the private key</param>
/// <exception cref="ArgumentException">Public key not found. Private key not found. Missing password</exception>
public pgpEncryptionKeys(string publicKeyPath, string privateKeyPath, string passPhrase)
{
if (!File.Exists(publicKeyPath))
throw new ArgumentException("Public key file not found", "publicKeyPath");
if (!File.Exists(privateKeyPath))
throw new ArgumentException("Private key file not found", "privateKeyPath");
if (String.IsNullOrEmpty(passPhrase))
throw new ArgumentException("passPhrase is null or empty.", "passPhrase");

PublicKey = ReadPublicKey(publicKeyPath);
SecretKey = ReadSecretKey(privateKeyPath);
PrivateKey = ReadPrivateKey(passPhrase);
}

public PgpPublicKey PublicKey { get; private set; }
public PgpPrivateKey PrivateKey { get; private set; }
public PgpSecretKey SecretKey { get; private set; }

#region Private Key

private PgpPrivateKey ReadPrivateKey(string passPhrase)
{
var privateKey = SecretKey.ExtractPrivateKey(passPhrase.ToCharArray());
if (privateKey != null) return privateKey;
throw new ArgumentException("No private key found in secret key.");
}

#endregion Private Key

#region Secret Key

private PgpSecretKey ReadSecretKey(string privateKeyPath)
{
using (Stream keyIn = File.OpenRead(privateKeyPath))
{
using (var inputStream = PgpUtilities.GetDecoderStream(keyIn))
{
var secretKeyRingBundle = new PgpSecretKeyRingBundle(inputStream);
var foundKey = GetFirstSecretKey(secretKeyRingBundle);
if (foundKey != null) return foundKey;
}
}
throw new ArgumentException("Can’t find signing key in key ring.");
}

/// <summary>
/// Return the first key we can use to encrypt.
/// Note: A file can contain multiple keys (stored in "key rings")
/// </summary>
private PgpSecretKey GetFirstSecretKey(PgpSecretKeyRingBundle secretKeyRingBundle)
{
foreach (PgpSecretKeyRing kRing in secretKeyRingBundle.GetKeyRings())
{
var key =
kRing.GetSecretKeys().Cast<PgpSecretKey>().FirstOrDefault(k => k.IsSigningKey);
if (key != null) return key;
}
return null;
}

#endregion Secret Key

#region Public Key

private PgpPublicKey ReadPublicKey(string publicKeyPath)
{
using (Stream keyIn = File.OpenRead(publicKeyPath))
{
using (var inputStream = PgpUtilities.GetDecoderStream(keyIn))
{
var publicKeyRingBundle = new PgpPublicKeyRingBundle(inputStream);
var foundKey = GetFirstPublicKey(publicKeyRingBundle);
if (foundKey != null) return foundKey;
}
}
throw new ArgumentException("No encryption key found in public key ring.");
}

private PgpPublicKey GetFirstPublicKey(PgpPublicKeyRingBundle publicKeyRingBundle)
{
foreach (PgpPublicKeyRing kRing in publicKeyRingBundle.GetKeyRings())
{
var key =
kRing.GetPublicKeys().Cast<PgpPublicKey>().FirstOrDefault(k => k.IsEncryptionKey);
if (key != null) return key;
}
return null;
}

#endregion Public Key
}
}
[/csharp]

pgpEncryptDecrypt.cs
[csharp]
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Utilities.IO;

namespace pgpClassLib
{
public static class pgpEncryptDecrypt
{
private const int BufferSize = 0x10000; // should always be power of 2

public static string PublicKeyRingDump(string fqnKeyFile)
{
var sb = new StringBuilder();

using (Stream fs = File.OpenRead(fqnKeyFile))
{
// Read the public key rings
var pubRings = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(fs));

foreach (PgpPublicKeyRing pgpPub in pubRings.GetKeyRings())
{
try
{
pgpPub.GetPublicKey();
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
Console.Error.WriteLine(e.StackTrace);
continue;
}

var subkey = "";

foreach (PgpPublicKey pgpKey in pgpPub.GetPublicKeys())
{
sb.AppendLine("Key ID: " + pgpKey.KeyId.ToString("X") + subkey);
Console.WriteLine("Key ID: " + pgpKey.KeyId.ToString("X") + subkey);
subkey = " (subkey)";

sb.AppendLine("Algorithm: " + GetAlgorithm(pgpKey.Algorithm));
sb.AppendLine("Fingerprint: " + Hex.ToHexString(pgpKey.GetFingerprint()));
Console.WriteLine(" Algorithm: " + GetAlgorithm(pgpKey.Algorithm));
Console.WriteLine(" Fingerprint: " + Hex.ToHexString(pgpKey.GetFingerprint()));

sb.AppendLine("BitStrength: " + pgpKey.BitStrength);
sb.AppendLine("CreationTime: " + pgpKey.CreationTime.ToString("MM/dd/yy HH:mm:ss"));

sb.AppendLine("");
}
}

try
{
var ring = new PgpPublicKeyRing(PgpUtilities.GetDecoderStream(fs));
var key = ring.GetPublicKey();

// iterate through all direct key signatures and look for NotationData sub packets
foreach (PgpSignature sig in key.GetSignaturesOfType(PgpSignature.DirectKey))
{
sb.AppendLine("Signature date is: "
+ sig.GetHashedSubPackets().GetSignatureCreationTime());
Console.WriteLine("Signature date is: "
+ sig.GetHashedSubPackets().GetSignatureCreationTime());

var data = sig.GetHashedSubPackets().GetNotationDataOccurences();

foreach (var notdat in data)
{
sb.AppendLine("Found Notation named ‘" + notdat.GetNotationName()
+ "’ with content ‘" + notdat.GetNotationValue() + "’.");
Console.WriteLine("Found Notation named ‘" + notdat.GetNotationName()
+ "’ with content ‘" + notdat.GetNotationValue() + "’.");
}
}
}
catch (Exception e)
{
sb.AppendLine("Error getting public key: " + e.Message);
Console.Error.WriteLine(e.Message);
Console.Error.WriteLine(e.StackTrace);
}
}
sb.AppendLine("");

return sb.ToString();
}

public static string GetAlgorithm(PublicKeyAlgorithmTag algId)
{
switch (algId)
{
case PublicKeyAlgorithmTag.RsaGeneral:
return "RsaGeneral";
case PublicKeyAlgorithmTag.RsaEncrypt:
return "RsaEncrypt";
case PublicKeyAlgorithmTag.RsaSign:
return "RsaSign";
case PublicKeyAlgorithmTag.ElGamalEncrypt:
return "ElGamalEncrypt";
case PublicKeyAlgorithmTag.Dsa:
return "DSA";
case PublicKeyAlgorithmTag.EC:
return "EC";
case PublicKeyAlgorithmTag.ECDsa:
return "ECDSA";
case PublicKeyAlgorithmTag.ElGamalGeneral:
return "ElGamalGeneral";
case PublicKeyAlgorithmTag.DiffieHellman:
return "DiffieHellman";
}
return "unknown";
}

#region Encrypt

/// <summary>
/// Encrypt the file
/// </summary>
/// <param name="inputFile"></param>
/// <param name="outputFile"></param>
/// <param name="publicKeyFile"></param>
/// <param name="armor"></param>
/// <param name="withIntegrityCheck"></param>
public static void EncryptFile(string inputFile, string outputFile, string publicKeyFile, bool armor,
bool withIntegrityCheck)
{
using (Stream publicKeyStream = File.OpenRead(publicKeyFile))
{
var encKey = ReadPublicKey(publicKeyStream);

using (var bOut = new MemoryStream())
{
var comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
PgpUtilities.WriteFileToLiteralData(comData.Open(bOut), PgpLiteralData.Binary,
new FileInfo(inputFile));

comData.Close();
var cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256,
withIntegrityCheck, new SecureRandom());

cPk.AddMethod(encKey);
var bytes = bOut.ToArray();

using (Stream outputStream = File.Create(outputFile))
{
if (armor)
{
using (var armoredStream = new ArmoredOutputStream(outputStream))
using (var cOut = cPk.Open(armoredStream, bytes.Length))
{
cOut.Write(bytes, 0, bytes.Length);
}
}
else
{
using (var cOut = cPk.Open(outputStream, bytes.Length))
{
cOut.Write(bytes, 0, bytes.Length);
}
}
}
}
}
}

public static void EncryptFile(string inputFile, string outputFile, PgpPublicKey encKey, bool armor,
bool withIntegrityCheck)
{
using (var bOut = new MemoryStream())
{
var comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
PgpUtilities.WriteFileToLiteralData(comData.Open(bOut), PgpLiteralData.Binary,
new FileInfo(inputFile));

comData.Close();
var cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256,
withIntegrityCheck, new SecureRandom());

cPk.AddMethod(encKey);
var bytes = bOut.ToArray();

using (Stream outputStream = File.Create(outputFile))
{
if (armor)
{
using (var armoredStream = new ArmoredOutputStream(outputStream))
using (var cOut = cPk.Open(armoredStream, bytes.Length))
{
cOut.Write(bytes, 0, bytes.Length);
}
}
else
{
using (var cOut = cPk.Open(outputStream, bytes.Length))
{
cOut.Write(bytes, 0, bytes.Length);
}
}
}
}
}

#endregion Encrypt

#region Encrypt and Sign

/// <summary>
/// Encrypt and sign the file pointed to by unencryptedFileInfo
/// </summary>
/// <param name="actualFileName"></param>
/// <param name="embeddedFileName"></param>
/// <param name="keyIn"></param>
/// <param name="keyId"></param>
/// <param name="outputStream"></param>
/// <param name="password"></param>
/// <param name="armor"></param>
/// <param name="withIntegrityCheck"></param>
/// <param name="encKey"></param>
public static void SignAndEncryptFile(string actualFileName, string embeddedFileName,
Stream keyIn, long keyId, Stream outputStream,
char[] password, bool armor, bool withIntegrityCheck, PgpPublicKey encKey)
{
const int bufferSize = 1 << 16; // should always be power of 2

if (armor)
outputStream = new ArmoredOutputStream(outputStream);

// Init encrypted data generator
var encryptedDataGenerator =
new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, withIntegrityCheck, new SecureRandom());
encryptedDataGenerator.AddMethod(encKey);
var encryptedOut = encryptedDataGenerator.Open(outputStream, new byte[bufferSize]);

// Init compression
var compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
var compressedOut = compressedDataGenerator.Open(encryptedOut);

// Init signature
var pgpSecBundle = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
var pgpSecKey = pgpSecBundle.GetSecretKey(keyId);
if (pgpSecKey == null)
throw new ArgumentException(keyId.ToString("X") + " could not be found in specified key ring bundle.",
"keyId");
var pgpPrivKey = pgpSecKey.ExtractPrivateKey(password);
var signatureGenerator = new PgpSignatureGenerator(pgpSecKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
signatureGenerator.InitSign(PgpSignature.BinaryDocument, pgpPrivKey);
foreach (string userId in pgpSecKey.PublicKey.GetUserIds())
{
var spGen = new PgpSignatureSubpacketGenerator();
spGen.SetSignerUserId(false, userId);
signatureGenerator.SetHashedSubpackets(spGen.Generate());
// Just the first one!
break;
}
signatureGenerator.GenerateOnePassVersion(false).Encode(compressedOut);

// Create the Literal Data generator output stream
var literalDataGenerator = new PgpLiteralDataGenerator();
var embeddedFile = new FileInfo(embeddedFileName);
var actualFile = new FileInfo(actualFileName);
var literalOut = literalDataGenerator.Open(compressedOut, PgpLiteralData.Binary,
embeddedFile.Name, actualFile.LastWriteTime, new byte[bufferSize]);

// Open the input file
var inputStream = actualFile.OpenRead();

var buf = new byte[bufferSize];
int len;
while ((len = inputStream.Read(buf, 0, buf.Length)) > 0)
{
literalOut.Write(buf, 0, len);
signatureGenerator.Update(buf, 0, len);
}

literalOut.Close();
literalDataGenerator.Close();
signatureGenerator.Generate().Encode(compressedOut);
compressedOut.Close();
compressedDataGenerator.Close();
encryptedOut.Close();
encryptedDataGenerator.Close();
inputStream.Close();

if (armor)
outputStream.Close();
}

public static void EncryptAndSign(string inputFile, string outputFile, string publicKeyFile,
string privateKeyFile, string passPhrase, bool armor)
{
var encryptionKeys = new pgpEncryptionKeys(publicKeyFile, privateKeyFile, passPhrase);

if (!File.Exists(inputFile))
throw new FileNotFoundException(String.Format("Input file [{0}] does not exist.", inputFile));

if (!File.Exists(publicKeyFile))
throw new FileNotFoundException(String.Format("Public Key file [{0}] does not exist.", publicKeyFile));

if (!File.Exists(privateKeyFile))
throw new FileNotFoundException(String.Format("Private Key file [{0}] does not exist.", privateKeyFile));

if (String.IsNullOrEmpty(passPhrase))
throw new ArgumentNullException("passPhrase");

if (encryptionKeys == null)
throw new ArgumentNullException("privateKeyFile");

using (Stream outputStream = File.Create(outputFile))
{
if (armor)
using (var armoredOutputStream = new ArmoredOutputStream(outputStream))
{
OutputEncrypted(inputFile, armoredOutputStream, encryptionKeys);
}
else
OutputEncrypted(inputFile, outputStream, encryptionKeys);
}
}

private static void OutputEncrypted(string inputFile, Stream outputStream, pgpEncryptionKeys encryptionKeys)
{
using (var encryptedOut = ChainEncryptedOut(outputStream, encryptionKeys))
{
var unencryptedFileInfo = new FileInfo(inputFile);
using (var compressedOut = ChainCompressedOut(encryptedOut))
{
var signatureGenerator = InitSignatureGenerator(compressedOut, encryptionKeys);
using (var literalOut = ChainLiteralOut(compressedOut, unencryptedFileInfo))
{
using (var inputFileStream = unencryptedFileInfo.OpenRead())
{
WriteOutputAndSign(compressedOut, literalOut, inputFileStream, signatureGenerator);
inputFileStream.Close();
}
}
}
}
}

private static void WriteOutputAndSign(Stream compressedOut, Stream literalOut, FileStream inputFile,
PgpSignatureGenerator signatureGenerator)
{
int length;
var buf = new byte[BufferSize];
while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)
{
literalOut.Write(buf, 0, length);
signatureGenerator.Update(buf, 0, length);
}
signatureGenerator.Generate().Encode(compressedOut);
}

private static Stream ChainEncryptedOut(Stream outputStream, pgpEncryptionKeys mEncryptionKeys)
{
var encryptedDataGenerator =
new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, new SecureRandom());
encryptedDataGenerator.AddMethod(mEncryptionKeys.PublicKey);
return encryptedDataGenerator.Open(outputStream, new byte[BufferSize]);
}

private static Stream ChainCompressedOut(Stream encryptedOut)
{
var compressedDataGenerator =
new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
return compressedDataGenerator.Open(encryptedOut);
}

private static Stream ChainLiteralOut(Stream compressedOut, FileInfo file)
{
var pgpLiteralDataGenerator = new PgpLiteralDataGenerator();
return pgpLiteralDataGenerator.Open(compressedOut, PgpLiteralData.Binary, file);
}

private static PgpSignatureGenerator InitSignatureGenerator(Stream compressedOut,
pgpEncryptionKeys mEncryptionKeys)
{
const bool isCritical = false;
const bool isNested = false;
var tag = mEncryptionKeys.SecretKey.PublicKey.Algorithm;
var pgpSignatureGenerator = new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);
pgpSignatureGenerator.InitSign(PgpSignature.BinaryDocument, mEncryptionKeys.PrivateKey);
foreach (string userId in mEncryptionKeys.SecretKey.PublicKey.GetUserIds())
{
var subPacketGenerator = new PgpSignatureSubpacketGenerator();
subPacketGenerator.SetSignerUserId(isCritical, userId);
pgpSignatureGenerator.SetHashedSubpackets(subPacketGenerator.Generate());
// Just the first one!
break;
}
pgpSignatureGenerator.GenerateOnePassVersion(isNested).Encode(compressedOut);
return pgpSignatureGenerator;
}

#endregion Encrypt and Sign

#region Decrypt

/// <summary>
/// decrypt a given file
/// </summary>
/// <param name="inputfile"></param>
/// <param name="privateKeyFile"></param>
/// <param name="passPhrase"></param>
/// <param name="outputFile"></param>
public static void Decrypt(string inputfile, string privateKeyFile, string passPhrase, string outputFile)
{
if (!File.Exists(inputfile))
throw new FileNotFoundException(String.Format("Encrypted File [{0}] not found.", inputfile));

if (!File.Exists(privateKeyFile))
throw new FileNotFoundException(String.Format("Private Key File [{0}] not found.", privateKeyFile));

if (String.IsNullOrEmpty(outputFile))
throw new ArgumentNullException("outputFile");

using (Stream inputStream = File.OpenRead(inputfile))
{
using (Stream keyIn = File.OpenRead(privateKeyFile))
{
Decrypt(inputStream, keyIn, passPhrase, outputFile);
}
}
}

/// <summary>
/// decrypt a given stream
/// </summary>
/// <param name="inputStream"></param>
/// <param name="privateKeyStream"></param>
/// <param name="passPhrase"></param>
/// <param name="outputFile"></param>
public static void Decrypt(Stream inputStream, Stream privateKeyStream, string passPhrase, string outputFile)
{
PgpEncryptedDataList enc;
PgpPrivateKey sKey = null;
PgpPublicKeyEncryptedData pbe = null;

var pgpF = new PgpObjectFactory(PgpUtilities.GetDecoderStream(inputStream));
// find secret key
var pgpSec = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(privateKeyStream));

var o = pgpF.NextPgpObject();

// the first object might be a PGP marker packet.
var list = o as PgpEncryptedDataList;
if (list != null)
enc = list;
else
enc = (PgpEncryptedDataList) pgpF.NextPgpObject();

// decrypt
foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
{
sKey = FindSecretKey(pgpSec, pked.KeyId, passPhrase.ToCharArray());

if (sKey != null)
{
pbe = pked;
break;
}
}

if (sKey == null)
throw new ArgumentException("Secret key for message not found.");

PgpObjectFactory plainFact;

using (var clear = pbe.GetDataStream(sKey))
{
plainFact = new PgpObjectFactory(clear);
}

var message = plainFact.NextPgpObject();

var literalData = message as PgpCompressedData;
if (literalData != null)
{
var cData = literalData;
PgpObjectFactory of;

using (var compDataIn = cData.GetDataStream())
{
of = new PgpObjectFactory(compDataIn);
}

message = of.NextPgpObject();
if (message is PgpOnePassSignatureList)
{
message = of.NextPgpObject();
var ld = (PgpLiteralData) message;
using (Stream output = File.Create(outputFile))
{
var unc = ld.GetInputStream();
Streams.PipeAll(unc, output);
}
}
else
{
var ld = (PgpLiteralData) message;
using (Stream output = File.Create(outputFile))
{
var unc = ld.GetInputStream();
Streams.PipeAll(unc, output);
}
}
}
else
{
var data = message as PgpLiteralData;
if (data != null)
{
var ld = data;

using (Stream fOut = File.Create(outputFile))
{
var unc = ld.GetInputStream();
Streams.PipeAll(unc, fOut);
}
}
else if (message is PgpOnePassSignatureList)
throw new PgpException("Encrypted message contains a signed message – not literal data.");
else
throw new PgpException("Message is not a simple encrypted file – type unknown.");
}

#region commented code

//if (pbe.IsIntegrityProtected())
//{
// if (!pbe.Verify())
// msg = "message failed integrity check.";
// //Console.Error.WriteLine("message failed integrity check");
// else
// msg = "message integrity check passed.";
// //Console.Error.WriteLine("message integrity check passed");
//}
//else
//{
// msg = "no message integrity check.";
// //Console.Error.WriteLine("no message integrity check");
//}

#endregion commented code
}

#endregion Decrypt

#region Private helpers

/// <summary>
/// A simple routine that opens a key ring file and loads the first available key suitable for encryption.
/// </summary>
/// <param name="inputStream"></param>
/// <returns></returns>
private static PgpPublicKey ReadPublicKey(Stream inputStream)
{
inputStream = PgpUtilities.GetDecoderStream(inputStream);

var pgpPub = new PgpPublicKeyRingBundle(inputStream);

// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
// iterate through the key rings.
foreach (PgpPublicKeyRing kRing in pgpPub.GetKeyRings())
{
foreach (PgpPublicKey k in kRing.GetPublicKeys())
{
if (k.IsEncryptionKey)
return k;
}
}

throw new ArgumentException("Can’t find encryption key in key ring.");
}

/// <summary>
/// Search a secret key ring collection for a secret key corresponding to keyId if it exists
/// </summary>
/// <param name="pgpSec"></param>
/// <param name="keyId"></param>
/// <param name="pass"></param>
/// <returns></returns>
private static PgpPrivateKey FindSecretKey(PgpSecretKeyRingBundle pgpSec, long keyId, char[] pass)
{
var pgpSecKey = pgpSec.GetSecretKey(keyId);

if (pgpSecKey == null) return null;

return pgpSecKey.ExtractPrivateKey(pass);
}

#endregion Private helpers

// DirectKeySignature
public static void signPublicKey(
string secretKeyFile, string secretKeyPass, string publicKeyFile,
string notationName, string notationValue)
{
using (Stream secFis = File.OpenRead(secretKeyFile))
using (Stream pubFis = File.OpenRead(publicKeyFile))
{
var secRing = new PgpSecretKeyRing(
PgpUtilities.GetDecoderStream(secFis));
var ring = new PgpPublicKeyRing(
PgpUtilities.GetDecoderStream(pubFis));

// create the signed keyRing
var sRing = new PgpPublicKeyRing(
new MemoryStream(
SignPublicKey(secRing.GetSecretKey(), secretKeyPass,
ring.GetPublicKey(), notationName, notationValue, true), false));

// write the created keyRing to file
using (Stream fos = File.Create(Path.Combine(Path.GetDirectoryName(publicKeyFile), "SignedKey.asc")))
using (var aOut = new ArmoredOutputStream(fos))
{
sRing.Encode(aOut);
}
}
}

private static byte[] SignPublicKey(
PgpSecretKey secretKey,
string secretKeyPass,
PgpPublicKey keyToBeSigned,
string notationName,
string notationValue,
bool armor)
{
Stream os = new MemoryStream();
if (armor)
{
os = new ArmoredOutputStream(os);
}

var pgpPrivKey = secretKey.ExtractPrivateKey(
secretKeyPass.ToCharArray());

var sGen = new PgpSignatureGenerator(
secretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);

sGen.InitSign(PgpSignature.DirectKey, pgpPrivKey);

var bOut = new BcpgOutputStream(os);

sGen.GenerateOnePassVersion(false).Encode(bOut);

var spGen = new PgpSignatureSubpacketGenerator();

const bool isHumanReadable = true;

spGen.SetNotationData(true, isHumanReadable, notationName, notationValue);

var packetVector = spGen.Generate();
sGen.SetHashedSubpackets(packetVector);

bOut.Flush();

if (armor)
{
os.Close();
}

return PgpPublicKey.AddCertification(keyToBeSigned, sGen.Generate()).GetEncoded();
}
}
}
[/csharp]

Put this all together using a GUI using textboxes and buttons:

pgpManager

fmMain.cs
[csharp]
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using pgpClassLib;

#pragma warning disable 1587
/// <remarks>
/// http://www.pgpi.org/doc/pgpintro/
/// </remarks>
#pragma warning restore 1587

namespace PgpManager
{
public partial class fmMain : Form
{
public fmMain()
{
InitializeComponent();

folderBrowserDialog1.SelectedPath = @"C:\Temp";

openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
openFileDialog1.FilterIndex = 2;
openFileDialog1.RestoreDirectory = true;

// Set up the ToolTip text for the Button and Checkbox.
toolTip1.SetToolTip(tbDir, "Double Click to Select Directory.");
toolTip1.SetToolTip(tbPrivateKeyFile, "Double Click to Select File.");
toolTip1.SetToolTip(tbPublicKeyFile, "Double Click to Select File.");
toolTip1.SetToolTip(tbPrivateKeyFileED, "Double Click to Select File.");
toolTip1.SetToolTip(tbPublicKeyFileED, "Double Click to Select File.");
toolTip1.SetToolTip(tbUnencryptedFile, "Double Click to Select File.");
toolTip1.SetToolTip(tbEncryptedFile, "Double Click to Select File.");
}

private void tbDir_DoubleClick(object sender, EventArgs e)
{
var result = folderBrowserDialog1.ShowDialog();
if (result == DialogResult.OK)
tbDir.Text = folderBrowserDialog1.SelectedPath;
}

private void btncreatePGPKeyFiles_Click(object sender, EventArgs e)
{
try
{
if (!Directory.Exists(tbDir.Text))
throw new FileNotFoundException(String.Format("Directory [{0}] does not exist.", tbDir.Text));

if (String.IsNullOrEmpty(tbId.Text))
throw new ArgumentException("Identity cannot be blank");

if (tbPw.Text == null)
throw new ArgumentException("Password cannot be blank");

Cursor = Cursors.WaitCursor;
try
{
var krgen =
pgpGenerateKeyRingGenerator.generateKeyRingGenerator(tbId.Text, tbPw.Text);

// Generate public key ring, save to file.
var pkr = krgen.GeneratePublicKeyRing();
var pubout = new BufferedStream(new FileStream(
Path.Combine(tbDir.Text, tbFileName.Text + ".pkr"), FileMode.Create));
pkr.Encode(pubout);
pubout.Close();

// Generate secret key ring (private), save to file.
var skr = krgen.GenerateSecretKeyRing();
var secout = new BufferedStream(new FileStream(
Path.Combine(tbDir.Text, tbFileName.Text + ".skr"), FileMode.Create));
skr.Encode(secout);
secout.Close();
}
finally
{
Cursor = Cursors.Default;
}

MessageBox.Show("Key files created.");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}

private void btnCreateAsciiKeyFromRing_Click(object sender, EventArgs e)
{
// extract public key and save to armored file "asc"
using (Stream pubFis = File.OpenRead(Path.Combine(tbDir.Text, tbFileName.Text + ".pkr")))
{
var ring = new PgpPublicKeyRing(
PgpUtilities.GetDecoderStream(pubFis));

var pubMemStream = new MemoryStream();
var pubArmoredStream = new ArmoredOutputStream(pubMemStream);

ring.Encode(pubArmoredStream);

pubArmoredStream.Close();

var ascPgpPublicKey = Encoding.ASCII.GetString(pubMemStream.ToArray());

Console.WriteLine(ascPgpPublicKey);
tbResults.Text += ascPgpPublicKey;
var ascfile = Path.Combine(tbDir.Text, tbFileName.Text + ".asc");
using (var outfile = new StreamWriter(ascfile))
{
outfile.Write(ascPgpPublicKey);
}
MessageBox.Show(ascfile + " file created.");
}
}

private void btnAddAsciiKeyToRing_Click(object sender, EventArgs e)
{
var ascfilein = Path.Combine(tbDir.Text, tbFileName.Text + ".asc");

var pkr = asciiPublicKeyToRing(ascfilein);
if (pkr != null)
{
var pubKey = pkr.GetEncoded();
tbResults.Text = BitConverter.ToString(pubKey).Replace("-", " ");
}
else
{
tbResults.Text = ascfilein + " is not a public key.";
}
}

private PgpPublicKeyRing asciiPublicKeyToRing(string ascfilein)
{
using (Stream pubFis = File.OpenRead(ascfilein))
{
var pubArmoredStream = new ArmoredInputStream(pubFis);

var pgpFact = new PgpObjectFactory(pubArmoredStream);
Object opgp = pgpFact.NextPgpObject();
var pkr = opgp as PgpPublicKeyRing;
return pkr;
}
}

private PgpPublicKey getFirstPublicEncryptionKeyFromRing(PgpPublicKeyRing pkr)
{
foreach (PgpPublicKey k in pkr.GetPublicKeys())
{
if (k.IsEncryptionKey)
return k;
}
throw new ArgumentException("Can’t find encryption key in key ring.");
}

/// <summary>
/// Signing the public key from the party you are sending the encrypted
/// file to using your private key tells the receiver that the file
/// has been encrypted only by you since you left your private key
/// ‘mark’ on it that only you possess.
/// </summary>
private void btnSignFile_Click(object sender, EventArgs e)
{
try
{
if (!File.Exists(tbPublicKeyFile.Text))
throw new FileNotFoundException(String.Format("Public Key file [{0}] does not exist.",
tbPublicKeyFile.Text));

if (!File.Exists(tbPrivateKeyFile.Text))
throw new FileNotFoundException(String.Format("Private Key file [{0}] does not exist.",
tbPrivateKeyFile.Text));

if (String.IsNullOrEmpty(tbNotationName.Text))
throw new ArgumentException("Notation Name cannot be blank");

if (String.IsNullOrEmpty(tbNotationValue.Text))
throw new ArgumentException("Notation Value cannot be blank");

if (tbPwSign.Text == null)
throw new ArgumentException("Password cannot be blank");

pgpEncryptDecrypt.signPublicKey(
tbPrivateKeyFile.Text, tbPwSign.Text, tbPublicKeyFile.Text, tbNotationName.Text,
tbNotationValue.Text);

MessageBox.Show("File Signed.");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}

private void btnDecrypt_Click(object sender, EventArgs e)
{
try
{
if (!File.Exists(tbEncryptedFile.Text))
throw new FileNotFoundException(String.Format("Encrypted file [{0}] does not exist.",
tbEncryptedFile.Text));

if (!File.Exists(tbPrivateKeyFileED.Text))
throw new FileNotFoundException(String.Format("Private Key file [{0}] does not exist.",
tbPrivateKeyFileED.Text));

if (tbPwED.Text == null)
throw new ArgumentException("Password cannot be blank");

pgpEncryptDecrypt.Decrypt(tbEncryptedFile.Text, tbPrivateKeyFileED.Text, tbPwED.Text,
tbUnencryptedFile.Text);

MessageBox.Show("File Decrypted.");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}

/// <summary>
/// intended to use another parties public key to encrypt a file
/// it can then only be decrypted by them using their private key
/// </summary>
private void btnEncrypt_Click(object sender, EventArgs e)
{
try
{
if (!File.Exists(tbUnencryptedFile.Text))
throw new FileNotFoundException(String.Format("Unencrypted file [{0}] does not exist.",
tbUnencryptedFile.Text));

if (!File.Exists(tbPublicKeyFileED.Text))
throw new FileNotFoundException(String.Format("Public Key file [{0}] does not exist.",
tbPublicKeyFileED.Text));

pgpEncryptDecrypt.EncryptFile(
tbUnencryptedFile.Text, tbEncryptedFile.Text, tbPublicKeyFileED.Text, true, true);

MessageBox.Show("File Encrypted.");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}

private void btnEncryptFilewithASCIIKey_Click(object sender, EventArgs e)
{
if (!File.Exists(tbUnencryptedFile.Text))
throw new FileNotFoundException(String.Format("Unencrypted file [{0}] does not exist.",
tbUnencryptedFile.Text));

var ascfilein = Path.ChangeExtension(tbPublicKeyFileED.Text, "asc");

if (!File.Exists(ascfilein))
throw new FileNotFoundException(String.Format("Public Key file [{0}] does not exist.", ascfilein));

var pkr = asciiPublicKeyToRing(ascfilein);
if (pkr != null)
{
try
{
pgpEncryptDecrypt.EncryptFile(
tbUnencryptedFile.Text, tbEncryptedFile.Text, getFirstPublicEncryptionKeyFromRing(pkr), true,
true);

MessageBox.Show("File Encrypted.");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}
else
{
MessageBox.Show(ascfilein + " is not a public key.");
}
}

/// <summary>
/// Encrypt the file using the other parties public key and then signing the encrypted
/// file to using your private key tells the receiver that the file
/// has been encrypted only by you since you left your private key
/// ‘mark’ on it that only you possess.
/// </summary>
private void btnEncryptSign_Click(object sender, EventArgs e)
{
try
{
if (!File.Exists(tbUnencryptedFile.Text))
throw new FileNotFoundException(String.Format("Unencrypted file [{0}] does not exist.",
tbUnencryptedFile.Text));

if (!File.Exists(tbPublicKeyFileED.Text))
throw new FileNotFoundException(String.Format("Public Key file [{0}] does not exist.",
tbPublicKeyFileED.Text));

if (!File.Exists(tbPrivateKeyFileED.Text))
throw new FileNotFoundException(String.Format("Private Key file [{0}] does not exist.",
tbPrivateKeyFileED.Text));

if (tbPwED.Text == null)
throw new ArgumentException("Password cannot be blank");

pgpEncryptDecrypt.EncryptAndSign(
tbUnencryptedFile.Text, tbEncryptedFile.Text, tbPublicKeyFileED.Text, tbPrivateKeyFileED.Text,
tbPwED.Text, true);

MessageBox.Show("File Encrypted.");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}

private void tbKeyFile_DoubleClick(object sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null)
{
openFileDialog1.InitialDirectory = folderBrowserDialog1.SelectedPath;
openFileDialog1.FileName = textBox.Text;
openFileDialog1.CheckFileExists = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
textBox.Text = openFileDialog1.FileName;
folderBrowserDialog1.SelectedPath = Path.GetFullPath(openFileDialog1.FileName);
}
}
}

private void tbFile_DoubleClick(object sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null)
{
openFileDialog1.InitialDirectory = folderBrowserDialog1.SelectedPath;
openFileDialog1.FileName = textBox.Text;
openFileDialog1.CheckFileExists = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
textBox.Text = openFileDialog1.FileName;
folderBrowserDialog1.SelectedPath = Path.GetFullPath(openFileDialog1.FileName);
}
}
}

private void btnReadPublicKey_Click(object sender, EventArgs e)
{
using (Stream pubFis = File.OpenRead(tbPublicKeyFileED.Text))
{
var ring = new PgpPublicKeyRing(
PgpUtilities.GetDecoderStream(pubFis));

var pubMemStream = new MemoryStream();
var pubArmoredStream = new ArmoredOutputStream(pubMemStream);

ring.Encode(pubArmoredStream);

pubArmoredStream.Close();

var ascPgpPublicKey = Encoding.ASCII.GetString(pubMemStream.ToArray());

tbResults.Text = ascPgpPublicKey;
}
}

private void btnFillSampleData_Click(object sender, EventArgs e)
{
const string root = "keyname";
const string sname = "Joe Smith";
const string semail = "JSmith@keyname.com";
const string pw = "areallygoodpassword";
const string dir = @"C:\Temp";

tbId.Text = sname + " <" + semail + ">";
tbPw.Text = pw;
tbDir.Text = dir;
tbFileName.Text = root;

tbNotationName.Text = sname;
tbNotationValue.Text = semail;
tbPrivateKeyFile.Text = Path.Combine(dir, root + ".skr");
tbPwSign.Text = pw;
tbPublicKeyFile.Text = Path.Combine(dir, "OtherParty.asc");

tbPrivateKeyFileED.Text = Path.Combine(dir, root + ".skr");
tbPublicKeyFileED.Text = Path.Combine(dir, root + ".pkr");
tbPwED.Text = pw;
tbUnencryptedFile.Text = dir;
tbEncryptedFile.Text = dir;
}
}
}
[/csharp]

Leave a Reply

PGP Using Bouncy Castle Libraries