Jotting down stuffs
PGP Single Pass Sign and Encrypt .NET Stream
In a recent software development project, I was required to be able to read PGP encrypted content, do something, and write PGP encrypted result. The project allowed calling of PGP (or GPG) command line to do the encryption and decryption needed. However that did not feel right to me. Googled a bit and then I found the Bouncy Castle C# Crypto APIs, that among everything else, included support for PGP without having any external dependency.
The compiled assembly for the Bouncy Castle APIs can be downloaded from the website, however it was compiled for the .NET 1.1 runtime version. You can easily download the source code instead and compile it for the other .NET versions if you want to. I managed to compile the source code for the .NET 2.0 runtime version without any changes to the original source code.
The bigger issue at using the Bouncy Castle APIs is that it does not seem to have any proper documentation for it. There are examples provided in the downloadable source code which you can read through to figure out how to do things. I was lazy at that time, and managed to find this blog entry: PGP Single Pass Sign and Encrypt with Bouncy Castle that provided me with the code to encrypt file contents using PGP.
From that code, I refactored it bit by bit:
- Factored out the actualFileName (string) parameter so that the actual input content can be from any source, not just physical files.
- Factored out the keyIn (Stream) and keyId (long) parameters so that the handling of the secret key that will be used to sign the input content can be externalized from the sign and encrypted method itself.
- Removed the armor (bool) and withIntegrityCheck (bool) parameters and made both of it as constants of true instead so that the generated output will always be armored and integrity-checked. It can easily be made into parameters back again if need to.
- Removed the embeddedFileName (string) parameter and made it as a constant of empty string since the file name value is not important to generate proper signed-and-encrypted output.
- Made it into a .NET Stream so that allow it to be used as any other .NET Streams and the output can be chained to any imaginable .NET Streams such as MemoryStream, HttpResponse’s OutputStream, etc. Usage example will be provided at the end of this blog post.
The Code for the .NET Stream
using System;
using System.IO;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Security;
class PgpSignAndEncryptStream : Stream
{
readonly Stream outputStream;
readonly Stream armoredOutputStream;
readonly PgpEncryptedDataGenerator encryptedDataGenerator;
readonly Stream encryptedOutputStream;
readonly PgpCompressedDataGenerator compressedDataGenerator;
readonly Stream compressedOutputStream;
readonly PgpSignatureGenerator signatureGenerator;
readonly PgpLiteralDataGenerator literalDataGenerator;
readonly Stream literalOutputStream;
bool closed = false;
public PgpSignAndEncryptStream(Stream outputStream, PgpPublicKey encryptionPublicKey, PgpSecretKey signatureSecretKey, string signatureSecretPassword)
{
//
// literal + signature -> compress -> encrypt -> armored -> output
//
const int BUFFER_SIZE = 1 << 16; // should always be power of 2
const bool armor = true;
const bool withIntegrityCheck = true;
const string fileName = "";
DateTime modificationTime = DateTime.UtcNow;
this.outputStream = outputStream;
//
// Always create armored output
//
if (armor) {
outputStream = new ArmoredOutputStream(outputStream);
armoredOutputStream = outputStream;
}
//
// Init encrypted data generator
//
encryptedDataGenerator =
new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
encryptedDataGenerator.AddMethod(encryptionPublicKey);
encryptedOutputStream = encryptedDataGenerator.Open(outputStream, new byte[BUFFER_SIZE]);
//
// Init compression
//
compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
compressedOutputStream = compressedDataGenerator.Open(encryptedOutputStream);
//
// Init signature
//
signatureGenerator = new PgpSignatureGenerator(signatureSecretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
PgpPrivateKey signaturePrivateKey = signatureSecretKey.ExtractPrivateKey(signatureSecretPassword.ToCharArray());
signatureGenerator.InitSign(PgpSignature.BinaryDocument, signaturePrivateKey);
foreach (string userId in signatureSecretKey.PublicKey.GetUserIds()) {
PgpSignatureSubpacketGenerator signatureSubpacketGenerator = new PgpSignatureSubpacketGenerator();
signatureSubpacketGenerator.SetSignerUserId(false, userId);
signatureGenerator.SetHashedSubpackets(signatureSubpacketGenerator.Generate());
// Just the first one!
break;
}
signatureGenerator.GenerateOnePassVersion(false).Encode(compressedOutputStream);
//
// Create the literal data generator output stream
//
literalDataGenerator = new PgpLiteralDataGenerator();
literalOutputStream = literalDataGenerator.Open(compressedOutputStream, PgpLiteralData.Binary,
fileName, modificationTime, new byte[BUFFER_SIZE]);
}
public override void Flush()
{
literalOutputStream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
literalOutputStream.Write(buffer, offset, count);
signatureGenerator.Update(buffer, offset, count);
}
public override void Close()
{
if (closed) {
return;
}
closed = true;
literalOutputStream.Close();
literalDataGenerator.Close();
signatureGenerator.Generate().Encode(compressedOutputStream);
compressedOutputStream.Close();
compressedDataGenerator.Close();
encryptedOutputStream.Close();
encryptedDataGenerator.Close();
if (armoredOutputStream != null) {
armoredOutputStream.Close();
}
outputStream.Close();
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
}
Usage Example of the .NET Stream
Assumption made for now: you know how to actually get the public key and the secret key out of the keyring files. Later I will write another post on how to get the keys.
Using StreamWriter to write encrypted string to a file
using System.IO;
using Org.BouncyCastle.Bcpg.OpenPgp;
...
PgpPublicKey publicKey = null; // get this value from somewhere
PgpSecretKey secretKey = null; // get this value from somewhere
string password = null; // get this value from somewhere
Stream outputStream = File.OpenWrite("output.txt"); // this can be replaced with any other output stream e.g. MemoryStream
PgpSignAndEncryptStream pgpOutStream = new PgpSignAndEncryptStream(outputStream, publicKey, secretKey, password);
StreamWriter writer = new StreamWriter(pgpOutStream);
writer.Write("This text will be signed and encrypted into output.txt");
writer.Close();
pgpOutStream.Close();
outputStream.Close();
Sign and encrypt one whole input file
using System.IO;
using Org.BouncyCastle.Bcpg.OpenPgp;
...
PgpPublicKey publicKey = null; // get this value from somewhere
PgpSecretKey secretKey = null; // get this value from somewhere
string password = null; // get this value from somewhere
Stream inputStream = File.OpenRead("input.txt");
Stream outputStream = File.OpenWrite("output.txt");
PgpSignAndEncryptStream pgpOutStream = new PgpSignAndEncryptStream(outputStream, publicKey, secretKey, password);
byte[] buffer = new byte[4096];
int readCount;
while ((readCount = inputStream.Read(buffer, 0, buffer.Length)) > 0) {
outputStream.Write(buffer, 0, readCount);
}
outputStream.Close();
inputStream.Close();
Pending Related Posts
- As mentioned earlier, a post on how to get the public keys and secret keys out of the keyring files. And probably on how to use GPG to import the keys into the keyring files as I can’t seem to find out how to programmatically import a public key block into a public key ring file.
- PGP Decrypt and Single Pass Signature Verify .NET Stream. The working code is already in my hard disk, will share it later.
| Print article | This entry was posted by Amry on March 8, 2010 at 5:24 pm, and is filed under Coding. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 4 months ago
I had to modify the Close() override method to keep my memorystream’s open…
public override void Close()
{
if (closed)
{
return;
}
closed = true;
literalOutputStream.Close();
literalDataGenerator.Close();
signatureGenerator.Generate().Encode(compressedOutputStream);
compressedOutputStream.Close();
compressedDataGenerator.Close();
encryptedOutputStream.Close();
encryptedDataGenerator.Close();
if (armoredOutputStream != null)
{
armoredOutputStream.Close();
}
//outputStream.Close();
}
about 4 months ago
Hi Jason,
Yes, you would need to comment that out if you will still be doing something with the stream. For my case, I didn’t have any use to leave it open, that’s why I coded it to close the stream.
Anyhow, I hope the code I shared is useful to you (although I still haven’t blogged about how to retrieve the keys, really am busy with work).
about 3 months ago
Part of my project uses this class.. I’m signing & encrypting with the same private and public keys.. it would be interesting to see how to store my keys in memory or something to keep from having to reload from the pgp database.
I did make another change. When looping through the uesrs in the secrey-key collection – I am passing in a UserId so should I ever work with a large pgp database – i wouldn’t be signing with the wrong private key.
about 2 months ago
Here is my complete class change:
using System;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Bcpg.OpenPgp;
using System.Text;
using System.IO;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Security;
namespace OpenPGP
{
public class GPG
{
private static readonly byte[] myPasword = Base64.Decode(“”); //password for your secret key in encoded in base64
private static readonly byte[] pkr = Convert.FromBase64String(@”"); //base64 encoded public-key/ring stuff goes here
private static readonly byte[] skr = Convert.FromBase64String(@”"); //base64 encoded sec-key/ring stuff goes here.. note: you can rest asured that your public key is avail in the secret keyring data
public MemoryStream OutputStream { get; private set; }
public GPG()
{
}
public bool SignAndEncrypt(MemoryStream dataStream)
{
try
{
PgpPublicKeyRingBundle pkrb = new PgpPublicKeyRingBundle(pkr);
PgpSecretKeyRingBundle skrb = new PgpSecretKeyRingBundle(skr);
PgpPublicKey pgpPubKey = pkrb.GetPublicKey(unchecked((long) 0×00 /* their public key id */));
PgpPublicKey pgpMyPubKey = pkrb.GetPublicKey(unchecked((long) 0×00 /* your sec key id */)); // by passing in your secret-key id, your public key will be avail.. in other words, this works fine.
PgpSecretKey pgpMySecretKey = skrb.GetSecretKey(unchecked((long) 0×00 /* your sec key id */));
char[] privatePwd = Encoding.ASCII.GetChars(myPasword);
// memory object
MemoryStream outputStream = new MemoryStream();
// pgp sign and encrypt class
GPGSignAndEncryptStream pgpOutStream = new GPGSignAndEncryptStream(outputStream, pgpPubKey, pgpMySecretKey, pgpMyPubKey, privatePwd);
// grab the unecrypted data (dataStream) and convert to string
byte[] buf = new byte[dataStream.Length];
dataStream.Position = 0;
int count = dataStream.Read(buf, 0, buf.Length);
string temp = Encoding.ASCII.GetString(buf);
// write unencrypted data to encrypted stream (writer->pgpOutStream)..
StreamWriter writer = new StreamWriter(pgpOutStream);
writer.Write(temp);
writer.Close();
pgpOutStream.Close();
OutputStream = outputStream;
return true;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
}
class GPGSignAndEncryptStream : Stream
{
readonly Stream outputStream;
readonly Stream armoredOutputStream;
readonly PgpEncryptedDataGenerator encryptedDataGenerator;
readonly Stream encryptedOutputStream;
readonly PgpCompressedDataGenerator compressedDataGenerator;
readonly Stream compressedOutputStream;
readonly PgpSignatureGenerator signatureGenerator;
readonly PgpLiteralDataGenerator literalDataGenerator;
readonly Stream literalOutputStream;
bool closed = false;
public GPGSignAndEncryptStream(Stream outputStream, PgpPublicKey encryptionPublicKey, PgpSecretKey signatureSecretKey, PgpPublicKey jason, char[] signatureSecretPassword)
{
try
{
//
// literal + signature -> compress -> encrypt -> armored -> output
//
const int BUFFER_SIZE = 1 << 16; // should always be power of 2
const bool armor = true;
const bool withIntegrityCheck = true;
const string fileName = "";
DateTime modificationTime = DateTime.UtcNow;
this.outputStream = outputStream;
//
// Always create armored output
//
if (armor)
{
outputStream = new ArmoredOutputStream(outputStream);
armoredOutputStream = outputStream;
}
//
// Init encrypted data generator
//
encryptedDataGenerator = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
encryptedDataGenerator.AddMethod(encryptionPublicKey);
encryptedDataGenerator.AddMethod(jason); //added so we can decrypt our own messages
encryptedOutputStream = encryptedDataGenerator.Open(outputStream, new byte[BUFFER_SIZE]);
//
// Init compression
//
compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
compressedOutputStream = compressedDataGenerator.Open(encryptedOutputStream);
//
// Init signature
//
signatureGenerator = new PgpSignatureGenerator(signatureSecretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
PgpPrivateKey signaturePrivateKey = signatureSecretKey.ExtractPrivateKey(signatureSecretPassword);
signatureGenerator.InitSign(PgpSignature.BinaryDocument, signaturePrivateKey);
foreach (string userId in signatureSecretKey.PublicKey.GetUserIds())
{
PgpSignatureSubpacketGenerator signatureSubpacketGenerator = new PgpSignatureSubpacketGenerator();
signatureSubpacketGenerator.SetSignerUserId(false, userId);
signatureGenerator.SetHashedSubpackets(signatureSubpacketGenerator.Generate());
// Just the first one!
break;
}
signatureGenerator.GenerateOnePassVersion(false).Encode(compressedOutputStream);
//
// Create the literal data generator output stream
//
literalDataGenerator = new PgpLiteralDataGenerator();
literalOutputStream = literalDataGenerator.Open(compressedOutputStream, PgpLiteralData.Binary, fileName, modificationTime, new byte[BUFFER_SIZE]);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public override void Flush()
{
literalOutputStream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
literalOutputStream.Write(buffer, offset, count);
signatureGenerator.Update(buffer, offset, count);
}
public override void Close()
{
if (closed)
{
return;
}
closed = true;
literalOutputStream.Close();
literalDataGenerator.Close();
signatureGenerator.Generate().Encode(compressedOutputStream);
compressedOutputStream.Close();
compressedDataGenerator.Close();
encryptedOutputStream.Close();
encryptedDataGenerator.Close();
if (armoredOutputStream != null)
{
armoredOutputStream.Close();
}
//outputStream.Close();
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
}
}
about 2 months ago
Here is a quick / nasty sample showing how you might use it w/ a memorystream:
OpenPGP.GPG gpg = new OpenPGP.GPG();
MemoryStream strm = new MemoryStream();
byte[] buffer = Encoding.ASCII.GetBytes(“something to write into the memory stream”);
strm.Write(buffer, 0, buffer.Length);
if (gpg.SignAndEncrypt(strm) == true)
{
byte[] streamBuffer = new byte[gpg.OutputStream.Length];
gpg.OutputStream.Position = 0;
gpg.OutputStream.Read(streamBuffer, 0, streamBuffer.Length);
gpg.OutputStream.Close();
}
about 2 months ago
So, to explain why I decided to hardcode my PGP data/info into the project(s)..
I found that I didn’t want to open up the GPG local database (noted in the example to come). From a webservice view, I didn’t want to constantly open the pgp database for each time i needed some encryption. I surely didn’t want to cache the data in memory on app startup. I basically wanted zero disc access due to pgp-encryption.
So I thought the best solution would be to hardcode the pgp database in my app. I didn’t take the whole DB. Just the specific keys (or keyrings) that pertained to me. Since each key (both public and secret) have unique keyId’s, I decided to rewrite the class a little to load the public key and secret key by keyId’s.
so, basically, i ended up with a solution that held only my secret+pub key along with the 3rd party’s public key(for encryption). This was perfect! no attached databases, etc.
I also noted that the signing portion didn’t work for me because I wasn’t adding my key to the encryption buffer.. this is noted in the code/class I posted above..
Here is an example showing how to enumerate through the key rings then keys from each database, it very well may not work for you but it works flawlessly with the current GNU-pgp implementation.
Stream fileDb = File.OpenRead(@”pubring.gpg”);
Stream inputStream = PgpUtilities.GetDecoderStream(fileDb);
PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(inputStream);
// this converts the entire public key ring bundle to string
string pubBuffer = Encoding.ASCII.GetString(Base64.Encode(pgpPub.GetEncoded()));
// loop through each Public Key Ring
foreach (PgpPublicKeyRingBundle kRing in pgpPub.GetKeyRings())
{
// loop through each public key
foreach (PgpPublicKey k in kRing.GetKeyRings())
{
// –> k.KeyId returns …the key id
// convert key to base64 encoded bytes
byte[] pubKey1 = k.GetEncoded();
// convert encoded key to string
string pubKey2 = Encoding.ASCII.GetString(pubKey1);
// single line to string
string pubKey3 = Encoding.ASCII.GetString(k.GetEncoded());
}
}
fileDb = File.OpenRead(@”secring.gpg”);
inputStream = PgpUtilities.GetDecoderStream(fileDb);
PgpSecretKeyRingBundle pgpSec = new PgpSecretKeyRingBundle(inputStream);
// this converts the entire secret key ring bundle to string
string tmp = Encoding.ASCII.GetString(Base64.Encode(pgpSec.GetEncoded()));
// loop through each Secret Key Ring
foreach (PgpSecretKeyRingBundle kRing in pgpPub.GetKeyRings())
{
// loop through each Secret key
foreach (PgpSecretKey k in kRing.GetKeyRings())
{
// –> k.KeyId returns …the key id
// convert key to base64 encoded bytes
byte[] secKey1 = k.GetEncoded();
// convert encoded key to string
string secKey2 = Encoding.ASCII.GetString(secKey1);
// single line to string
string secKey3 = Encoding.ASCII.GetString(k.GetEncoded());
}
}