summary refs log tree commit diff
path: root/crypto/src/openpgp
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/src/openpgp')
-rw-r--r--crypto/src/openpgp/IStreamGenerator.cs7
-rw-r--r--crypto/src/openpgp/PGPKeyRing.cs79
-rw-r--r--crypto/src/openpgp/PGPObject.cs9
-rw-r--r--crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs33
-rw-r--r--crypto/src/openpgp/PgpCompressedData.cs50
-rw-r--r--crypto/src/openpgp/PgpCompressedDataGenerator.cs203
-rw-r--r--crypto/src/openpgp/PgpDataValidationException.cs15
-rw-r--r--crypto/src/openpgp/PgpEncryptedData.cs151
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataGenerator.cs506
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataList.cs72
-rw-r--r--crypto/src/openpgp/PgpException.cs19
-rw-r--r--crypto/src/openpgp/PgpExperimental.cs16
-rw-r--r--crypto/src/openpgp/PgpKeyFlags.cs13
-rw-r--r--crypto/src/openpgp/PgpKeyPair.cs67
-rw-r--r--crypto/src/openpgp/PgpKeyRingGenerator.cs167
-rw-r--r--crypto/src/openpgp/PgpKeyValidationException.cs15
-rw-r--r--crypto/src/openpgp/PgpLiteralData.cs63
-rw-r--r--crypto/src/openpgp/PgpLiteralDataGenerator.cs180
-rw-r--r--crypto/src/openpgp/PgpMarker.cs18
-rw-r--r--crypto/src/openpgp/PgpObjectFactory.cs143
-rw-r--r--crypto/src/openpgp/PgpOnePassSignature.cs179
-rw-r--r--crypto/src/openpgp/PgpOnePassSignatureList.cs51
-rw-r--r--crypto/src/openpgp/PgpPbeEncryptedData.cs135
-rw-r--r--crypto/src/openpgp/PgpPrivateKey.cs42
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs890
-rw-r--r--crypto/src/openpgp/PgpPublicKeyEncryptedData.cs252
-rw-r--r--crypto/src/openpgp/PgpPublicKeyRing.cs200
-rw-r--r--crypto/src/openpgp/PgpPublicKeyRingBundle.cs280
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs666
-rw-r--r--crypto/src/openpgp/PgpSecretKeyRing.cs301
-rw-r--r--crypto/src/openpgp/PgpSecretKeyRingBundle.cs281
-rw-r--r--crypto/src/openpgp/PgpSignature.cs422
-rw-r--r--crypto/src/openpgp/PgpSignatureGenerator.cs393
-rw-r--r--crypto/src/openpgp/PgpSignatureList.cs51
-rw-r--r--crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs193
-rw-r--r--crypto/src/openpgp/PgpSignatureSubpacketVector.cs229
-rw-r--r--crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs81
-rw-r--r--crypto/src/openpgp/PgpUtilities.cs428
-rw-r--r--crypto/src/openpgp/PgpV3SignatureGenerator.cs199
-rw-r--r--crypto/src/openpgp/WrappedGeneratorStream.cs28
40 files changed, 7127 insertions, 0 deletions
diff --git a/crypto/src/openpgp/IStreamGenerator.cs b/crypto/src/openpgp/IStreamGenerator.cs
new file mode 100644
index 000000000..379213a66
--- /dev/null
+++ b/crypto/src/openpgp/IStreamGenerator.cs
@@ -0,0 +1,7 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	public interface IStreamGenerator
+	{
+		void Close();
+	}
+}
diff --git a/crypto/src/openpgp/PGPKeyRing.cs b/crypto/src/openpgp/PGPKeyRing.cs
new file mode 100644
index 000000000..6426f3f25
--- /dev/null
+++ b/crypto/src/openpgp/PGPKeyRing.cs
@@ -0,0 +1,79 @@
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	public abstract class PgpKeyRing
+		: PgpObject
+	{
+		internal PgpKeyRing()
+		{
+		}
+
+		internal static TrustPacket ReadOptionalTrustPacket(
+			BcpgInputStream bcpgInput)
+		{
+			return (bcpgInput.NextPacketTag() == PacketTag.Trust)
+				?	(TrustPacket) bcpgInput.ReadPacket()
+				:	null;
+		}
+
+		internal static IList ReadSignaturesAndTrust(
+			BcpgInputStream	bcpgInput)
+		{
+			try
+			{
+				IList sigList = Platform.CreateArrayList();
+
+				while (bcpgInput.NextPacketTag() == PacketTag.Signature)
+				{
+					SignaturePacket signaturePacket = (SignaturePacket) bcpgInput.ReadPacket();
+					TrustPacket trustPacket = ReadOptionalTrustPacket(bcpgInput);
+
+					sigList.Add(new PgpSignature(signaturePacket, trustPacket));
+				}
+
+				return sigList;
+			}
+			catch (PgpException e)
+			{
+				throw new IOException("can't create signature object: " + e.Message, e);
+			}
+		}
+
+		internal static void ReadUserIDs(
+			BcpgInputStream	bcpgInput,
+			out IList       ids,
+			out IList       idTrusts,
+			out IList       idSigs)
+		{
+			ids = Platform.CreateArrayList();
+            idTrusts = Platform.CreateArrayList();
+            idSigs = Platform.CreateArrayList();
+
+			while (bcpgInput.NextPacketTag() == PacketTag.UserId
+				|| bcpgInput.NextPacketTag() == PacketTag.UserAttribute)
+			{
+				Packet obj = bcpgInput.ReadPacket();
+				if (obj is UserIdPacket)
+				{
+					UserIdPacket id = (UserIdPacket)obj;
+					ids.Add(id.GetId());
+				}
+				else
+				{
+					UserAttributePacket user = (UserAttributePacket) obj;
+					ids.Add(new PgpUserAttributeSubpacketVector(user.GetSubpackets()));
+				}
+
+				idTrusts.Add(
+					ReadOptionalTrustPacket(bcpgInput));
+
+				idSigs.Add(
+					ReadSignaturesAndTrust(bcpgInput));
+			}
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PGPObject.cs b/crypto/src/openpgp/PGPObject.cs
new file mode 100644
index 000000000..d38276cb6
--- /dev/null
+++ b/crypto/src/openpgp/PGPObject.cs
@@ -0,0 +1,9 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	public abstract class PgpObject
+	{
+		internal PgpObject()
+		{
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs b/crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs
new file mode 100644
index 000000000..9d56c8bc3
--- /dev/null
+++ b/crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Bcpg.Attr;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	public class PgpUserAttributeSubpacketVectorGenerator
+	{
+		private IList list = Platform.CreateArrayList();
+
+		public virtual void SetImageAttribute(
+			ImageAttrib.Format	imageType,
+			byte[]				imageData)
+		{
+			if (imageData == null)
+				throw new ArgumentException("attempt to set null image", "imageData");
+
+			list.Add(new ImageAttrib(imageType, imageData));
+		}
+
+        public virtual PgpUserAttributeSubpacketVector Generate()
+		{
+            UserAttributeSubpacket[] a = new UserAttributeSubpacket[list.Count];
+            for (int i = 0; i < list.Count; ++i)
+            {
+                a[i] = (UserAttributeSubpacket)list[i];
+            }
+            return new PgpUserAttributeSubpacketVector(a);
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpCompressedData.cs b/crypto/src/openpgp/PgpCompressedData.cs
new file mode 100644
index 000000000..e64a17c9c
--- /dev/null
+++ b/crypto/src/openpgp/PgpCompressedData.cs
@@ -0,0 +1,50 @@
+using System.IO;
+
+using Org.BouncyCastle.Apache.Bzip2;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Compressed data objects</remarks>
+    public class PgpCompressedData
+		: PgpObject
+    {
+        private readonly CompressedDataPacket data;
+
+		public PgpCompressedData(
+            BcpgInputStream bcpgInput)
+        {
+            data = (CompressedDataPacket) bcpgInput.ReadPacket();
+        }
+
+		/// <summary>The algorithm used for compression</summary>
+        public CompressionAlgorithmTag Algorithm
+        {
+			get { return data.Algorithm; }
+        }
+
+		/// <summary>Get the raw input stream contained in the object.</summary>
+        public Stream GetInputStream()
+        {
+            return data.GetInputStream();
+        }
+
+		/// <summary>Return an uncompressed input stream which allows reading of the compressed data.</summary>
+        public Stream GetDataStream()
+        {
+            switch (Algorithm)
+            {
+				case CompressionAlgorithmTag.Uncompressed:
+					return GetInputStream();
+				case CompressionAlgorithmTag.Zip:
+					return new ZInputStream(GetInputStream(), true);
+                case CompressionAlgorithmTag.ZLib:
+					return new ZInputStream(GetInputStream());
+				case CompressionAlgorithmTag.BZip2:
+					return new CBZip2InputStream(GetInputStream());
+                default:
+                    throw new PgpException("can't recognise compression algorithm: " + Algorithm);
+            }
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpCompressedDataGenerator.cs b/crypto/src/openpgp/PgpCompressedDataGenerator.cs
new file mode 100644
index 000000000..c758ecb05
--- /dev/null
+++ b/crypto/src/openpgp/PgpCompressedDataGenerator.cs
@@ -0,0 +1,203 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Apache.Bzip2;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Class for producing compressed data packets.</remarks>
+	public class PgpCompressedDataGenerator
+		: IStreamGenerator
+	{
+		private readonly CompressionAlgorithmTag algorithm;
+		private readonly int compression;
+
+		private Stream dOut;
+		private BcpgOutputStream pkOut;
+
+		public PgpCompressedDataGenerator(
+			CompressionAlgorithmTag algorithm)
+			: this(algorithm, JZlib.Z_DEFAULT_COMPRESSION)
+		{
+		}
+
+		public PgpCompressedDataGenerator(
+			CompressionAlgorithmTag	algorithm,
+			int						compression)
+		{
+			switch (algorithm)
+			{
+				case CompressionAlgorithmTag.Uncompressed:
+				case CompressionAlgorithmTag.Zip:
+				case CompressionAlgorithmTag.ZLib:
+				case CompressionAlgorithmTag.BZip2:
+					break;
+				default:
+					throw new ArgumentException("unknown compression algorithm", "algorithm");
+			}
+
+			if (compression != JZlib.Z_DEFAULT_COMPRESSION)
+			{
+				if ((compression < JZlib.Z_NO_COMPRESSION) || (compression > JZlib.Z_BEST_COMPRESSION))
+				{
+					throw new ArgumentException("unknown compression level: " + compression);
+				}
+			}
+
+			this.algorithm = algorithm;
+			this.compression = compression;
+		}
+
+		/// <summary>
+		/// <p>
+		/// Return an output stream which will save the data being written to
+		/// the compressed object.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// </summary>
+		/// <param name="outStr">Stream to be used for output.</param>
+		/// <returns>A Stream for output of the compressed data.</returns>
+		/// <exception cref="ArgumentNullException"></exception>
+		/// <exception cref="InvalidOperationException"></exception>
+		/// <exception cref="IOException"></exception>
+		public Stream Open(
+			Stream outStr)
+		{
+			if (dOut != null)
+				throw new InvalidOperationException("generator already in open state");
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+
+			this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData);
+
+			doOpen();
+
+			return new WrappedGeneratorStream(this, dOut);
+		}
+
+		/// <summary>
+		/// <p>
+		/// Return an output stream which will compress the data as it is written to it.
+		/// The stream will be written out in chunks according to the size of the passed in buffer.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// <p>
+		/// <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
+		/// bytes worth of the buffer will be used.
+		/// </p>
+		/// <p>
+		/// <b>Note</b>: using this may break compatibility with RFC 1991 compliant tools.
+		/// Only recent OpenPGP implementations are capable of accepting these streams.
+		/// </p>
+		/// </summary>
+		/// <param name="outStr">Stream to be used for output.</param>
+		/// <param name="buffer">The buffer to use.</param>
+		/// <returns>A Stream for output of the compressed data.</returns>
+		/// <exception cref="ArgumentNullException"></exception>
+		/// <exception cref="InvalidOperationException"></exception>
+		/// <exception cref="IOException"></exception>
+		/// <exception cref="PgpException"></exception>
+		public Stream Open(
+			Stream	outStr,
+			byte[]	buffer)
+		{
+			if (dOut != null)
+				throw new InvalidOperationException("generator already in open state");
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+			if (buffer == null)
+				throw new ArgumentNullException("buffer");
+
+			this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData, buffer);
+
+			doOpen();
+
+			return new WrappedGeneratorStream(this, dOut);
+		}
+
+		private void doOpen()
+		{
+			pkOut.WriteByte((byte) algorithm);
+
+			switch (algorithm)
+			{
+				case CompressionAlgorithmTag.Uncompressed:
+					dOut = pkOut;
+					break;
+				case CompressionAlgorithmTag.Zip:
+					dOut = new SafeZOutputStream(pkOut, compression, true);
+					break;
+				case CompressionAlgorithmTag.ZLib:
+					dOut = new SafeZOutputStream(pkOut, compression, false);
+					break;
+				case CompressionAlgorithmTag.BZip2:
+					dOut = new SafeCBZip2OutputStream(pkOut);
+					break;
+				default:
+					// Constructor should guard against this possibility
+					throw new InvalidOperationException();
+			}
+		}
+
+		/// <summary>Close the compressed object.</summary>summary>
+		public void Close()
+		{
+			if (dOut != null)
+			{
+				if (dOut != pkOut)
+				{
+                    dOut.Dispose();
+					dOut.Flush();
+				}
+
+				dOut = null;
+
+				pkOut.Finish();
+				pkOut.Flush();
+				pkOut = null;
+			}
+		}
+
+		private class SafeCBZip2OutputStream : CBZip2OutputStream
+		{
+			public SafeCBZip2OutputStream(Stream output)
+				: base(output)
+			{
+			}
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    Finish();
+                }
+            }
+		}
+
+		private class SafeZOutputStream : ZOutputStream
+		{
+			public SafeZOutputStream(Stream output, int level, bool nowrap)
+				: base(output, level, nowrap)
+			{
+			}
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    Finish();
+                    End();
+                }
+            }
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpDataValidationException.cs b/crypto/src/openpgp/PgpDataValidationException.cs
new file mode 100644
index 000000000..74674da59
--- /dev/null
+++ b/crypto/src/openpgp/PgpDataValidationException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Thrown if the IV at the start of a data stream indicates the wrong key is being used.
+	/// </remarks>
+    public class PgpDataValidationException
+        : PgpException
+	{
+		public PgpDataValidationException() : base() {}
+		public PgpDataValidationException(string message) : base(message) {}
+		public PgpDataValidationException(string message, Exception exception) : base(message, exception) {}
+	}
+}
diff --git a/crypto/src/openpgp/PgpEncryptedData.cs b/crypto/src/openpgp/PgpEncryptedData.cs
new file mode 100644
index 000000000..0d237b56c
--- /dev/null
+++ b/crypto/src/openpgp/PgpEncryptedData.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    public abstract class PgpEncryptedData
+    {
+		internal class TruncatedStream
+			: BaseInputStream
+		{
+			private const int LookAheadSize = 22;
+			private const int LookAheadBufSize = 512;
+			private const int LookAheadBufLimit = LookAheadBufSize - LookAheadSize;
+
+			private readonly Stream inStr;
+			private readonly byte[] lookAhead = new byte[LookAheadBufSize];
+			private int bufStart, bufEnd;
+
+			internal TruncatedStream(
+				Stream inStr)
+			{
+				int numRead = Streams.ReadFully(inStr, lookAhead, 0, lookAhead.Length);
+
+				if (numRead < LookAheadSize)
+					throw new EndOfStreamException();
+
+				this.inStr = inStr;
+				this.bufStart = 0;
+				this.bufEnd = numRead - LookAheadSize;
+			}
+
+			private int FillBuffer()
+			{
+				if (bufEnd < LookAheadBufLimit)
+					return 0;
+
+				Debug.Assert(bufStart == LookAheadBufLimit);
+				Debug.Assert(bufEnd == LookAheadBufLimit);
+
+				Array.Copy(lookAhead, LookAheadBufLimit, lookAhead, 0, LookAheadSize);
+				bufEnd = Streams.ReadFully(inStr, lookAhead, LookAheadSize, LookAheadBufLimit);
+				bufStart = 0;
+				return bufEnd;
+			}
+
+			public override int ReadByte()
+			{
+				if (bufStart < bufEnd)
+					return lookAhead[bufStart++];
+
+				if (FillBuffer() < 1)
+					return -1;
+
+				return lookAhead[bufStart++];
+			}
+
+			public override int Read(byte[] buf, int off, int len)
+			{
+				int avail = bufEnd - bufStart;
+
+				int pos = off;
+				while (len > avail)
+				{
+					Array.Copy(lookAhead, bufStart, buf, pos, avail);
+
+					bufStart += avail;
+					pos += avail;
+					len -= avail;
+
+					if ((avail = FillBuffer()) < 1)
+						return pos - off;
+				}
+
+				Array.Copy(lookAhead, bufStart, buf, pos, len);
+				bufStart += len;
+
+				return pos + len - off;;
+			}
+
+			internal byte[] GetLookAhead()
+			{
+				byte[] temp = new byte[LookAheadSize];
+				Array.Copy(lookAhead, bufStart, temp, 0, LookAheadSize);
+				return temp;
+			}
+		}
+
+		internal InputStreamPacket	encData;
+        internal Stream				encStream;
+        internal TruncatedStream	truncStream;
+
+		internal PgpEncryptedData(
+            InputStreamPacket encData)
+        {
+            this.encData = encData;
+        }
+
+		/// <summary>Return the raw input stream for the data stream.</summary>
+        public virtual Stream GetInputStream()
+        {
+            return encData.GetInputStream();
+        }
+
+		/// <summary>Return true if the message is integrity protected.</summary>
+		/// <returns>True, if there is a modification detection code namespace associated
+		/// with this stream.</returns>
+        public bool IsIntegrityProtected()
+        {
+			return encData is SymmetricEncIntegrityPacket;
+        }
+
+		/// <summary>Note: This can only be called after the message has been read.</summary>
+		/// <returns>True, if the message verifies, false otherwise</returns>
+        public bool Verify()
+        {
+            if (!IsIntegrityProtected())
+                throw new PgpException("data not integrity protected.");
+
+			DigestStream dIn = (DigestStream) encStream;
+
+			//
+            // make sure we are at the end.
+            //
+            while (encStream.ReadByte() >= 0)
+            {
+				// do nothing
+            }
+
+			//
+            // process the MDC packet
+            //
+			byte[] lookAhead = truncStream.GetLookAhead();
+
+			IDigest hash = dIn.ReadDigest();
+			hash.BlockUpdate(lookAhead, 0, 2);
+			byte[] digest = DigestUtilities.DoFinal(hash);
+
+			byte[] streamDigest = new byte[digest.Length];
+			Array.Copy(lookAhead, 2, streamDigest, 0, streamDigest.Length);
+
+			return Arrays.ConstantTimeAreEqual(digest, streamDigest);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
new file mode 100644
index 000000000..f46f99d37
--- /dev/null
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -0,0 +1,506 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Generator for encrypted objects.</remarks>
+    public class PgpEncryptedDataGenerator
+		: IStreamGenerator
+    {
+		private BcpgOutputStream	pOut;
+        private CipherStream		cOut;
+        private IBufferedCipher		c;
+        private bool				withIntegrityPacket;
+        private bool				oldFormat;
+        private DigestStream		digestOut;
+
+		private abstract class EncMethod
+            : ContainedPacket
+        {
+            protected byte[]                    sessionInfo;
+            protected SymmetricKeyAlgorithmTag  encAlgorithm;
+            protected KeyParameter              key;
+
+			public abstract void AddSessionInfo(byte[] si, SecureRandom random);
+        }
+
+        private class PbeMethod
+            : EncMethod
+        {
+            private S2k s2k;
+
+            internal PbeMethod(
+                SymmetricKeyAlgorithmTag  encAlgorithm,
+                S2k                       s2k,
+                KeyParameter              key)
+            {
+                this.encAlgorithm = encAlgorithm;
+                this.s2k = s2k;
+                this.key = key;
+            }
+
+            public KeyParameter GetKey()
+            {
+                return key;
+            }
+
+			public override void AddSessionInfo(
+                byte[]			si,
+				SecureRandom	random)
+            {
+                string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+                IBufferedCipher c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+
+				byte[] iv = new byte[c.GetBlockSize()];
+                c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random));
+
+				this.sessionInfo = c.DoFinal(si, 0, si.Length - 2);
+			}
+
+			public override void Encode(BcpgOutputStream pOut)
+            {
+                SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(
+                    encAlgorithm, s2k, sessionInfo);
+
+				pOut.WritePacket(pk);
+            }
+        }
+
+		private class PubMethod
+            : EncMethod
+        {
+			internal PgpPublicKey pubKey;
+            internal BigInteger[] data;
+
+			internal PubMethod(
+                PgpPublicKey pubKey)
+            {
+                this.pubKey = pubKey;
+            }
+
+			public override void AddSessionInfo(
+                byte[]			si,
+				SecureRandom	random)
+            {
+                IBufferedCipher c;
+
+				switch (pubKey.Algorithm)
+                {
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+                        c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
+                        break;
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+                        c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+                        break;
+                    case PublicKeyAlgorithmTag.Dsa:
+                        throw new PgpException("Can't use DSA for encryption.");
+                    case PublicKeyAlgorithmTag.ECDsa:
+                        throw new PgpException("Can't use ECDSA for encryption.");
+                    default:
+                        throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
+                }
+
+				AsymmetricKeyParameter akp = pubKey.GetKey();
+
+				c.Init(true, new ParametersWithRandom(akp, random));
+
+				byte[] encKey = c.DoFinal(si);
+
+				switch (pubKey.Algorithm)
+                {
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+						data = new BigInteger[]{ new BigInteger(1, encKey) };
+                        break;
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+						int halfLength = encKey.Length / 2;
+						data = new BigInteger[]
+						{
+							new BigInteger(1, encKey, 0, halfLength),
+							new BigInteger(1, encKey, halfLength, halfLength)
+						};
+                        break;
+                    default:
+                        throw new PgpException("unknown asymmetric algorithm: " + encAlgorithm);
+                }
+            }
+
+			public override void Encode(BcpgOutputStream pOut)
+            {
+                PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(
+                    pubKey.KeyId, pubKey.Algorithm, data);
+
+				pOut.WritePacket(pk);
+            }
+        }
+
+		private readonly IList methods = Platform.CreateArrayList();
+        private readonly SymmetricKeyAlgorithmTag defAlgorithm;
+        private readonly SecureRandom rand;
+
+		public PgpEncryptedDataGenerator(
+			SymmetricKeyAlgorithmTag encAlgorithm)
+		{
+			this.defAlgorithm = encAlgorithm;
+			this.rand = new SecureRandom();
+		}
+
+		public PgpEncryptedDataGenerator(
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+			bool						withIntegrityPacket)
+		{
+			this.defAlgorithm = encAlgorithm;
+			this.withIntegrityPacket = withIntegrityPacket;
+			this.rand = new SecureRandom();
+		}
+
+		/// <summary>Existing SecureRandom constructor.</summary>
+		/// <param name="encAlgorithm">The symmetric algorithm to use.</param>
+		/// <param name="rand">Source of randomness.</param>
+        public PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            SecureRandom				rand)
+        {
+            this.defAlgorithm = encAlgorithm;
+            this.rand = rand;
+        }
+
+		/// <summary>Creates a cipher stream which will have an integrity packet associated with it.</summary>
+        public PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            bool						withIntegrityPacket,
+            SecureRandom				rand)
+        {
+            this.defAlgorithm = encAlgorithm;
+            this.rand = rand;
+            this.withIntegrityPacket = withIntegrityPacket;
+        }
+
+		/// <summary>Base constructor.</summary>
+		/// <param name="encAlgorithm">The symmetric algorithm to use.</param>
+		/// <param name="rand">Source of randomness.</param>
+		/// <param name="oldFormat">PGP 2.6.x compatibility required.</param>
+        public PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            SecureRandom				rand,
+            bool						oldFormat)
+        {
+            this.defAlgorithm = encAlgorithm;
+            this.rand = rand;
+            this.oldFormat = oldFormat;
+        }
+
+		/// <summary>
+		/// Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1).
+		/// </summary>
+		public void AddMethod(
+			char[] passPhrase) 
+		{
+			AddMethod(passPhrase, HashAlgorithmTag.Sha1);
+		}
+
+		/// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        public void AddMethod(
+ 			char[]				passPhrase,
+			HashAlgorithmTag	s2kDigest)
+        {
+            byte[] iv = new byte[8];
+			rand.NextBytes(iv);
+
+			S2k s2k = new S2k(s2kDigest, iv, 0x60);
+
+			methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.MakeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase)));
+        }
+
+		/// <summary>Add a public key encrypted session key to the encrypted object.</summary>
+        public void AddMethod(
+            PgpPublicKey key)
+        {
+			if (!key.IsEncryptionKey)
+            {
+                throw new ArgumentException("passed in key not an encryption key!");
+            }
+
+			methods.Add(new PubMethod(key));
+        }
+
+		private void AddCheckSum(
+            byte[] sessionInfo)
+        {
+			Debug.Assert(sessionInfo != null);
+			Debug.Assert(sessionInfo.Length >= 3);
+
+			int check = 0;
+
+			for (int i = 1; i < sessionInfo.Length - 2; i++)
+            {
+                check += sessionInfo[i];
+            }
+
+			sessionInfo[sessionInfo.Length - 2] = (byte)(check >> 8);
+            sessionInfo[sessionInfo.Length - 1] = (byte)(check);
+        }
+
+		private byte[] CreateSessionInfo(
+			SymmetricKeyAlgorithmTag	algorithm,
+			KeyParameter				key)
+		{
+			byte[] keyBytes = key.GetKey();
+			byte[] sessionInfo = new byte[keyBytes.Length + 3];
+			sessionInfo[0] = (byte) algorithm;
+			keyBytes.CopyTo(sessionInfo, 1);
+			AddCheckSum(sessionInfo);
+			return sessionInfo;
+		}
+
+		/// <summary>
+		/// <p>
+		/// If buffer is non null stream assumed to be partial, otherwise the length will be used
+		/// to output a fixed length packet.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// </summary>
+        private Stream Open(
+            Stream	outStr,
+            long	length,
+            byte[]	buffer)
+        {
+			if (cOut != null)
+				throw new InvalidOperationException("generator already in open state");
+			if (methods.Count == 0)
+				throw new InvalidOperationException("No encryption methods specified");
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+
+			pOut = new BcpgOutputStream(outStr);
+
+			KeyParameter key;
+
+			if (methods.Count == 1)
+            {
+                if (methods[0] is PbeMethod)
+                {
+                    PbeMethod m = (PbeMethod)methods[0];
+
+					key = m.GetKey();
+                }
+                else
+                {
+                    key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
+
+					byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
+                    PubMethod m = (PubMethod)methods[0];
+
+                    try
+                    {
+                        m.AddSessionInfo(sessionInfo, rand);
+                    }
+                    catch (Exception e)
+                    {
+                        throw new PgpException("exception encrypting session key", e);
+                    }
+                }
+
+				pOut.WritePacket((ContainedPacket)methods[0]);
+            }
+            else // multiple methods
+            {
+                key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
+				byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
+
+				for (int i = 0; i != methods.Count; i++)
+                {
+                    EncMethod m = (EncMethod)methods[i];
+
+                    try
+                    {
+                        m.AddSessionInfo(sessionInfo, rand);
+                    }
+                    catch (Exception e)
+                    {
+                        throw new PgpException("exception encrypting session key", e);
+                    }
+
+                    pOut.WritePacket(m);
+                }
+            }
+
+            string cName = PgpUtilities.GetSymmetricCipherName(defAlgorithm);
+			if (cName == null)
+            {
+                throw new PgpException("null cipher specified");
+            }
+
+			try
+            {
+                if (withIntegrityPacket)
+                {
+                    cName += "/CFB/NoPadding";
+                }
+                else
+                {
+                    cName += "/OpenPGPCFB/NoPadding";
+                }
+
+                c = CipherUtilities.GetCipher(cName);
+
+				// TODO Confirm the IV should be all zero bytes (not inLineIv - see below)
+				byte[] iv = new byte[c.GetBlockSize()];
+                c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), rand));
+
+                if (buffer == null)
+                {
+                    //
+                    // we have to Add block size + 2 for the Generated IV and + 1 + 22 if integrity protected
+                    //
+                    if (withIntegrityPacket)
+                    {
+                        pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, length + c.GetBlockSize() + 2 + 1 + 22);
+                        pOut.WriteByte(1);        // version number
+                    }
+                    else
+                    {
+                        pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, length + c.GetBlockSize() + 2, oldFormat);
+                    }
+                }
+                else
+                {
+                    if (withIntegrityPacket)
+                    {
+                        pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, buffer);
+                        pOut.WriteByte(1);        // version number
+                    }
+                    else
+                    {
+                        pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, buffer);
+                    }
+                }
+
+				int blockSize = c.GetBlockSize();
+				byte[] inLineIv = new byte[blockSize + 2];
+                rand.NextBytes(inLineIv, 0, blockSize);
+				Array.Copy(inLineIv, inLineIv.Length - 4, inLineIv, inLineIv.Length - 2, 2);
+
+				Stream myOut = cOut = new CipherStream(pOut, null, c);
+
+				if (withIntegrityPacket)
+                {
+					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
+					IDigest digest = DigestUtilities.GetDigest(digestName);
+					myOut = digestOut = new DigestStream(myOut, null, digest);
+                }
+
+				myOut.Write(inLineIv, 0, inLineIv.Length);
+
+				return new WrappedGeneratorStream(this, myOut);
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception creating cipher", e);
+            }
+        }
+
+		/// <summary>
+		/// <p>
+		/// Return an output stream which will encrypt the data as it is written to it.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// </summary>
+        public Stream Open(
+            Stream	outStr,
+            long	length)
+        {
+            return Open(outStr, length, null);
+        }
+
+		/// <summary>
+		/// <p>
+		/// Return an output stream which will encrypt the data as it is written to it.
+		/// The stream will be written out in chunks according to the size of the passed in buffer.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// <p>
+		/// <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
+		/// bytes worth of the buffer will be used.
+		/// </p>
+		/// </summary>
+        public Stream Open(
+            Stream	outStr,
+            byte[]	buffer)
+        {
+            return Open(outStr, 0, buffer);
+        }
+
+		/// <summary>
+		/// <p>
+		/// Close off the encrypted object - this is equivalent to calling Close() on the stream
+		/// returned by the Open() method.
+		/// </p>
+		/// <p>
+		/// <b>Note</b>: This does not close the underlying output stream, only the stream on top of
+		/// it created by the Open() method.
+		/// </p>
+		/// </summary>
+        public void Close()
+        {
+            if (cOut != null)
+            {
+				// TODO Should this all be under the try/catch block?
+                if (digestOut != null)
+                {
+                    //
+                    // hand code a mod detection packet
+                    //
+                    BcpgOutputStream bOut = new BcpgOutputStream(
+						digestOut, PacketTag.ModificationDetectionCode, 20);
+
+                    bOut.Flush();
+                    digestOut.Flush();
+
+					// TODO
+					byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest());
+					cOut.Write(dig, 0, dig.Length);
+                }
+
+				cOut.Flush();
+
+				try
+                {
+					pOut.Write(c.DoFinal());
+                    pOut.Finish();
+                }
+                catch (Exception e)
+                {
+                    throw new IOException(e.Message, e);
+                }
+
+				cOut = null;
+				pOut = null;
+            }
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpEncryptedDataList.cs b/crypto/src/openpgp/PgpEncryptedDataList.cs
new file mode 100644
index 000000000..8dded7c05
--- /dev/null
+++ b/crypto/src/openpgp/PgpEncryptedDataList.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>A holder for a list of PGP encryption method packets.</remarks>
+    public class PgpEncryptedDataList
+		: PgpObject
+    {
+        private IList list = Platform.CreateArrayList();
+        private InputStreamPacket data;
+
+		public PgpEncryptedDataList(
+            BcpgInputStream bcpgInput)
+        {
+            while (bcpgInput.NextPacketTag() == PacketTag.PublicKeyEncryptedSession
+                || bcpgInput.NextPacketTag() == PacketTag.SymmetricKeyEncryptedSessionKey)
+            {
+                list.Add(bcpgInput.ReadPacket());
+            }
+
+			data = (InputStreamPacket)bcpgInput.ReadPacket();
+
+			for (int i = 0; i != list.Count; i++)
+            {
+                if (list[i] is SymmetricKeyEncSessionPacket)
+                {
+                    list[i] = new PgpPbeEncryptedData((SymmetricKeyEncSessionPacket) list[i], data);
+                }
+                else
+                {
+                    list[i] = new PgpPublicKeyEncryptedData((PublicKeyEncSessionPacket) list[i], data);
+                }
+            }
+        }
+
+		public PgpEncryptedData this[int index]
+		{
+			get { return (PgpEncryptedData) list[index]; }
+		}
+
+		[Obsolete("Use 'object[index]' syntax instead")]
+		public object Get(int index)
+        {
+            return this[index];
+        }
+
+		[Obsolete("Use 'Count' property instead")]
+		public int Size
+        {
+			get { return list.Count; }
+        }
+
+		public int Count
+		{
+			get { return list.Count; }
+		}
+
+		public bool IsEmpty
+        {
+			get { return list.Count == 0; }
+        }
+
+		public IEnumerable GetEncryptedDataObjects()
+        {
+            return new EnumerableProxy(list);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpException.cs b/crypto/src/openpgp/PgpException.cs
new file mode 100644
index 000000000..3048116fa
--- /dev/null
+++ b/crypto/src/openpgp/PgpException.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Generic exception class for PGP encoding/decoding problems.</remarks>
+	public class PgpException
+		: Exception
+	{
+		public PgpException() : base() {}
+		public PgpException(string message) : base(message) {}
+		public PgpException(string message, Exception exception) : base(message, exception) {}
+
+		[Obsolete("Use InnerException property")]
+		public Exception UnderlyingException
+		{
+			get { return InnerException; }
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpExperimental.cs b/crypto/src/openpgp/PgpExperimental.cs
new file mode 100644
index 000000000..8518335a1
--- /dev/null
+++ b/crypto/src/openpgp/PgpExperimental.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	public class PgpExperimental
+		: PgpObject
+	{
+		private readonly ExperimentalPacket p;
+
+		public PgpExperimental(
+			BcpgInputStream bcpgIn)
+		{
+			p = (ExperimentalPacket) bcpgIn.ReadPacket();
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpKeyFlags.cs b/crypto/src/openpgp/PgpKeyFlags.cs
new file mode 100644
index 000000000..ea1800606
--- /dev/null
+++ b/crypto/src/openpgp/PgpKeyFlags.cs
@@ -0,0 +1,13 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Key flag values for the KeyFlags subpacket.</remarks>
+    public abstract class PgpKeyFlags
+    {
+        public const int CanCertify = 0x01; // This key may be used to certify other keys.
+        public const int CanSign = 0x02; // This key may be used to sign data.
+        public const int CanEncryptCommunications = 0x04; // This key may be used to encrypt communications.
+        public const int CanEncryptStorage = 0x08; // This key may be used to encrypt storage.
+        public const int MaybeSplit = 0x10; // The private component of this key may have been split by a secret-sharing mechanism.
+        public const int MaybeShared = 0x80; // The private component of this key may be in the possession of more than one person.
+    }
+}
diff --git a/crypto/src/openpgp/PgpKeyPair.cs b/crypto/src/openpgp/PgpKeyPair.cs
new file mode 100644
index 000000000..6efb03a42
--- /dev/null
+++ b/crypto/src/openpgp/PgpKeyPair.cs
@@ -0,0 +1,67 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// General class to handle JCA key pairs and convert them into OpenPGP ones.
+	/// <p>
+	/// A word for the unwary, the KeyId for an OpenPGP public key is calculated from
+	/// a hash that includes the time of creation, if you pass a different date to the
+	/// constructor below with the same public private key pair the KeyIs will not be the
+	/// same as for previous generations of the key, so ideally you only want to do
+	/// this once.
+	/// </p>
+	/// </remarks>
+    public class PgpKeyPair
+    {
+        private readonly PgpPublicKey	pub;
+        private readonly PgpPrivateKey	priv;
+
+		public PgpKeyPair(
+            PublicKeyAlgorithmTag	algorithm,
+            AsymmetricCipherKeyPair	keyPair,
+            DateTime				time)
+			: this(algorithm, keyPair.Public, keyPair.Private, time)
+        {
+        }
+
+		public PgpKeyPair(
+            PublicKeyAlgorithmTag	algorithm,
+            AsymmetricKeyParameter	pubKey,
+            AsymmetricKeyParameter	privKey,
+            DateTime				time)
+        {
+            this.pub = new PgpPublicKey(algorithm, pubKey, time);
+			this.priv = new PgpPrivateKey(privKey, pub.KeyId);
+        }
+
+		/// <summary>Create a key pair from a PgpPrivateKey and a PgpPublicKey.</summary>
+		/// <param name="pub">The public key.</param>
+		/// <param name="priv">The private key.</param>
+        public PgpKeyPair(
+            PgpPublicKey	pub,
+            PgpPrivateKey	priv)
+        {
+            this.pub = pub;
+            this.priv = priv;
+        }
+
+		/// <summary>The keyId associated with this key pair.</summary>
+        public long KeyId
+        {
+            get { return pub.KeyId; }
+        }
+
+		public PgpPublicKey PublicKey
+        {
+			get { return pub; }
+        }
+
+		public PgpPrivateKey PrivateKey
+        {
+			get { return priv; }
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs
new file mode 100644
index 000000000..e85fc2eef
--- /dev/null
+++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Generator for a PGP master and subkey ring.
+	/// This class will generate both the secret and public key rings
+	/// </remarks>
+    public class PgpKeyRingGenerator
+    {
+        private IList					    keys = Platform.CreateArrayList();
+        private string                      id;
+        private SymmetricKeyAlgorithmTag	encAlgorithm;
+        private int                         certificationLevel;
+        private char[]                      passPhrase;
+		private bool						useSha1;
+		private PgpKeyPair                  masterKey;
+        private PgpSignatureSubpacketVector hashedPacketVector;
+        private PgpSignatureSubpacketVector unhashedPacketVector;
+        private SecureRandom				rand;
+
+		/// <summary>
+		/// Create a new key ring generator using old style checksumming. It is recommended to use
+		/// SHA1 checksumming where possible.
+		/// </summary>
+		/// <param name="certificationLevel">The certification level for keys on this ring.</param>
+		/// <param name="masterKey">The master key pair.</param>
+		/// <param name="id">The id to be associated with the ring.</param>
+		/// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+		/// <param name="passPhrase">The passPhrase to be used to protect secret keys.</param>
+		/// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+		/// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+		/// <param name="rand">input secured random.</param>
+		public PgpKeyRingGenerator(
+			int							certificationLevel,
+			PgpKeyPair					masterKey,
+			string						id,
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+			char[]						passPhrase,
+			PgpSignatureSubpacketVector	hashedPackets,
+			PgpSignatureSubpacketVector	unhashedPackets,
+			SecureRandom				rand)
+			: this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand)
+		{
+		}
+
+		/// <summary>
+		/// Create a new key ring generator.
+		/// </summary>
+		/// <param name="certificationLevel">The certification level for keys on this ring.</param>
+		/// <param name="masterKey">The master key pair.</param>
+		/// <param name="id">The id to be associated with the ring.</param>
+		/// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+		/// <param name="passPhrase">The passPhrase to be used to protect secret keys.</param>
+		/// <param name="useSha1">Checksum the secret keys with SHA1 rather than the older 16 bit checksum.</param>
+		/// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+		/// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+		/// <param name="rand">input secured random.</param>
+        public PgpKeyRingGenerator(
+            int							certificationLevel,
+            PgpKeyPair					masterKey,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            char[]						passPhrase,
+			bool						useSha1,
+			PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+        {
+            this.certificationLevel = certificationLevel;
+            this.masterKey = masterKey;
+            this.id = id;
+            this.encAlgorithm = encAlgorithm;
+            this.passPhrase = passPhrase;
+			this.useSha1 = useSha1;
+			this.hashedPacketVector = hashedPackets;
+            this.unhashedPacketVector = unhashedPackets;
+            this.rand = rand;
+
+			keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand));
+        }
+
+		/// <summary>Add a subkey to the key ring to be generated with default certification.</summary>
+        public void AddSubKey(
+            PgpKeyPair keyPair)
+        {
+			AddSubKey(keyPair, this.hashedPacketVector, this.unhashedPacketVector);
+		}
+
+		/// <summary>
+		/// Add a subkey with specific hashed and unhashed packets associated with it and
+		/// default certification.
+		/// </summary>
+		/// <param name="keyPair">Public/private key pair.</param>
+		/// <param name="hashedPackets">Hashed packet values to be included in certification.</param>
+		/// <param name="unhashedPackets">Unhashed packets values to be included in certification.</param>
+		/// <exception cref="PgpException"></exception>
+		public void AddSubKey(
+			PgpKeyPair					keyPair,
+			PgpSignatureSubpacketVector	hashedPackets,
+			PgpSignatureSubpacketVector	unhashedPackets)
+		{
+			try
+            {
+                PgpSignatureGenerator sGen = new PgpSignatureGenerator(
+					masterKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
+
+				//
+                // Generate the certification
+                //
+                sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey);
+
+				sGen.SetHashedSubpackets(hashedPackets);
+                sGen.SetUnhashedSubpackets(unhashedPackets);
+
+				IList subSigs = Platform.CreateArrayList();
+
+				subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey));
+
+				keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, passPhrase, useSha1, rand));
+			}
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("exception adding subkey: ", e);
+            }
+        }
+
+		/// <summary>Return the secret key ring.</summary>
+        public PgpSecretKeyRing GenerateSecretKeyRing()
+        {
+            return new PgpSecretKeyRing(keys);
+        }
+
+		/// <summary>Return the public key ring that corresponds to the secret key ring.</summary>
+        public PgpPublicKeyRing GeneratePublicKeyRing()
+        {
+            IList pubKeys = Platform.CreateArrayList();
+
+            IEnumerator enumerator = keys.GetEnumerator();
+            enumerator.MoveNext();
+
+			PgpSecretKey pgpSecretKey = (PgpSecretKey) enumerator.Current;
+			pubKeys.Add(pgpSecretKey.PublicKey);
+
+			while (enumerator.MoveNext())
+            {
+                pgpSecretKey = (PgpSecretKey) enumerator.Current;
+
+				PgpPublicKey k = new PgpPublicKey(pgpSecretKey.PublicKey);
+				k.publicPk = new PublicSubkeyPacket(
+					k.Algorithm, k.CreationTime, k.publicPk.Key);
+
+				pubKeys.Add(k);
+			}
+
+			return new PgpPublicKeyRing(pubKeys);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpKeyValidationException.cs b/crypto/src/openpgp/PgpKeyValidationException.cs
new file mode 100644
index 000000000..da07f400f
--- /dev/null
+++ b/crypto/src/openpgp/PgpKeyValidationException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Thrown if the key checksum is invalid.
+	/// </remarks>
+	public class PgpKeyValidationException
+		: PgpException
+	{
+		public PgpKeyValidationException() : base() {}
+		public PgpKeyValidationException(string message) : base(message) {}
+		public PgpKeyValidationException(string message, Exception exception) : base(message, exception) {}
+	}
+}
diff --git a/crypto/src/openpgp/PgpLiteralData.cs b/crypto/src/openpgp/PgpLiteralData.cs
new file mode 100644
index 000000000..79bbc3984
--- /dev/null
+++ b/crypto/src/openpgp/PgpLiteralData.cs
@@ -0,0 +1,63 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <summary>Class for processing literal data objects.</summary>
+    public class PgpLiteralData
+		: PgpObject
+    {
+        public const char Binary = 'b';
+        public const char Text = 't';
+		public const char Utf8 = 'u';
+
+		/// <summary>The special name indicating a "for your eyes only" packet.</summary>
+        public const string Console = "_CONSOLE";
+
+		private LiteralDataPacket data;
+
+		public PgpLiteralData(
+            BcpgInputStream bcpgInput)
+        {
+            data = (LiteralDataPacket) bcpgInput.ReadPacket();
+        }
+
+		/// <summary>The format of the data stream - Binary or Text</summary>
+        public int Format
+        {
+            get { return data.Format; }
+        }
+
+		/// <summary>The file name that's associated with the data stream.</summary>
+        public string FileName
+        {
+			get { return data.FileName; }
+        }
+
+		/// Return the file name as an unintrepreted byte array.
+		public byte[] GetRawFileName()
+		{
+			return data.GetRawFileName();
+		}
+
+		/// <summary>The modification time for the file.</summary>
+        public DateTime ModificationTime
+        {
+			get { return DateTimeUtilities.UnixMsToDateTime(data.ModificationTime); }
+        }
+
+		/// <summary>The raw input stream for the data stream.</summary>
+        public Stream GetInputStream()
+        {
+            return data.GetInputStream();
+        }
+
+		/// <summary>The input stream representing the data stream.</summary>
+        public Stream GetDataStream()
+        {
+            return GetInputStream();
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpLiteralDataGenerator.cs b/crypto/src/openpgp/PgpLiteralDataGenerator.cs
new file mode 100644
index 000000000..b0337c80d
--- /dev/null
+++ b/crypto/src/openpgp/PgpLiteralDataGenerator.cs
@@ -0,0 +1,180 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Class for producing literal data packets.</remarks>
+    public class PgpLiteralDataGenerator
+		: IStreamGenerator
+	{
+        public const char Binary = PgpLiteralData.Binary;
+        public const char Text = PgpLiteralData.Text;
+		public const char Utf8 = PgpLiteralData.Utf8;
+
+		/// <summary>The special name indicating a "for your eyes only" packet.</summary>
+        public const string Console = PgpLiteralData.Console;
+
+		private BcpgOutputStream pkOut;
+        private bool oldFormat;
+
+		public PgpLiteralDataGenerator()
+        {
+        }
+
+		/// <summary>
+		/// Generates literal data objects in the old format.
+		/// This is important if you need compatibility with PGP 2.6.x.
+		/// </summary>
+		/// <param name="oldFormat">If true, uses old format.</param>
+        public PgpLiteralDataGenerator(
+            bool oldFormat)
+        {
+            this.oldFormat = oldFormat;
+        }
+
+		private void WriteHeader(
+            BcpgOutputStream	outStr,
+            char				format,
+            string				name,
+            long				modificationTime)
+        {
+			byte[] encName = Strings.ToUtf8ByteArray(name);
+
+			outStr.Write(
+				(byte) format,
+				(byte) encName.Length);
+
+			outStr.Write(encName);
+
+			long modDate = modificationTime / 1000L;
+
+			outStr.Write(
+				(byte)(modDate >> 24),
+				(byte)(modDate >> 16),
+				(byte)(modDate >> 8),
+				(byte)modDate);
+        }
+
+		/// <summary>
+		/// <p>
+		/// Open a literal data packet, returning a stream to store the data inside the packet.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// </summary>
+		/// <param name="outStr">The stream we want the packet in.</param>
+		/// <param name="format">The format we are using.</param>
+		/// <param name="name">The name of the 'file'.</param>
+		/// <param name="length">The length of the data we will write.</param>
+		/// <param name="modificationTime">The time of last modification we want stored.</param>
+        public Stream Open(
+            Stream		outStr,
+            char		format,
+            string		name,
+            long		length,
+            DateTime	modificationTime)
+        {
+			if (pkOut != null)
+				throw new InvalidOperationException("generator already in open state");
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+
+			// Do this first, since it might throw an exception
+			long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime);
+
+			pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData,
+				length + 2 + name.Length + 4, oldFormat);
+
+			WriteHeader(pkOut, format, name, unixMs);
+
+			return new WrappedGeneratorStream(this, pkOut);
+        }
+
+		/// <summary>
+		/// <p>
+		/// Open a literal data packet, returning a stream to store the data inside the packet,
+		/// as an indefinite length stream. The stream is written out as a series of partial
+		/// packets with a chunk size determined by the size of the passed in buffer.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// <p>
+		/// <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
+		/// bytes worth of the buffer will be used.</p>
+		/// </summary>
+		/// <param name="outStr">The stream we want the packet in.</param>
+		/// <param name="format">The format we are using.</param>
+		/// <param name="name">The name of the 'file'.</param>
+		/// <param name="modificationTime">The time of last modification we want stored.</param>
+		/// <param name="buffer">The buffer to use for collecting data to put into chunks.</param>
+        public Stream Open(
+            Stream		outStr,
+            char		format,
+            string		name,
+            DateTime	modificationTime,
+            byte[]		buffer)
+        {
+			if (pkOut != null)
+				throw new InvalidOperationException("generator already in open state");
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+
+			// Do this first, since it might throw an exception
+			long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime);
+
+			pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, buffer);
+
+			WriteHeader(pkOut, format, name, unixMs);
+
+			return new WrappedGeneratorStream(this, pkOut);
+		}
+
+#if !PORTABLE
+		/// <summary>
+		/// <p>
+		/// Open a literal data packet for the passed in <c>FileInfo</c> object, returning
+		/// an output stream for saving the file contents.
+		/// </p>
+		/// <p>
+		/// The stream created can be closed off by either calling Close()
+		/// on the stream or Close() on the generator. Closing the returned
+		/// stream does not close off the Stream parameter <c>outStr</c>.
+		/// </p>
+		/// </summary>
+		/// <param name="outStr">The stream we want the packet in.</param>
+		/// <param name="format">The format we are using.</param>
+		/// <param name="file">The <c>FileInfo</c> object containg the packet details.</param>
+		public Stream Open(
+            Stream		outStr,
+            char		format,
+            FileInfo	file)
+        {
+			return Open(outStr, format, file.Name, file.Length, file.LastWriteTime);
+        }
+#endif
+
+		/// <summary>
+		/// Close the literal data packet - this is equivalent to calling Close()
+		/// on the stream returned by the Open() method.
+		/// </summary>
+        public void Close()
+        {
+			if (pkOut != null)
+			{
+				pkOut.Finish();
+				pkOut.Flush();
+				pkOut = null;
+			}
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpMarker.cs b/crypto/src/openpgp/PgpMarker.cs
new file mode 100644
index 000000000..733e4e959
--- /dev/null
+++ b/crypto/src/openpgp/PgpMarker.cs
@@ -0,0 +1,18 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// A PGP marker packet - in general these should be ignored other than where
+	/// the idea is to preserve the original input stream.
+	/// </remarks>
+    public class PgpMarker
+		: PgpObject
+    {
+        private readonly MarkerPacket p;
+
+		public PgpMarker(
+            BcpgInputStream bcpgIn)
+        {
+            p = (MarkerPacket) bcpgIn.ReadPacket();
+        }
+	}
+}
diff --git a/crypto/src/openpgp/PgpObjectFactory.cs b/crypto/src/openpgp/PgpObjectFactory.cs
new file mode 100644
index 000000000..c5c6fcb68
--- /dev/null
+++ b/crypto/src/openpgp/PgpObjectFactory.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+    /// General class for reading a PGP object stream.
+    /// <p>
+    /// Note: if this class finds a PgpPublicKey or a PgpSecretKey it
+    /// will create a PgpPublicKeyRing, or a PgpSecretKeyRing for each
+    /// key found. If all you are trying to do is read a key ring file use
+    /// either PgpPublicKeyRingBundle or PgpSecretKeyRingBundle.</p>
+	/// </remarks>
+	public class PgpObjectFactory
+    {
+        private readonly BcpgInputStream bcpgIn;
+
+		public PgpObjectFactory(
+            Stream inputStream)
+        {
+            this.bcpgIn = BcpgInputStream.Wrap(inputStream);
+        }
+
+        public PgpObjectFactory(
+            byte[] bytes)
+            : this(new MemoryStream(bytes, false))
+        {
+        }
+
+		/// <summary>Return the next object in the stream, or null if the end is reached.</summary>
+		/// <exception cref="IOException">On a parse error</exception>
+        public PgpObject NextPgpObject()
+        {
+            PacketTag tag = bcpgIn.NextPacketTag();
+
+            if ((int) tag == -1) return null;
+
+            switch (tag)
+            {
+                case PacketTag.Signature:
+                {
+                    IList l = Platform.CreateArrayList();
+
+                    while (bcpgIn.NextPacketTag() == PacketTag.Signature)
+                    {
+                        try
+                        {
+                            l.Add(new PgpSignature(bcpgIn));
+                        }
+                        catch (PgpException e)
+                        {
+                            throw new IOException("can't create signature object: " + e);
+                        }
+                    }
+
+                    PgpSignature[] sigs = new PgpSignature[l.Count];
+                    for (int i = 0; i < l.Count; ++i)
+                    {
+                        sigs[i] = (PgpSignature)l[i];
+                    }
+					return new PgpSignatureList(sigs);
+                }
+                case PacketTag.SecretKey:
+                    try
+                    {
+                        return new PgpSecretKeyRing(bcpgIn);
+                    }
+                    catch (PgpException e)
+                    {
+                        throw new IOException("can't create secret key object: " + e);
+                    }
+                case PacketTag.PublicKey:
+                    return new PgpPublicKeyRing(bcpgIn);
+				// TODO Make PgpPublicKey a PgpObject or return a PgpPublicKeyRing
+//				case PacketTag.PublicSubkey:
+//					return PgpPublicKeyRing.ReadSubkey(bcpgIn);
+                case PacketTag.CompressedData:
+                    return new PgpCompressedData(bcpgIn);
+                case PacketTag.LiteralData:
+                    return new PgpLiteralData(bcpgIn);
+                case PacketTag.PublicKeyEncryptedSession:
+                case PacketTag.SymmetricKeyEncryptedSessionKey:
+                    return new PgpEncryptedDataList(bcpgIn);
+                case PacketTag.OnePassSignature:
+                {
+                    IList l = Platform.CreateArrayList();
+
+                    while (bcpgIn.NextPacketTag() == PacketTag.OnePassSignature)
+                    {
+                        try
+                        {
+                            l.Add(new PgpOnePassSignature(bcpgIn));
+                        }
+                        catch (PgpException e)
+                        {
+							throw new IOException("can't create one pass signature object: " + e);
+						}
+                    }
+
+                    PgpOnePassSignature[] sigs = new PgpOnePassSignature[l.Count];
+                    for (int i = 0; i < l.Count; ++i)
+                    {
+                        sigs[i] = (PgpOnePassSignature)l[i];
+                    }
+					return new PgpOnePassSignatureList(sigs);
+                }
+                case PacketTag.Marker:
+                    return new PgpMarker(bcpgIn);
+                case PacketTag.Experimental1:
+                case PacketTag.Experimental2:
+                case PacketTag.Experimental3:
+                case PacketTag.Experimental4:
+					return new PgpExperimental(bcpgIn);
+            }
+
+            throw new IOException("unknown object in stream " + bcpgIn.NextPacketTag());
+        }
+
+		[Obsolete("Use NextPgpObject() instead")]
+		public object NextObject()
+		{
+			return NextPgpObject();
+		}
+
+		/// <summary>
+		/// Return all available objects in a list.
+		/// </summary>
+		/// <returns>An <c>IList</c> containing all objects from this factory, in order.</returns>
+		public IList AllPgpObjects()
+		{
+            IList result = Platform.CreateArrayList();
+			PgpObject pgpObject;
+			while ((pgpObject = NextPgpObject()) != null)
+			{
+				result.Add(pgpObject);
+			}
+			return result;
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
new file mode 100644
index 000000000..68fc5994d
--- /dev/null
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -0,0 +1,179 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>A one pass signature object.</remarks>
+    public class PgpOnePassSignature
+    {
+        private OnePassSignaturePacket sigPack;
+        private int signatureType;
+		private ISigner sig;
+		private byte lastb;
+
+		internal PgpOnePassSignature(
+            BcpgInputStream bcpgInput)
+            : this((OnePassSignaturePacket) bcpgInput.ReadPacket())
+        {
+        }
+
+		internal PgpOnePassSignature(
+            OnePassSignaturePacket sigPack)
+        {
+            this.sigPack = sigPack;
+            this.signatureType = sigPack.SignatureType;
+        }
+
+		/// <summary>Initialise the signature object for verification.</summary>
+        public void InitVerify(
+            PgpPublicKey pubKey)
+        {
+			lastb = 0;
+
+			try
+			{
+				sig = SignerUtilities.GetSigner(
+					PgpUtilities.GetSignatureName(sigPack.KeyAlgorithm, sigPack.HashAlgorithm));
+			}
+			catch (Exception e)
+			{
+				throw new PgpException("can't set up signature object.",  e);
+			}
+
+			try
+            {
+                sig.Init(false, pubKey.GetKey());
+            }
+			catch (InvalidKeyException e)
+            {
+                throw new PgpException("invalid key.", e);
+            }
+        }
+
+		public void Update(
+            byte b)
+        {
+			if (signatureType == PgpSignature.CanonicalTextDocument)
+			{
+				doCanonicalUpdateByte(b);
+			}
+			else
+			{
+				sig.Update(b);
+			}
+        }
+
+		private void doCanonicalUpdateByte(
+			byte b)
+		{
+			if (b == '\r')
+			{
+				doUpdateCRLF();
+			}
+			else if (b == '\n')
+			{
+				if (lastb != '\r')
+				{
+					doUpdateCRLF();
+				}
+			}
+			else
+			{
+				sig.Update(b);
+			}
+
+			lastb = b;
+		}
+
+		private void doUpdateCRLF()
+		{
+			sig.Update((byte)'\r');
+			sig.Update((byte)'\n');
+		}
+
+		public void Update(
+            byte[] bytes)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                for (int i = 0; i != bytes.Length; i++)
+                {
+                    doCanonicalUpdateByte(bytes[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(bytes, 0, bytes.Length);
+            }
+        }
+
+        public void Update(
+            byte[]  bytes,
+            int     off,
+            int     length)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                int finish = off + length;
+
+                for (int i = off; i != finish; i++)
+                {
+                    doCanonicalUpdateByte(bytes[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(bytes, off, length);
+            }
+        }
+
+		/// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
+        public bool Verify(
+            PgpSignature pgpSig)
+        {
+            byte[] trailer = pgpSig.GetSignatureTrailer();
+
+			sig.BlockUpdate(trailer, 0, trailer.Length);
+
+			return sig.VerifySignature(pgpSig.GetSignature());
+        }
+
+        public long KeyId
+        {
+			get { return sigPack.KeyId; }
+        }
+
+		public int SignatureType
+        {
+            get { return sigPack.SignatureType; }
+        }
+
+		public HashAlgorithmTag HashAlgorithm
+		{
+			get { return sigPack.HashAlgorithm; }
+		}
+
+		public PublicKeyAlgorithmTag KeyAlgorithm
+		{
+			get { return sigPack.KeyAlgorithm; }
+		}
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+
+            Encode(bOut);
+
+            return bOut.ToArray();
+        }
+
+		public void Encode(
+            Stream outStr)
+        {
+            BcpgOutputStream.Wrap(outStr).WritePacket(sigPack);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpOnePassSignatureList.cs b/crypto/src/openpgp/PgpOnePassSignatureList.cs
new file mode 100644
index 000000000..37c4288e3
--- /dev/null
+++ b/crypto/src/openpgp/PgpOnePassSignatureList.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Holder for a list of PgpOnePassSignature objects.</remarks>
+    public class PgpOnePassSignatureList
+		: PgpObject
+    {
+        private readonly PgpOnePassSignature[] sigs;
+
+		public PgpOnePassSignatureList(
+            PgpOnePassSignature[] sigs)
+        {
+			this.sigs = (PgpOnePassSignature[]) sigs.Clone();
+        }
+
+		public PgpOnePassSignatureList(
+            PgpOnePassSignature sig)
+        {
+			this.sigs = new PgpOnePassSignature[]{ sig };
+        }
+
+		public PgpOnePassSignature this[int index]
+		{
+			get { return sigs[index]; }
+		}
+
+		[Obsolete("Use 'object[index]' syntax instead")]
+		public PgpOnePassSignature Get(
+            int index)
+        {
+            return this[index];
+        }
+
+		[Obsolete("Use 'Count' property instead")]
+		public int Size
+        {
+			get { return sigs.Length; }
+        }
+
+		public int Count
+		{
+			get { return sigs.Length; }
+		}
+
+		public bool IsEmpty
+        {
+			get { return (sigs.Length == 0); }
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
new file mode 100644
index 000000000..c5fe89407
--- /dev/null
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -0,0 +1,135 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>A password based encryption object.</remarks>
+    public class PgpPbeEncryptedData
+        : PgpEncryptedData
+    {
+        private readonly SymmetricKeyEncSessionPacket keyData;
+
+		internal PgpPbeEncryptedData(
+			SymmetricKeyEncSessionPacket	keyData,
+			InputStreamPacket				encData)
+			: base(encData)
+		{
+			this.keyData = keyData;
+		}
+
+		/// <summary>Return the raw input stream for the data stream.</summary>
+		public override Stream GetInputStream()
+		{
+			return encData.GetInputStream();
+		}
+
+		/// <summary>Return the decrypted input stream, using the passed in passphrase.</summary>
+        public Stream GetDataStream(
+            char[] passPhrase)
+        {
+			try
+			{
+				SymmetricKeyAlgorithmTag keyAlgorithm = keyData.EncAlgorithm;
+
+				KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(
+					keyAlgorithm, keyData.S2k, passPhrase);
+
+
+				byte[] secKeyData = keyData.GetSecKeyData();
+				if (secKeyData != null && secKeyData.Length > 0)
+				{
+					IBufferedCipher keyCipher = CipherUtilities.GetCipher(
+						PgpUtilities.GetSymmetricCipherName(keyAlgorithm) + "/CFB/NoPadding");
+
+					keyCipher.Init(false,
+						new ParametersWithIV(key, new byte[keyCipher.GetBlockSize()]));
+
+					byte[] keyBytes = keyCipher.DoFinal(secKeyData);
+
+					keyAlgorithm = (SymmetricKeyAlgorithmTag) keyBytes[0];
+
+					key = ParameterUtilities.CreateKeyParameter(
+						PgpUtilities.GetSymmetricCipherName(keyAlgorithm),
+						keyBytes, 1, keyBytes.Length - 1);
+				}
+
+
+				IBufferedCipher c = CreateStreamCipher(keyAlgorithm);
+
+				byte[] iv = new byte[c.GetBlockSize()];
+
+				c.Init(false, new ParametersWithIV(key, iv));
+
+				encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c, null));
+
+				if (encData is SymmetricEncIntegrityPacket)
+				{
+					truncStream = new TruncatedStream(encStream);
+
+					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
+					IDigest digest = DigestUtilities.GetDigest(digestName);
+
+					encStream = new DigestStream(truncStream, digest, null);
+				}
+
+				if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
+					throw new EndOfStreamException("unexpected end of stream.");
+
+				int v1 = encStream.ReadByte();
+				int v2 = encStream.ReadByte();
+
+				if (v1 < 0 || v2 < 0)
+					throw new EndOfStreamException("unexpected end of stream.");
+
+
+				// Note: the oracle attack on the "quick check" bytes is not deemed
+				// a security risk for PBE (see PgpPublicKeyEncryptedData)
+
+				bool repeatCheckPassed =
+						iv[iv.Length - 2] == (byte)v1
+					&&	iv[iv.Length - 1] == (byte)v2;
+
+				// Note: some versions of PGP appear to produce 0 for the extra
+				// bytes rather than repeating the two previous bytes
+				bool zeroesCheckPassed =
+						v1 == 0
+					&&	v2 == 0;
+
+				if (!repeatCheckPassed && !zeroesCheckPassed)
+				{
+					throw new PgpDataValidationException("quick check failed.");
+				}
+
+
+				return encStream;
+			}
+			catch (PgpException e)
+			{
+				throw e;
+			}
+			catch (Exception e)
+			{
+				throw new PgpException("Exception creating cipher", e);
+			}
+		}
+
+		private IBufferedCipher CreateStreamCipher(
+			SymmetricKeyAlgorithmTag keyAlgorithm)
+		{
+			string mode = (encData is SymmetricEncIntegrityPacket)
+				? "CFB"
+				: "OpenPGPCFB";
+
+			string cName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm)
+				+ "/" + mode + "/NoPadding";
+
+			return CipherUtilities.GetCipher(cName);
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpPrivateKey.cs b/crypto/src/openpgp/PgpPrivateKey.cs
new file mode 100644
index 000000000..154c87cd7
--- /dev/null
+++ b/crypto/src/openpgp/PgpPrivateKey.cs
@@ -0,0 +1,42 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>General class to contain a private key for use with other OpenPGP objects.</remarks>
+    public class PgpPrivateKey
+    {
+        private readonly long keyId;
+        private readonly AsymmetricKeyParameter privateKey;
+
+		/// <summary>
+		/// Create a PgpPrivateKey from a regular private key and the ID of its
+		/// associated public key.
+		/// </summary>
+		/// <param name="privateKey">Private key to use.</param>
+		/// <param name="keyId">ID of the corresponding public key.</param>
+		public PgpPrivateKey(
+            AsymmetricKeyParameter	privateKey,
+            long					keyId)
+        {
+			if (!privateKey.IsPrivate)
+				throw new ArgumentException("Expected a private key", "privateKey");
+
+			this.privateKey = privateKey;
+            this.keyId = keyId;
+        }
+
+		/// <summary>The keyId associated with the contained private key.</summary>
+        public long KeyId
+        {
+			get { return keyId; }
+        }
+
+		/// <summary>The contained private key.</summary>
+        public AsymmetricKeyParameter Key
+        {
+			get { return privateKey; }
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
new file mode 100644
index 000000000..b0720146c
--- /dev/null
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -0,0 +1,890 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>General class to handle a PGP public key object.</remarks>
+    public class PgpPublicKey
+    {
+		private static readonly int[] MasterKeyCertificationTypes = new int[]
+		{
+			PgpSignature.PositiveCertification,
+			PgpSignature.CasualCertification,
+			PgpSignature.NoCertification,
+			PgpSignature.DefaultCertification
+		};
+
+		private long				keyId;
+        private byte[]				fingerprint;
+        private int					keyStrength;
+
+		internal PublicKeyPacket	publicPk;
+        internal TrustPacket		trustPk;
+        internal IList			    keySigs = Platform.CreateArrayList();
+        internal IList			    ids = Platform.CreateArrayList();
+        internal IList              idTrusts = Platform.CreateArrayList();
+        internal IList              idSigs = Platform.CreateArrayList();
+        internal IList			    subSigs;
+
+		private void Init()
+        {
+            IBcpgKey key = publicPk.Key;
+
+			if (publicPk.Version <= 3)
+            {
+                RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
+
+				this.keyId = rK.Modulus.LongValue;
+
+				try
+                {
+                    IDigest digest = DigestUtilities.GetDigest("MD5");
+
+					byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
+					digest.BlockUpdate(bytes, 0, bytes.Length);
+
+					bytes = rK.PublicExponent.ToByteArrayUnsigned();
+					digest.BlockUpdate(bytes, 0, bytes.Length);
+
+					this.fingerprint = DigestUtilities.DoFinal(digest);
+                }
+				//catch (NoSuchAlgorithmException)
+				catch (Exception e)
+                {
+                    throw new IOException("can't find MD5", e);
+                }
+
+				this.keyStrength = rK.Modulus.BitLength;
+            }
+            else
+            {
+                byte[] kBytes = publicPk.GetEncodedContents();
+
+				try
+                {
+                    IDigest digest = DigestUtilities.GetDigest("SHA1");
+
+					digest.Update(0x99);
+                    digest.Update((byte)(kBytes.Length >> 8));
+                    digest.Update((byte)kBytes.Length);
+                    digest.BlockUpdate(kBytes, 0, kBytes.Length);
+                    this.fingerprint = DigestUtilities.DoFinal(digest);
+                }
+                catch (Exception e)
+                {
+                    throw new IOException("can't find SHA1", e);
+                }
+
+				this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
+                    | ((ulong)fingerprint[fingerprint.Length - 7] << 48)
+                    | ((ulong)fingerprint[fingerprint.Length - 6] << 40)
+                    | ((ulong)fingerprint[fingerprint.Length - 5] << 32)
+                    | ((ulong)fingerprint[fingerprint.Length - 4] << 24)
+                    | ((ulong)fingerprint[fingerprint.Length - 3] << 16)
+                    | ((ulong)fingerprint[fingerprint.Length - 2] << 8)
+                    | (ulong)fingerprint[fingerprint.Length - 1]);
+
+				if (key is RsaPublicBcpgKey)
+                {
+                    this.keyStrength = ((RsaPublicBcpgKey)key).Modulus.BitLength;
+                }
+                else if (key is DsaPublicBcpgKey)
+                {
+                    this.keyStrength = ((DsaPublicBcpgKey)key).P.BitLength;
+                }
+                else if (key is ElGamalPublicBcpgKey)
+                {
+                    this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength;
+                }
+            }
+        }
+
+		/// <summary>
+		/// Create a PgpPublicKey from the passed in lightweight one.
+		/// </summary>
+		/// <remarks>
+		/// Note: the time passed in affects the value of the key's keyId, so you probably only want
+		/// to do this once for a lightweight key, or make sure you keep track of the time you used.
+		/// </remarks>
+		/// <param name="algorithm">Asymmetric algorithm type representing the public key.</param>
+		/// <param name="pubKey">Actual public key to associate.</param>
+		/// <param name="time">Date of creation.</param>
+		/// <exception cref="ArgumentException">If <c>pubKey</c> is not public.</exception>
+		/// <exception cref="PgpException">On key creation problem.</exception>
+        public PgpPublicKey(
+            PublicKeyAlgorithmTag	algorithm,
+            AsymmetricKeyParameter	pubKey,
+            DateTime				time)
+        {
+			if (pubKey.IsPrivate)
+				throw new ArgumentException("Expected a public key", "pubKey");
+
+			IBcpgKey bcpgKey;
+            if (pubKey is RsaKeyParameters)
+            {
+                RsaKeyParameters rK = (RsaKeyParameters) pubKey;
+
+				bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent);
+            }
+            else if (pubKey is DsaPublicKeyParameters)
+            {
+                DsaPublicKeyParameters dK = (DsaPublicKeyParameters) pubKey;
+                DsaParameters dP = dK.Parameters;
+
+				bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
+            }
+            else if (pubKey is ElGamalPublicKeyParameters)
+            {
+                ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
+                ElGamalParameters eS = eK.Parameters;
+
+				bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+            }
+            else
+            {
+                throw new PgpException("unknown key class");
+            }
+
+			this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
+            this.ids = Platform.CreateArrayList();
+            this.idSigs = Platform.CreateArrayList();
+
+			try
+            {
+                Init();
+            }
+            catch (IOException e)
+            {
+                throw new PgpException("exception calculating keyId", e);
+            }
+        }
+
+		/// <summary>Constructor for a sub-key.</summary>
+        internal PgpPublicKey(
+            PublicKeyPacket	publicPk,
+            TrustPacket		trustPk,
+            IList           sigs)
+        {
+            this.publicPk = publicPk;
+            this.trustPk = trustPk;
+            this.subSigs = sigs;
+
+			Init();
+        }
+
+		internal PgpPublicKey(
+            PgpPublicKey	key,
+            TrustPacket		trust,
+            IList           subSigs)
+        {
+            this.publicPk = key.publicPk;
+            this.trustPk = trust;
+            this.subSigs = subSigs;
+
+			this.fingerprint = key.fingerprint;
+            this.keyId = key.keyId;
+            this.keyStrength = key.keyStrength;
+        }
+
+		/// <summary>Copy constructor.</summary>
+		/// <param name="pubKey">The public key to copy.</param>
+        internal PgpPublicKey(
+            PgpPublicKey pubKey)
+        {
+            this.publicPk = pubKey.publicPk;
+
+			this.keySigs = Platform.CreateArrayList(pubKey.keySigs);
+            this.ids = Platform.CreateArrayList(pubKey.ids);
+            this.idTrusts = Platform.CreateArrayList(pubKey.idTrusts);
+            this.idSigs = Platform.CreateArrayList(pubKey.idSigs.Count);
+            for (int i = 0; i != pubKey.idSigs.Count; i++)
+            {
+                this.idSigs.Add(Platform.CreateArrayList((IList)pubKey.idSigs[i]));
+            }
+
+			if (pubKey.subSigs != null)
+            {
+                this.subSigs = Platform.CreateArrayList(pubKey.subSigs.Count);
+                for (int i = 0; i != pubKey.subSigs.Count; i++)
+                {
+                    this.subSigs.Add(pubKey.subSigs[i]);
+                }
+            }
+
+			this.fingerprint = pubKey.fingerprint;
+            this.keyId = pubKey.keyId;
+            this.keyStrength = pubKey.keyStrength;
+        }
+
+		internal PgpPublicKey(
+            PublicKeyPacket	publicPk,
+            TrustPacket		trustPk,
+            IList		    keySigs,
+            IList		    ids,
+            IList           idTrusts,
+            IList           idSigs)
+        {
+            this.publicPk = publicPk;
+            this.trustPk = trustPk;
+            this.keySigs = keySigs;
+            this.ids = ids;
+            this.idTrusts = idTrusts;
+            this.idSigs = idSigs;
+
+			Init();
+        }
+
+		internal PgpPublicKey(
+            PublicKeyPacket	publicPk,
+            IList           ids,
+            IList           idSigs)
+        {
+            this.publicPk = publicPk;
+            this.ids = ids;
+            this.idSigs = idSigs;
+            Init();
+        }
+
+		/// <summary>The version of this key.</summary>
+        public int Version
+        {
+			get { return publicPk.Version; }
+        }
+
+		/// <summary>The creation time of this key.</summary>
+		public DateTime CreationTime
+        {
+			get { return publicPk.GetTime(); }
+        }
+
+		/// <summary>The number of valid days from creation time - zero means no expiry.</summary>
+        public int ValidDays
+        {
+			get
+			{
+				if (publicPk.Version > 3)
+				{
+					return (int)(GetValidSeconds() / (24 * 60 * 60));
+				}
+
+				return publicPk.ValidDays;
+			}
+        }
+
+		/// <summary>Return the trust data associated with the public key, if present.</summary>
+		/// <returns>A byte array with trust data, null otherwise.</returns>
+		public byte[] GetTrustData()
+		{
+			if (trustPk == null)
+			{
+				return null;
+			}
+
+			return trustPk.GetLevelAndTrustAmount();
+		}
+
+		/// <summary>The number of valid seconds from creation time - zero means no expiry.</summary>
+		public long GetValidSeconds()
+        {
+			if (publicPk.Version > 3)
+			{
+				if (IsMasterKey)
+				{
+					for (int i = 0; i != MasterKeyCertificationTypes.Length; i++)
+					{
+						long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]);
+
+						if (seconds >= 0)
+						{
+							return seconds;
+						}
+					}
+				}
+				else
+				{
+					long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding);
+
+					if (seconds >= 0)
+					{
+						return seconds;
+					}
+				}
+
+				return 0;
+			}
+
+			return (long) publicPk.ValidDays * 24 * 60 * 60;
+        }
+
+		private long GetExpirationTimeFromSig(
+			bool	selfSigned,
+			int		signatureType)
+		{
+			foreach (PgpSignature sig in GetSignaturesOfType(signatureType))
+			{
+				if (!selfSigned || sig.KeyId == KeyId)
+				{
+					PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets();
+
+					if (hashed != null)
+					{
+						return hashed.GetKeyExpirationTime();
+					}
+
+					return 0;
+				}
+			}
+
+			return -1;
+		}
+
+		/// <summary>The keyId associated with the public key.</summary>
+        public long KeyId
+        {
+            get { return keyId; }
+        }
+
+		/// <summary>The fingerprint of the key</summary>
+        public byte[] GetFingerprint()
+        {
+			return (byte[]) fingerprint.Clone();
+        }
+
+		/// <summary>
+		/// Check if this key has an algorithm type that makes it suitable to use for encryption.
+		/// </summary>
+		/// <remarks>
+		/// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+		/// determining the preferred use of the key.
+		/// </remarks>
+		/// <returns>
+		/// <c>true</c> if this key algorithm is suitable for encryption.
+		/// </returns>
+		public bool IsEncryptionKey
+        {
+            get
+            {
+				switch (publicPk.Algorithm)
+				{
+					case PublicKeyAlgorithmTag.ElGamalEncrypt:
+					case PublicKeyAlgorithmTag.ElGamalGeneral:
+					case PublicKeyAlgorithmTag.RsaEncrypt:
+					case PublicKeyAlgorithmTag.RsaGeneral:
+						return true;
+					default:
+						return false;
+				}
+            }
+        }
+
+		/// <summary>True, if this is a master key.</summary>
+        public bool IsMasterKey
+        {
+            get { return subSigs == null; }
+        }
+
+		/// <summary>The algorithm code associated with the public key.</summary>
+        public PublicKeyAlgorithmTag Algorithm
+        {
+			get { return publicPk.Algorithm; }
+        }
+
+		/// <summary>The strength of the key in bits.</summary>
+        public int BitStrength
+        {
+            get { return keyStrength; }
+        }
+
+		/// <summary>The public key contained in the object.</summary>
+		/// <returns>A lightweight public key.</returns>
+		/// <exception cref="PgpException">If the key algorithm is not recognised.</exception>
+        public AsymmetricKeyParameter GetKey()
+        {
+            try
+            {
+                switch (publicPk.Algorithm)
+                {
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+                    case PublicKeyAlgorithmTag.RsaSign:
+                        RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey) publicPk.Key;
+                        return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
+                    case PublicKeyAlgorithmTag.Dsa:
+                        DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey) publicPk.Key;
+                        return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+                        ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey) publicPk.Key;
+                        return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
+                    default:
+                        throw new PgpException("unknown public key algorithm encountered");
+                }
+            }
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("exception constructing public key", e);
+            }
+        }
+
+		/// <summary>Allows enumeration of any user IDs associated with the key.</summary>
+		/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+        public IEnumerable GetUserIds()
+        {
+            IList temp = Platform.CreateArrayList();
+
+			foreach (object o in ids)
+			{
+				if (o is string)
+				{
+					temp.Add(o);
+                }
+            }
+
+			return new EnumerableProxy(temp);
+        }
+
+		/// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
+		/// <returns>An <c>IEnumerable</c> of <c>PgpUserAttributeSubpacketVector</c> objects.</returns>
+        public IEnumerable GetUserAttributes()
+        {
+            IList temp = Platform.CreateArrayList();
+
+			foreach (object o in ids)
+			{
+				if (o is PgpUserAttributeSubpacketVector)
+				{
+					temp.Add(o);
+				}
+			}
+
+			return new EnumerableProxy(temp);
+        }
+
+		/// <summary>Allows enumeration of any signatures associated with the passed in id.</summary>
+		/// <param name="id">The ID to be matched.</param>
+		/// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+        public IEnumerable GetSignaturesForId(
+            string id)
+        {
+			if (id == null)
+				throw new ArgumentNullException("id");
+
+			for (int i = 0; i != ids.Count; i++)
+            {
+                if (id.Equals(ids[i]))
+                {
+                    return new EnumerableProxy((IList)idSigs[i]);
+                }
+            }
+
+			return null;
+        }
+
+		/// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary>
+		/// <param name="userAttributes">The vector of user attributes to be matched.</param>
+		/// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+        public IEnumerable GetSignaturesForUserAttribute(
+            PgpUserAttributeSubpacketVector userAttributes)
+        {
+            for (int i = 0; i != ids.Count; i++)
+            {
+                if (userAttributes.Equals(ids[i]))
+                {
+                    return new EnumerableProxy((IList) idSigs[i]);
+                }
+            }
+
+			return null;
+        }
+
+		/// <summary>Allows enumeration of signatures of the passed in type that are on this key.</summary>
+		/// <param name="signatureType">The type of the signature to be returned.</param>
+		/// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+        public IEnumerable GetSignaturesOfType(
+            int signatureType)
+        {
+            IList temp = Platform.CreateArrayList();
+
+			foreach (PgpSignature sig in GetSignatures())
+            {
+                if (sig.SignatureType == signatureType)
+                {
+                    temp.Add(sig);
+                }
+            }
+
+			return new EnumerableProxy(temp);
+        }
+
+		/// <summary>Allows enumeration of all signatures/certifications associated with this key.</summary>
+		/// <returns>An <c>IEnumerable</c> with all signatures/certifications.</returns>
+        public IEnumerable GetSignatures()
+        {
+			IList sigs;
+			if (subSigs != null)
+			{
+				sigs = subSigs;
+			}
+			else
+			{
+                sigs = Platform.CreateArrayList(keySigs);
+
+				foreach (ICollection extraSigs in idSigs)
+				{
+                    CollectionUtilities.AddRange(sigs, extraSigs);
+				}
+			}
+
+			return new EnumerableProxy(sigs);
+        }
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+            Encode(bOut);
+            return bOut.ToArray();
+        }
+
+		public void Encode(
+            Stream outStr)
+        {
+            BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+			bcpgOut.WritePacket(publicPk);
+            if (trustPk != null)
+            {
+                bcpgOut.WritePacket(trustPk);
+            }
+
+			if (subSigs == null)    // not a sub-key
+            {
+				foreach (PgpSignature keySig in keySigs)
+				{
+					keySig.Encode(bcpgOut);
+				}
+
+				for (int i = 0; i != ids.Count; i++)
+                {
+                    if (ids[i] is string)
+                    {
+                        string id = (string) ids[i];
+
+						bcpgOut.WritePacket(new UserIdPacket(id));
+                    }
+                    else
+                    {
+                        PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector)ids[i];
+                        bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray()));
+                    }
+
+					if (idTrusts[i] != null)
+                    {
+                        bcpgOut.WritePacket((ContainedPacket)idTrusts[i]);
+                    }
+
+					foreach (PgpSignature sig in (IList) idSigs[i])
+					{
+						sig.Encode(bcpgOut);
+					}
+                }
+            }
+            else
+            {
+				foreach (PgpSignature subSig in subSigs)
+				{
+					subSig.Encode(bcpgOut);
+				}
+            }
+        }
+
+		/// <summary>Check whether this (sub)key has a revocation signature on it.</summary>
+		/// <returns>True, if this (sub)key has been revoked.</returns>
+        public bool IsRevoked()
+        {
+            int ns = 0;
+            bool revoked = false;
+            if (IsMasterKey)	// Master key
+            {
+                while (!revoked && (ns < keySigs.Count))
+                {
+                    if (((PgpSignature)keySigs[ns++]).SignatureType == PgpSignature.KeyRevocation)
+                    {
+                        revoked = true;
+                    }
+                }
+            }
+            else	// Sub-key
+            {
+                while (!revoked && (ns < subSigs.Count))
+                {
+                    if (((PgpSignature)subSigs[ns++]).SignatureType == PgpSignature.SubkeyRevocation)
+                    {
+                        revoked = true;
+                    }
+                }
+            }
+            return revoked;
+        }
+
+		/// <summary>Add a certification for an id to the given public key.</summary>
+		/// <param name="key">The key the certification is to be added to.</param>
+		/// <param name="id">The ID the certification is associated with.</param>
+		/// <param name="certification">The new certification.</param>
+		/// <returns>The re-certified key.</returns>
+        public static PgpPublicKey AddCertification(
+            PgpPublicKey	key,
+            string			id,
+            PgpSignature	certification)
+        {
+			return AddCert(key, id, certification);
+		}
+
+		/// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary>
+		/// <param name="key">The key the certification is to be added to.</param>
+		/// <param name="userAttributes">The attributes the certification is associated with.</param>
+		/// <param name="certification">The new certification.</param>
+		/// <returns>The re-certified key.</returns>
+		public static PgpPublicKey AddCertification(
+			PgpPublicKey					key,
+			PgpUserAttributeSubpacketVector	userAttributes,
+			PgpSignature					certification)
+		{
+			return AddCert(key, userAttributes, certification);
+		}
+
+		private static PgpPublicKey AddCert(
+			PgpPublicKey	key,
+			object			id,
+			PgpSignature	certification)
+		{
+			PgpPublicKey returnKey = new PgpPublicKey(key);
+			IList sigList = null;
+
+			for (int i = 0; i != returnKey.ids.Count; i++)
+			{
+				if (id.Equals(returnKey.ids[i]))
+				{
+					sigList = (IList) returnKey.idSigs[i];
+				}
+			}
+
+			if (sigList != null)
+			{
+				sigList.Add(certification);
+			}
+			else
+			{
+				sigList = Platform.CreateArrayList();
+				sigList.Add(certification);
+				returnKey.ids.Add(id);
+				returnKey.idTrusts.Add(null);
+				returnKey.idSigs.Add(sigList);
+			}
+
+			return returnKey;
+		}
+
+		/// <summary>
+		/// Remove any certifications associated with a user attribute subpacket on a key.
+		/// </summary>
+		/// <param name="key">The key the certifications are to be removed from.</param>
+		/// <param name="userAttributes">The attributes to be removed.</param>
+		/// <returns>
+		/// The re-certified key, or null if the user attribute subpacket was not found on the key.
+		/// </returns>
+		public static PgpPublicKey RemoveCertification(
+			PgpPublicKey					key,
+			PgpUserAttributeSubpacketVector	userAttributes)
+		{
+			return RemoveCert(key, userAttributes);
+		}
+
+		/// <summary>Remove any certifications associated with a given ID on a key.</summary>
+		/// <param name="key">The key the certifications are to be removed from.</param>
+		/// <param name="id">The ID that is to be removed.</param>
+		/// <returns>The re-certified key, or null if the ID was not found on the key.</returns>
+        public static PgpPublicKey RemoveCertification(
+            PgpPublicKey	key,
+            string			id)
+        {
+			return RemoveCert(key, id);
+		}
+
+		private static PgpPublicKey RemoveCert(
+			PgpPublicKey	key,
+			object			id)
+		{
+			PgpPublicKey returnKey = new PgpPublicKey(key);
+            bool found = false;
+
+			for (int i = 0; i < returnKey.ids.Count; i++)
+            {
+                if (id.Equals(returnKey.ids[i]))
+                {
+                    found = true;
+                    returnKey.ids.RemoveAt(i);
+                    returnKey.idTrusts.RemoveAt(i);
+                    returnKey.idSigs.RemoveAt(i);
+                }
+            }
+
+			return found ? returnKey : null;
+        }
+
+		/// <summary>Remove a certification associated with a given ID on a key.</summary>
+		/// <param name="key">The key the certifications are to be removed from.</param>
+		/// <param name="id">The ID that the certfication is to be removed from.</param>
+		/// <param name="certification">The certfication to be removed.</param>
+		/// <returns>The re-certified key, or null if the certification was not found.</returns>
+        public static PgpPublicKey RemoveCertification(
+            PgpPublicKey	key,
+            string			id,
+            PgpSignature	certification)
+        {
+			return RemoveCert(key, id, certification);
+		}
+
+		/// <summary>Remove a certification associated with a given user attributes on a key.</summary>
+		/// <param name="key">The key the certifications are to be removed from.</param>
+		/// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param>
+		/// <param name="certification">The certification to be removed.</param>
+		/// <returns>The re-certified key, or null if the certification was not found.</returns>
+		public static PgpPublicKey RemoveCertification(
+			PgpPublicKey					key,
+			PgpUserAttributeSubpacketVector	userAttributes,
+			PgpSignature					certification)
+		{
+			return RemoveCert(key, userAttributes, certification);
+		}
+
+		private static PgpPublicKey RemoveCert(
+			PgpPublicKey	key,
+			object			id,
+			PgpSignature	certification)
+		{
+			PgpPublicKey returnKey = new PgpPublicKey(key);
+            bool found = false;
+
+			for (int i = 0; i < returnKey.ids.Count; i++)
+            {
+                if (id.Equals(returnKey.ids[i]))
+                {
+                    IList certs = (IList) returnKey.idSigs[i];
+                    found = certs.Contains(certification);
+
+					if (found)
+					{
+						certs.Remove(certification);
+					}
+                }
+            }
+
+			return found ? returnKey : null;
+        }
+
+		/// <summary>Add a revocation or some other key certification to a key.</summary>
+		/// <param name="key">The key the revocation is to be added to.</param>
+		/// <param name="certification">The key signature to be added.</param>
+		/// <returns>The new changed public key object.</returns>
+        public static PgpPublicKey AddCertification(
+            PgpPublicKey	key,
+            PgpSignature	certification)
+        {
+            if (key.IsMasterKey)
+            {
+                if (certification.SignatureType == PgpSignature.SubkeyRevocation)
+                {
+                    throw new ArgumentException("signature type incorrect for master key revocation.");
+                }
+            }
+            else
+            {
+                if (certification.SignatureType == PgpSignature.KeyRevocation)
+                {
+                    throw new ArgumentException("signature type incorrect for sub-key revocation.");
+                }
+            }
+
+			PgpPublicKey returnKey = new PgpPublicKey(key);
+
+			if (returnKey.subSigs != null)
+            {
+                returnKey.subSigs.Add(certification);
+            }
+            else
+            {
+                returnKey.keySigs.Add(certification);
+            }
+
+			return returnKey;
+        }
+
+		/// <summary>Remove a certification from the key.</summary>
+		/// <param name="key">The key the certifications are to be removed from.</param>
+		/// <param name="certification">The certfication to be removed.</param>
+		/// <returns>The modified key, null if the certification was not found.</returns>
+		public static PgpPublicKey RemoveCertification(
+			PgpPublicKey	key,
+			PgpSignature	certification)
+		{
+			PgpPublicKey returnKey = new PgpPublicKey(key);
+			IList sigs = returnKey.subSigs != null
+				?	returnKey.subSigs
+				:	returnKey.keySigs;
+
+//			bool found = sigs.Remove(certification);
+			int pos = sigs.IndexOf(certification);
+			bool found = pos >= 0;
+
+			if (found)
+			{
+				sigs.RemoveAt(pos);
+			}
+			else
+			{
+				foreach (String id in key.GetUserIds())
+				{
+					foreach (object sig in key.GetSignaturesForId(id))
+					{
+						// TODO Is this the right type of equality test?
+						if (certification == sig)
+						{
+							found = true;
+							returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
+						}
+					}
+				}
+
+				if (!found)
+				{
+					foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
+					{
+						foreach (object sig in key.GetSignaturesForUserAttribute(id))
+						{
+							// TODO Is this the right type of equality test?
+							if (certification == sig)
+							{
+								found = true;
+								returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
+							}
+						}
+					}
+				}
+			}
+
+			return returnKey;
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
new file mode 100644
index 000000000..b6504cbcd
--- /dev/null
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -0,0 +1,252 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>A public key encrypted data object.</remarks>
+    public class PgpPublicKeyEncryptedData
+        : PgpEncryptedData
+    {
+        private PublicKeyEncSessionPacket keyData;
+
+		internal PgpPublicKeyEncryptedData(
+            PublicKeyEncSessionPacket	keyData,
+            InputStreamPacket			encData)
+            : base(encData)
+        {
+            this.keyData = keyData;
+        }
+
+		private static IBufferedCipher GetKeyCipher(
+            PublicKeyAlgorithmTag algorithm)
+        {
+            try
+            {
+                switch (algorithm)
+                {
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+                        return CipherUtilities.GetCipher("RSA//PKCS1Padding");
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+                        return CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+                    default:
+                        throw new PgpException("unknown asymmetric algorithm: " + algorithm);
+                }
+            }
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception creating cipher", e);
+            }
+        }
+
+		private bool ConfirmCheckSum(
+            byte[] sessionInfo)
+        {
+            int check = 0;
+
+			for (int i = 1; i != sessionInfo.Length - 2; i++)
+            {
+                check += sessionInfo[i] & 0xff;
+            }
+
+			return (sessionInfo[sessionInfo.Length - 2] == (byte)(check >> 8))
+                && (sessionInfo[sessionInfo.Length - 1] == (byte)(check));
+        }
+
+		/// <summary>The key ID for the key used to encrypt the data.</summary>
+        public long KeyId
+        {
+			get { return keyData.KeyId; }
+        }
+
+		/// <summary>
+		/// Return the algorithm code for the symmetric algorithm used to encrypt the data.
+		/// </summary>
+		public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
+			PgpPrivateKey privKey)
+		{
+			byte[] plain = fetchSymmetricKeyData(privKey);
+
+			return (SymmetricKeyAlgorithmTag) plain[0];
+		}
+
+		/// <summary>Return the decrypted data stream for the packet.</summary>
+        public Stream GetDataStream(
+            PgpPrivateKey privKey)
+        {
+			byte[] plain = fetchSymmetricKeyData(privKey);
+
+			IBufferedCipher c2;
+			string cipherName = PgpUtilities.GetSymmetricCipherName((SymmetricKeyAlgorithmTag) plain[0]);
+			string cName = cipherName;
+
+			try
+            {
+                if (encData is SymmetricEncIntegrityPacket)
+                {
+					cName += "/CFB/NoPadding";
+                }
+                else
+                {
+					cName += "/OpenPGPCFB/NoPadding";
+                }
+
+				c2 = CipherUtilities.GetCipher(cName);
+			}
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("exception creating cipher", e);
+            }
+
+			if (c2 == null)
+				return encData.GetInputStream();
+
+			try
+            {
+				KeyParameter key = ParameterUtilities.CreateKeyParameter(
+					cipherName, plain, 1, plain.Length - 3);
+
+				byte[] iv = new byte[c2.GetBlockSize()];
+
+				c2.Init(false, new ParametersWithIV(key, iv));
+
+                encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c2, null));
+
+				if (encData is SymmetricEncIntegrityPacket)
+                {
+                    truncStream = new TruncatedStream(encStream);
+
+					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
+					IDigest digest = DigestUtilities.GetDigest(digestName);
+
+					encStream = new DigestStream(truncStream, digest, null);
+                }
+
+				if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
+					throw new EndOfStreamException("unexpected end of stream.");
+
+				int v1 = encStream.ReadByte();
+                int v2 = encStream.ReadByte();
+
+				if (v1 < 0 || v2 < 0)
+                    throw new EndOfStreamException("unexpected end of stream.");
+
+				// Note: the oracle attack on the "quick check" bytes is deemed
+				// a security risk for typical public key encryption usages,
+				// therefore we do not perform the check.
+
+//				bool repeatCheckPassed =
+//					iv[iv.Length - 2] == (byte)v1
+//					&&	iv[iv.Length - 1] == (byte)v2;
+//
+//				// Note: some versions of PGP appear to produce 0 for the extra
+//				// bytes rather than repeating the two previous bytes
+//				bool zeroesCheckPassed =
+//					v1 == 0
+//					&&	v2 == 0;
+//
+//				if (!repeatCheckPassed && !zeroesCheckPassed)
+//				{
+//					throw new PgpDataValidationException("quick check failed.");
+//				}
+
+				return encStream;
+            }
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception starting decryption", e);
+            }
+		}
+
+		private byte[] fetchSymmetricKeyData(
+			PgpPrivateKey privKey)
+		{
+			IBufferedCipher c1 = GetKeyCipher(keyData.Algorithm);
+
+			try
+			{
+				c1.Init(false, privKey.Key);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new PgpException("error setting asymmetric cipher", e);
+			}
+
+			BigInteger[] keyD = keyData.GetEncSessionKey();
+
+			if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
+				|| keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
+			{
+				c1.ProcessBytes(keyD[0].ToByteArrayUnsigned());
+			}
+			else
+			{
+				ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
+				int size = (k.Parameters.P.BitLength + 7) / 8;
+
+				byte[] bi = keyD[0].ToByteArray();
+
+				int diff = bi.Length - size;
+				if (diff >= 0)
+				{
+					c1.ProcessBytes(bi, diff, size);
+				}
+				else
+				{
+					byte[] zeros = new byte[-diff];
+					c1.ProcessBytes(zeros);
+					c1.ProcessBytes(bi);
+				}
+
+				bi = keyD[1].ToByteArray();
+
+				diff = bi.Length - size;
+				if (diff >= 0)
+				{
+					c1.ProcessBytes(bi, diff, size);
+				}
+				else
+				{
+					byte[] zeros = new byte[-diff];
+					c1.ProcessBytes(zeros);
+					c1.ProcessBytes(bi);
+				}
+			}
+
+			byte[] plain;
+			try
+			{
+				plain = c1.DoFinal();
+			}
+			catch (Exception e)
+			{
+				throw new PgpException("exception decrypting secret key", e);
+			}
+
+			if (!ConfirmCheckSum(plain))
+				throw new PgpKeyValidationException("key checksum failed");
+
+			return plain;
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs
new file mode 100644
index 000000000..ecb935e4b
--- /dev/null
+++ b/crypto/src/openpgp/PgpPublicKeyRing.cs
@@ -0,0 +1,200 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Class to hold a single master public key and its subkeys.
+	/// <p>
+	/// Often PGP keyring files consist of multiple master keys, if you are trying to process
+	/// or construct one of these you should use the <c>PgpPublicKeyRingBundle</c> class.
+	/// </p>
+	/// </remarks>
+	public class PgpPublicKeyRing
+		: PgpKeyRing
+    {
+        private readonly IList keys;
+
+		public PgpPublicKeyRing(
+            byte[] encoding)
+            : this(new MemoryStream(encoding, false))
+        {
+        }
+
+		internal PgpPublicKeyRing(
+            IList pubKeys)
+        {
+            this.keys = pubKeys;
+        }
+
+		public PgpPublicKeyRing(
+            Stream inputStream)
+        {
+			this.keys = Platform.CreateArrayList();
+
+            BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream);
+
+			PacketTag initialTag = bcpgInput.NextPacketTag();
+            if (initialTag != PacketTag.PublicKey && initialTag != PacketTag.PublicSubkey)
+            {
+                throw new IOException("public key ring doesn't start with public key tag: "
+					+ "tag 0x" + ((int)initialTag).ToString("X"));
+            }
+
+			PublicKeyPacket pubPk = (PublicKeyPacket) bcpgInput.ReadPacket();;
+			TrustPacket trustPk = ReadOptionalTrustPacket(bcpgInput);
+
+            // direct signatures and revocations
+			IList keySigs = ReadSignaturesAndTrust(bcpgInput);
+
+			IList ids, idTrusts, idSigs;
+			ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+
+			keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs));
+
+
+			// Read subkeys
+			while (bcpgInput.NextPacketTag() == PacketTag.PublicSubkey)
+            {
+				keys.Add(ReadSubkey(bcpgInput));
+            }
+        }
+
+		/// <summary>Return the first public key in the ring.</summary>
+        public PgpPublicKey GetPublicKey()
+        {
+            return (PgpPublicKey) keys[0];
+        }
+
+		/// <summary>Return the public key referred to by the passed in key ID if it is present.</summary>
+        public PgpPublicKey GetPublicKey(
+            long keyId)
+        {
+			foreach (PgpPublicKey k in keys)
+			{
+				if (keyId == k.KeyId)
+                {
+                    return k;
+                }
+            }
+
+			return null;
+        }
+
+		/// <summary>Allows enumeration of all the public keys.</summary>
+		/// <returns>An <c>IEnumerable</c> of <c>PgpPublicKey</c> objects.</returns>
+        public IEnumerable GetPublicKeys()
+        {
+            return new EnumerableProxy(keys);
+        }
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+
+			Encode(bOut);
+
+			return bOut.ToArray();
+        }
+
+		public void Encode(
+            Stream outStr)
+        {
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+
+			foreach (PgpPublicKey k in keys)
+			{
+				k.Encode(outStr);
+            }
+        }
+
+		/// <summary>
+		/// Returns a new key ring with the public key passed in either added or
+		/// replacing an existing one.
+		/// </summary>
+		/// <param name="pubRing">The public key ring to be modified.</param>
+		/// <param name="pubKey">The public key to be inserted.</param>
+		/// <returns>A new <c>PgpPublicKeyRing</c></returns>
+        public static PgpPublicKeyRing InsertPublicKey(
+            PgpPublicKeyRing	pubRing,
+            PgpPublicKey		pubKey)
+        {
+            IList keys = Platform.CreateArrayList(pubRing.keys);
+            bool found = false;
+			bool masterFound = false;
+
+			for (int i = 0; i != keys.Count; i++)
+            {
+                PgpPublicKey key = (PgpPublicKey) keys[i];
+
+				if (key.KeyId == pubKey.KeyId)
+                {
+                    found = true;
+                    keys[i] = pubKey;
+                }
+				if (key.IsMasterKey)
+				{
+					masterFound = true;
+				}
+			}
+
+			if (!found)
+            {
+				if (pubKey.IsMasterKey)
+				{
+					if (masterFound)
+						throw new ArgumentException("cannot add a master key to a ring that already has one");
+
+					keys.Insert(0, pubKey);
+				}
+				else
+				{
+					keys.Add(pubKey);
+				}
+			}
+
+			return new PgpPublicKeyRing(keys);
+        }
+
+		/// <summary>Returns a new key ring with the public key passed in removed from the key ring.</summary>
+		/// <param name="pubRing">The public key ring to be modified.</param>
+		/// <param name="pubKey">The public key to be removed.</param>
+		/// <returns>A new <c>PgpPublicKeyRing</c>, or null if pubKey is not found.</returns>
+        public static PgpPublicKeyRing RemovePublicKey(
+            PgpPublicKeyRing	pubRing,
+            PgpPublicKey		pubKey)
+        {
+            IList keys = Platform.CreateArrayList(pubRing.keys);
+            bool found = false;
+
+			for (int i = 0; i < keys.Count; i++)
+            {
+                PgpPublicKey key = (PgpPublicKey) keys[i];
+
+				if (key.KeyId == pubKey.KeyId)
+                {
+                    found = true;
+                    keys.RemoveAt(i);
+                }
+            }
+
+			return found ? new PgpPublicKeyRing(keys) : null;
+        }
+
+		internal static PgpPublicKey ReadSubkey(BcpgInputStream bcpgInput)
+		{
+            PublicKeyPacket	pk = (PublicKeyPacket) bcpgInput.ReadPacket();
+			TrustPacket kTrust = ReadOptionalTrustPacket(bcpgInput);
+
+			// PGP 8 actually leaves out the signature.
+			IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+			return new PgpPublicKey(pk, kTrust, sigList);
+		}
+    }
+}
diff --git a/crypto/src/openpgp/PgpPublicKeyRingBundle.cs b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs
new file mode 100644
index 000000000..77cede4a1
--- /dev/null
+++ b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Often a PGP key ring file is made up of a succession of master/sub-key key rings.
+	/// If you want to read an entire public key file in one hit this is the class for you.
+	/// </remarks>
+    public class PgpPublicKeyRingBundle
+    {
+        private readonly IDictionary pubRings;
+        private readonly IList order;
+
+		private PgpPublicKeyRingBundle(
+            IDictionary	pubRings,
+            IList       order)
+        {
+            this.pubRings = pubRings;
+            this.order = order;
+        }
+
+		public PgpPublicKeyRingBundle(
+            byte[] encoding)
+            : this(new MemoryStream(encoding, false))
+        {
+        }
+
+		/// <summary>Build a PgpPublicKeyRingBundle from the passed in input stream.</summary>
+		/// <param name="inputStream">Input stream containing data.</param>
+		/// <exception cref="IOException">If a problem parsing the stream occurs.</exception>
+		/// <exception cref="PgpException">If an object is encountered which isn't a PgpPublicKeyRing.</exception>
+		public PgpPublicKeyRingBundle(
+            Stream inputStream)
+			: this(new PgpObjectFactory(inputStream).AllPgpObjects())
+		{
+        }
+
+		public PgpPublicKeyRingBundle(
+            IEnumerable e)
+        {
+			this.pubRings = Platform.CreateHashtable();
+			this.order = Platform.CreateArrayList();
+
+			foreach (object obj in e)
+            {
+				PgpPublicKeyRing pgpPub = obj as PgpPublicKeyRing;
+
+				if (pgpPub == null)
+				{
+					throw new PgpException(obj.GetType().FullName + " found where PgpPublicKeyRing expected");
+				}
+
+				long key = pgpPub.GetPublicKey().KeyId;
+                pubRings.Add(key, pgpPub);
+				order.Add(key);
+            }
+        }
+
+		[Obsolete("Use 'Count' property instead")]
+		public int Size
+		{
+			get { return order.Count; }
+		}
+
+		/// <summary>Return the number of key rings in this collection.</summary>
+        public int Count
+        {
+			get { return order.Count; }
+        }
+
+		/// <summary>Allow enumeration of the public key rings making up this collection.</summary>
+        public IEnumerable GetKeyRings()
+        {
+			return new EnumerableProxy(pubRings.Values);
+        }
+
+		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+		/// <param name="userId">The user ID to be matched.</param>
+		/// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+		public IEnumerable GetKeyRings(
+			string userId)
+		{
+			return GetKeyRings(userId, false, false);
+		}
+
+		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+		/// <param name="userId">The user ID to be matched.</param>
+		/// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+		/// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+        public IEnumerable GetKeyRings(
+            string	userId,
+            bool	matchPartial)
+        {
+			return GetKeyRings(userId, matchPartial, false);
+        }
+
+		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+		/// <param name="userId">The user ID to be matched.</param>
+		/// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+		/// <param name="ignoreCase">If true, case is ignored in user ID comparisons.</param>
+		/// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+		public IEnumerable GetKeyRings(
+			string	userId,
+			bool	matchPartial,
+			bool	ignoreCase)
+		{
+			IList rings = Platform.CreateArrayList();
+
+			if (ignoreCase)
+			{
+				userId = userId.ToLowerInvariant();
+			}
+
+			foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+			{
+				foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds())
+				{
+					string next = nextUserID;
+					if (ignoreCase)
+					{
+						next = next.ToLowerInvariant();
+					}
+
+					if (matchPartial)
+					{
+						if (next.IndexOf(userId) > -1)
+						{
+							rings.Add(pubRing);
+						}
+					}
+					else
+					{
+						if (next.Equals(userId))
+						{
+							rings.Add(pubRing);
+						}
+					}
+				}
+			}
+
+			return new EnumerableProxy(rings);
+		}
+
+		/// <summary>Return the PGP public key associated with the given key id.</summary>
+		/// <param name="keyId">The ID of the public key to return.</param>
+        public PgpPublicKey GetPublicKey(
+            long keyId)
+        {
+            foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+            {
+                PgpPublicKey pub = pubRing.GetPublicKey(keyId);
+
+				if (pub != null)
+                {
+                    return pub;
+                }
+            }
+
+			return null;
+        }
+
+		/// <summary>Return the public key ring which contains the key referred to by keyId</summary>
+		/// <param name="keyId">key ID to match against</param>
+        public PgpPublicKeyRing GetPublicKeyRing(
+            long keyId)
+        {
+            if (pubRings.Contains(keyId))
+            {
+                return (PgpPublicKeyRing)pubRings[keyId];
+            }
+
+			foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+            {
+                PgpPublicKey pub = pubRing.GetPublicKey(keyId);
+
+                if (pub != null)
+                {
+                    return pubRing;
+                }
+            }
+
+			return null;
+        }
+
+		/// <summary>
+		/// Return true if a key matching the passed in key ID is present, false otherwise.
+		/// </summary>
+		/// <param name="keyID">key ID to look for.</param>
+		public bool Contains(
+			long keyID)
+		{
+			return GetPublicKey(keyID) != null;
+		}
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+
+			Encode(bOut);
+
+			return bOut.ToArray();
+        }
+
+		public void Encode(
+            Stream outStr)
+        {
+			BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+			foreach (long key in order)
+            {
+                PgpPublicKeyRing sec = (PgpPublicKeyRing) pubRings[key];
+
+				sec.Encode(bcpgOut);
+            }
+        }
+
+		/// <summary>
+		/// Return a new bundle containing the contents of the passed in bundle and
+		/// the passed in public key ring.
+		/// </summary>
+		/// <param name="bundle">The <c>PgpPublicKeyRingBundle</c> the key ring is to be added to.</param>
+		/// <param name="publicKeyRing">The key ring to be added.</param>
+		/// <returns>A new <c>PgpPublicKeyRingBundle</c> merging the current one with the passed in key ring.</returns>
+		/// <exception cref="ArgumentException">If the keyId for the passed in key ring is already present.</exception>
+        public static PgpPublicKeyRingBundle AddPublicKeyRing(
+            PgpPublicKeyRingBundle  bundle,
+            PgpPublicKeyRing        publicKeyRing)
+        {
+            long key = publicKeyRing.GetPublicKey().KeyId;
+
+			if (bundle.pubRings.Contains(key))
+            {
+                throw new ArgumentException("Bundle already contains a key with a keyId for the passed in ring.");
+            }
+
+			IDictionary newPubRings = Platform.CreateHashtable(bundle.pubRings);
+            IList newOrder = Platform.CreateArrayList(bundle.order);
+
+			newPubRings[key] = publicKeyRing;
+
+			newOrder.Add(key);
+
+			return new PgpPublicKeyRingBundle(newPubRings, newOrder);
+        }
+
+		/// <summary>
+		/// Return a new bundle containing the contents of the passed in bundle with
+		/// the passed in public key ring removed.
+		/// </summary>
+		/// <param name="bundle">The <c>PgpPublicKeyRingBundle</c> the key ring is to be removed from.</param>
+		/// <param name="publicKeyRing">The key ring to be removed.</param>
+		/// <returns>A new <c>PgpPublicKeyRingBundle</c> not containing the passed in key ring.</returns>
+		/// <exception cref="ArgumentException">If the keyId for the passed in key ring is not present.</exception>
+        public static PgpPublicKeyRingBundle RemovePublicKeyRing(
+            PgpPublicKeyRingBundle	bundle,
+            PgpPublicKeyRing		publicKeyRing)
+        {
+            long key = publicKeyRing.GetPublicKey().KeyId;
+
+			if (!bundle.pubRings.Contains(key))
+            {
+                throw new ArgumentException("Bundle does not contain a key with a keyId for the passed in ring.");
+            }
+
+			IDictionary newPubRings = Platform.CreateHashtable(bundle.pubRings);
+            IList newOrder = Platform.CreateArrayList(bundle.order);
+
+			newPubRings.Remove(key);
+			newOrder.Remove(key);
+
+			return new PgpPublicKeyRingBundle(newPubRings, newOrder);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
new file mode 100644
index 000000000..9d87f49c8
--- /dev/null
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -0,0 +1,666 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>General class to handle a PGP secret key object.</remarks>
+    public class PgpSecretKey
+    {
+        private readonly SecretKeyPacket	secret;
+        private readonly PgpPublicKey		pub;
+
+		internal PgpSecretKey(
+			SecretKeyPacket	secret,
+			PgpPublicKey	pub)
+		{
+			this.secret = secret;
+			this.pub = pub;
+		}
+
+		internal PgpSecretKey(
+			PgpPrivateKey				privKey,
+			PgpPublicKey				pubKey,
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+			char[]						passPhrase,
+			bool						useSha1,
+			SecureRandom				rand)
+			: this(privKey, pubKey, encAlgorithm, passPhrase, useSha1, rand, false)
+		{
+		}
+
+		internal PgpSecretKey(
+			PgpPrivateKey				privKey,
+			PgpPublicKey				pubKey,
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+            char[]						passPhrase,
+			bool						useSha1,
+			SecureRandom				rand,
+			bool						isMasterKey)
+        {
+			BcpgObject secKey;
+
+			this.pub = pubKey;
+
+			switch (pubKey.Algorithm)
+            {
+				case PublicKeyAlgorithmTag.RsaEncrypt:
+				case PublicKeyAlgorithmTag.RsaSign:
+				case PublicKeyAlgorithmTag.RsaGeneral:
+					RsaPrivateCrtKeyParameters rsK = (RsaPrivateCrtKeyParameters) privKey.Key;
+					secKey = new RsaSecretBcpgKey(rsK.Exponent, rsK.P, rsK.Q);
+					break;
+				case PublicKeyAlgorithmTag.Dsa:
+					DsaPrivateKeyParameters dsK = (DsaPrivateKeyParameters) privKey.Key;
+					secKey = new DsaSecretBcpgKey(dsK.X);
+					break;
+				case PublicKeyAlgorithmTag.ElGamalEncrypt:
+				case PublicKeyAlgorithmTag.ElGamalGeneral:
+					ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
+					secKey = new ElGamalSecretBcpgKey(esK.X);
+					break;
+				default:
+					throw new PgpException("unknown key class");
+            }
+
+			try
+            {
+                MemoryStream bOut = new MemoryStream();
+                BcpgOutputStream pOut = new BcpgOutputStream(bOut);
+
+				pOut.WriteObject(secKey);
+
+				byte[] keyData = bOut.ToArray();
+				byte[] checksumBytes = Checksum(useSha1, keyData, keyData.Length);
+
+				pOut.Write(checksumBytes);
+
+				byte[] bOutData = bOut.ToArray();
+
+				if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
+				{
+					if (isMasterKey)
+					{
+						this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
+					}
+					else
+					{
+						this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
+					}
+				}
+				else
+                {
+					S2k s2k;
+					byte[] iv;
+					byte[] encData = EncryptKeyData(bOutData, encAlgorithm, passPhrase, rand, out s2k, out iv);
+
+					int s2kUsage = useSha1
+						?	SecretKeyPacket.UsageSha1
+						:	SecretKeyPacket.UsageChecksum;
+
+					if (isMasterKey)
+					{
+						this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+					}
+					else
+					{
+						this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+					}
+				}
+            }
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception encrypting key", e);
+            }
+        }
+
+		public PgpSecretKey(
+            int							certificationLevel,
+            PgpKeyPair					keyPair,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            char[]						passPhrase,
+            PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+			: this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand)
+		{
+		}
+
+		public PgpSecretKey(
+			int							certificationLevel,
+			PgpKeyPair					keyPair,
+			string						id,
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+			char[]						passPhrase,
+			bool						useSha1,
+			PgpSignatureSubpacketVector	hashedPackets,
+			PgpSignatureSubpacketVector	unhashedPackets,
+			SecureRandom				rand)
+			: this(keyPair.PrivateKey, certifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true)
+		{
+		}
+
+		private static PgpPublicKey certifiedPublicKey(
+			int							certificationLevel,
+			PgpKeyPair					keyPair,
+			string						id,
+			PgpSignatureSubpacketVector	hashedPackets,
+			PgpSignatureSubpacketVector	unhashedPackets)
+		{
+			PgpSignatureGenerator sGen;
+			try
+			{
+				sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
+			}
+			catch (Exception e)
+			{
+				throw new PgpException("Creating signature generator: " + e.Message, e);
+			}
+
+			//
+			// Generate the certification
+			//
+			sGen.InitSign(certificationLevel, keyPair.PrivateKey);
+
+			sGen.SetHashedSubpackets(hashedPackets);
+			sGen.SetUnhashedSubpackets(unhashedPackets);
+
+			try
+            {
+				PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey);
+                return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification);
+            }
+            catch (Exception e)
+            {
+				throw new PgpException("Exception doing certification: " + e.Message, e);
+			}
+        }
+
+		public PgpSecretKey(
+            int							certificationLevel,
+            PublicKeyAlgorithmTag		algorithm,
+            AsymmetricKeyParameter		pubKey,
+            AsymmetricKeyParameter		privKey,
+            DateTime					time,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            char[]						passPhrase,
+            PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+            : this(certificationLevel,
+                new PgpKeyPair(algorithm, pubKey, privKey, time),
+                id, encAlgorithm, passPhrase, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+		public PgpSecretKey(
+			int							certificationLevel,
+			PublicKeyAlgorithmTag		algorithm,
+			AsymmetricKeyParameter		pubKey,
+			AsymmetricKeyParameter		privKey,
+			DateTime					time,
+			string						id,
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+			char[]						passPhrase,
+			bool						useSha1,
+			PgpSignatureSubpacketVector	hashedPackets,
+			PgpSignatureSubpacketVector	unhashedPackets,
+			SecureRandom				rand)
+			: this(certificationLevel, new PgpKeyPair(algorithm, pubKey, privKey, time), id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
+		{
+		}
+
+		/// <summary>
+		/// Check if this key has an algorithm type that makes it suitable to use for signing.
+		/// </summary>
+		/// <remarks>
+		/// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+		/// determining the preferred use of the key.
+		/// </remarks>
+		/// <returns>
+		/// <c>true</c> if this key algorithm is suitable for use with signing.
+		/// </returns>
+		public bool IsSigningKey
+        {
+			get
+			{
+				switch (pub.Algorithm)
+				{
+					case PublicKeyAlgorithmTag.RsaGeneral:
+					case PublicKeyAlgorithmTag.RsaSign:
+					case PublicKeyAlgorithmTag.Dsa:
+					case PublicKeyAlgorithmTag.ECDsa:
+					case PublicKeyAlgorithmTag.ElGamalGeneral:
+						return true;
+					default:
+						return false;
+				}
+			}
+        }
+
+		/// <summary>True, if this is a master key.</summary>
+        public bool IsMasterKey
+		{
+			get { return pub.IsMasterKey; }
+        }
+
+		/// <summary>The algorithm the key is encrypted with.</summary>
+        public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm
+        {
+			get { return secret.EncAlgorithm; }
+        }
+
+		/// <summary>The key ID of the public key associated with this key.</summary>
+        public long KeyId
+        {
+            get { return pub.KeyId; }
+        }
+
+		/// <summary>The public key associated with this key.</summary>
+        public PgpPublicKey PublicKey
+        {
+			get { return pub; }
+        }
+
+		/// <summary>Allows enumeration of any user IDs associated with the key.</summary>
+		/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+        public IEnumerable UserIds
+        {
+			get { return pub.GetUserIds(); }
+        }
+
+		/// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
+		/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+        public IEnumerable UserAttributes
+        {
+			get { return pub.GetUserAttributes(); }
+        }
+
+		private byte[] ExtractKeyData(
+            char[] passPhrase)
+        {
+            SymmetricKeyAlgorithmTag alg = secret.EncAlgorithm;
+			byte[] encData = secret.GetSecretKeyData();
+
+			if (alg == SymmetricKeyAlgorithmTag.Null)
+				return encData;
+
+			byte[] data;
+			IBufferedCipher c = null;
+			try
+			{
+				string cName = PgpUtilities.GetSymmetricCipherName(alg);
+				c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+			}
+			catch (Exception e)
+			{
+				throw new PgpException("Exception creating cipher", e);
+			}
+
+			// TODO Factor this block out as 'encryptData'
+			try
+            {
+				KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase);
+				byte[] iv = secret.GetIV();
+
+				if (secret.PublicKeyPacket.Version == 4)
+                {
+					c.Init(false, new ParametersWithIV(key, iv));
+
+					data = c.DoFinal(encData);
+
+					bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1;
+					byte[] check = Checksum(useSha1, data, (useSha1) ? data.Length - 20 : data.Length - 2);
+
+					for (int i = 0; i != check.Length; i++)
+					{
+						if (check[i] != data[data.Length - check.Length + i])
+						{
+							throw new PgpException("Checksum mismatch at " + i + " of " + check.Length);
+						}
+					}
+				}
+                else // version 2 or 3, RSA only.
+                {
+					data = new byte[encData.Length];
+
+					//
+                    // read in the four numbers
+                    //
+                    int pos = 0;
+
+					for (int i = 0; i != 4; i++)
+                    {
+                        c.Init(false, new ParametersWithIV(key, iv));
+
+						int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
+
+						data[pos] = encData[pos];
+						data[pos + 1] = encData[pos + 1];
+						pos += 2;
+
+						c.DoFinal(encData, pos, encLen, data, pos);
+						pos += encLen;
+
+						if (i != 3)
+                        {
+                            Array.Copy(encData, pos - iv.Length, iv, 0, iv.Length);
+                        }
+                    }
+
+					//
+                    // verify Checksum
+                    //
+					int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
+                    int calcCs = 0;
+                    for (int j=0; j < data.Length-2; j++)
+                    {
+                        calcCs += data[j] & 0xff;
+                    }
+
+					calcCs &= 0xffff;
+                    if (calcCs != cs)
+                    {
+                        throw new PgpException("Checksum mismatch: passphrase wrong, expected "
+							+ cs.ToString("X")
+							+ " found " + calcCs.ToString("X"));
+                    }
+                }
+
+				return data;
+            }
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception decrypting key", e);
+            }
+        }
+
+		/// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
+        public PgpPrivateKey ExtractPrivateKey(
+            char[] passPhrase)
+        {
+			byte[] secKeyData = secret.GetSecretKeyData();
+            if (secKeyData == null || secKeyData.Length < 1)
+                return null;
+
+			PublicKeyPacket pubPk = secret.PublicKeyPacket;
+            try
+            {
+                byte[] data = ExtractKeyData(passPhrase);
+                BcpgInputStream bcpgIn = BcpgInputStream.Wrap(new MemoryStream(data, false));
+                AsymmetricKeyParameter privateKey;
+                switch (pubPk.Algorithm)
+                {
+                case PublicKeyAlgorithmTag.RsaEncrypt:
+                case PublicKeyAlgorithmTag.RsaGeneral:
+                case PublicKeyAlgorithmTag.RsaSign:
+                    RsaPublicBcpgKey rsaPub = (RsaPublicBcpgKey)pubPk.Key;
+                    RsaSecretBcpgKey rsaPriv = new RsaSecretBcpgKey(bcpgIn);
+                    RsaPrivateCrtKeyParameters rsaPrivSpec = new RsaPrivateCrtKeyParameters(
+                        rsaPriv.Modulus,
+                        rsaPub.PublicExponent,
+                        rsaPriv.PrivateExponent,
+                        rsaPriv.PrimeP,
+                        rsaPriv.PrimeQ,
+                        rsaPriv.PrimeExponentP,
+                        rsaPriv.PrimeExponentQ,
+                        rsaPriv.CrtCoefficient);
+                    privateKey = rsaPrivSpec;
+                    break;
+                case PublicKeyAlgorithmTag.Dsa:
+                    DsaPublicBcpgKey dsaPub = (DsaPublicBcpgKey)pubPk.Key;
+                    DsaSecretBcpgKey dsaPriv = new DsaSecretBcpgKey(bcpgIn);
+                    DsaParameters dsaParams = new DsaParameters(dsaPub.P, dsaPub.Q, dsaPub.G);
+                    privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
+                    break;
+                case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                case PublicKeyAlgorithmTag.ElGamalGeneral:
+                    ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
+                    ElGamalSecretBcpgKey elPriv = new ElGamalSecretBcpgKey(bcpgIn);
+                    ElGamalParameters elParams = new ElGamalParameters(elPub.P, elPub.G);
+                    privateKey = new ElGamalPrivateKeyParameters(elPriv.X, elParams);
+                    break;
+                default:
+                    throw new PgpException("unknown public key algorithm encountered");
+                }
+
+				return new PgpPrivateKey(privateKey, KeyId);
+            }
+            catch (PgpException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception constructing key", e);
+            }
+        }
+
+		private static byte[] Checksum(
+			bool	useSha1,
+			byte[]	bytes,
+			int		length)
+		{
+			if (useSha1)
+			{
+				try
+				{
+					IDigest dig = DigestUtilities.GetDigest("SHA1");
+					dig.BlockUpdate(bytes, 0, length);
+					return DigestUtilities.DoFinal(dig);
+				}
+				//catch (NoSuchAlgorithmException e)
+				catch (Exception e)
+				{
+					throw new PgpException("Can't find SHA-1", e);
+				}
+			}
+			else
+			{
+				int Checksum = 0;
+				for (int i = 0; i != length; i++)
+				{
+					Checksum += bytes[i];
+				}
+
+				return new byte[] { (byte)(Checksum >> 8), (byte)Checksum };
+			}
+		}
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+            Encode(bOut);
+            return bOut.ToArray();
+        }
+
+		public void Encode(
+            Stream outStr)
+        {
+            BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+			bcpgOut.WritePacket(secret);
+            if (pub.trustPk != null)
+            {
+                bcpgOut.WritePacket(pub.trustPk);
+            }
+
+			if (pub.subSigs == null) // is not a sub key
+            {
+				foreach (PgpSignature keySig in pub.keySigs)
+				{
+					keySig.Encode(bcpgOut);
+                }
+
+				for (int i = 0; i != pub.ids.Count; i++)
+                {
+					object pubID = pub.ids[i];
+                    if (pubID is string)
+                    {
+                        string id = (string) pubID;
+                        bcpgOut.WritePacket(new UserIdPacket(id));
+                    }
+                    else
+                    {
+                        PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector) pubID;
+                        bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray()));
+                    }
+
+					if (pub.idTrusts[i] != null)
+                    {
+                        bcpgOut.WritePacket((ContainedPacket)pub.idTrusts[i]);
+                    }
+
+					foreach (PgpSignature sig in (IList) pub.idSigs[i])
+					{
+						sig.Encode(bcpgOut);
+                    }
+                }
+            }
+            else
+            {
+				foreach (PgpSignature subSig in pub.subSigs)
+				{
+					subSig.Encode(bcpgOut);
+                }
+            }
+
+			// TODO Check that this is right/necessary
+			//bcpgOut.Finish();
+        }
+
+		/// <summary>
+		/// Return a copy of the passed in secret key, encrypted using a new password
+		/// and the passed in algorithm.
+		/// </summary>
+		/// <param name="key">The PgpSecretKey to be copied.</param>
+		/// <param name="oldPassPhrase">The current password for the key.</param>
+		/// <param name="newPassPhrase">The new password for the key.</param>
+		/// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+		/// <param name="rand">Source of randomness.</param>
+        public static PgpSecretKey CopyWithNewPassword(
+            PgpSecretKey				key,
+            char[]						oldPassPhrase,
+            char[]						newPassPhrase,
+            SymmetricKeyAlgorithmTag	newEncAlgorithm,
+            SecureRandom				rand)
+        {
+            byte[]	rawKeyData = key.ExtractKeyData(oldPassPhrase);
+			int		s2kUsage = key.secret.S2kUsage;
+			byte[]	iv = null;
+            S2k		s2k = null;
+            byte[]	keyData;
+
+			if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null)
+            {
+				s2kUsage = SecretKeyPacket.UsageNone;
+				if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1)   // SHA-1 hash, need to rewrite Checksum
+				{
+					keyData = new byte[rawKeyData.Length - 18];
+
+					Array.Copy(rawKeyData, 0, keyData, 0, keyData.Length - 2);
+
+					byte[] check = Checksum(false, keyData, keyData.Length - 2);
+
+					keyData[keyData.Length - 2] = check[0];
+					keyData[keyData.Length - 1] = check[1];
+				}
+				else
+				{
+					keyData = rawKeyData;
+				}
+			}
+            else
+            {
+                try
+                {
+					keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv);
+                }
+                catch (PgpException e)
+                {
+                    throw e;
+                }
+                catch (Exception e)
+                {
+                    throw new PgpException("Exception encrypting key", e);
+                }
+            }
+
+			SecretKeyPacket secret;
+            if (key.secret is SecretSubkeyPacket)
+            {
+                secret = new SecretSubkeyPacket(key.secret.PublicKeyPacket,
+					newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+            }
+            else
+            {
+                secret = new SecretKeyPacket(key.secret.PublicKeyPacket,
+	                newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+            }
+
+			return new PgpSecretKey(secret, key.pub);
+        }
+
+		/// <summary>Replace the passed the public key on the passed in secret key.</summary>
+		/// <param name="secretKey">Secret key to change.</param>
+		/// <param name="publicKey">New public key.</param>
+		/// <returns>A new secret key.</returns>
+		/// <exception cref="ArgumentException">If KeyId's do not match.</exception>
+		public static PgpSecretKey ReplacePublicKey(
+			PgpSecretKey	secretKey,
+			PgpPublicKey	publicKey)
+		{
+			if (publicKey.KeyId != secretKey.KeyId)
+				throw new ArgumentException("KeyId's do not match");
+
+			return new PgpSecretKey(secretKey.secret, publicKey);
+		}
+
+		private static byte[] EncryptKeyData(
+			byte[]						rawKeyData,
+			SymmetricKeyAlgorithmTag	encAlgorithm,
+			char[]						passPhrase,
+			SecureRandom				random,
+			out S2k						s2k,
+			out byte[]					iv)
+		{
+			IBufferedCipher c;
+			try
+			{
+				string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+				c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+			}
+			catch (Exception e)
+			{
+				throw new PgpException("Exception creating cipher", e);
+			}
+
+			byte[] s2kIV = new byte[8];
+			random.NextBytes(s2kIV);
+			s2k = new S2k(HashAlgorithmTag.Sha1, s2kIV, 0x60);
+
+			KeyParameter kp = PgpUtilities.MakeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase);
+
+			iv = new byte[c.GetBlockSize()];
+			random.NextBytes(iv);
+
+			c.Init(true, new ParametersWithRandom(new ParametersWithIV(kp, iv), random));
+
+			return c.DoFinal(rawKeyData);
+		}
+    }
+}
diff --git a/crypto/src/openpgp/PgpSecretKeyRing.cs b/crypto/src/openpgp/PgpSecretKeyRing.cs
new file mode 100644
index 000000000..3e646eaa1
--- /dev/null
+++ b/crypto/src/openpgp/PgpSecretKeyRing.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Class to hold a single master secret key and its subkeys.
+	/// <p>
+	/// Often PGP keyring files consist of multiple master keys, if you are trying to process
+	/// or construct one of these you should use the <c>PgpSecretKeyRingBundle</c> class.
+	/// </p>
+	/// </remarks>
+	public class PgpSecretKeyRing
+		: PgpKeyRing
+    {
+        private readonly IList keys;
+		private readonly IList extraPubKeys;
+
+		internal PgpSecretKeyRing(
+			IList keys)
+			: this(keys, Platform.CreateArrayList())
+		{
+		}
+
+		private PgpSecretKeyRing(
+			IList	keys,
+			IList	extraPubKeys)
+		{
+			this.keys = keys;
+			this.extraPubKeys = extraPubKeys;
+		}
+
+		public PgpSecretKeyRing(
+            byte[] encoding)
+            : this(new MemoryStream(encoding))
+        {
+        }
+
+		public PgpSecretKeyRing(
+            Stream inputStream)
+        {
+			this.keys = Platform.CreateArrayList();
+            this.extraPubKeys = Platform.CreateArrayList();
+
+			BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream);
+
+			PacketTag initialTag = bcpgInput.NextPacketTag();
+			if (initialTag != PacketTag.SecretKey && initialTag != PacketTag.SecretSubkey)
+            {
+                throw new IOException("secret key ring doesn't start with secret key tag: "
+					+ "tag 0x" + ((int)initialTag).ToString("X"));
+            }
+
+			SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket();
+
+			//
+            // ignore GPG comment packets if found.
+            //
+            while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
+            {
+                bcpgInput.ReadPacket();
+            }
+
+			TrustPacket trust = ReadOptionalTrustPacket(bcpgInput);
+
+			// revocation and direct signatures
+			IList keySigs = ReadSignaturesAndTrust(bcpgInput);
+
+			IList ids, idTrusts, idSigs;
+			ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+
+			keys.Add(new PgpSecretKey(secret, new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs)));
+
+
+			// Read subkeys
+			while (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey
+				|| bcpgInput.NextPacketTag() == PacketTag.PublicSubkey)
+            {
+				if (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey)
+				{
+					SecretSubkeyPacket sub = (SecretSubkeyPacket) bcpgInput.ReadPacket();
+
+					//
+					// ignore GPG comment packets if found.
+					//
+					while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
+					{
+						bcpgInput.ReadPacket();
+					}
+
+					TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
+					IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+					keys.Add(new PgpSecretKey(sub, new PgpPublicKey(sub.PublicKeyPacket, subTrust, sigList)));
+				}
+				else
+				{
+					PublicSubkeyPacket sub = (PublicSubkeyPacket) bcpgInput.ReadPacket();
+
+					TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
+					IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+					extraPubKeys.Add(new PgpPublicKey(sub, subTrust, sigList));
+				}
+            }
+        }
+
+		/// <summary>Return the public key for the master key.</summary>
+        public PgpPublicKey GetPublicKey()
+        {
+            return ((PgpSecretKey) keys[0]).PublicKey;
+        }
+
+		/// <summary>Return the master private key.</summary>
+        public PgpSecretKey GetSecretKey()
+        {
+            return (PgpSecretKey) keys[0];
+        }
+
+		/// <summary>Allows enumeration of the secret keys.</summary>
+		/// <returns>An <c>IEnumerable</c> of <c>PgpSecretKey</c> objects.</returns>
+		public IEnumerable GetSecretKeys()
+        {
+            return new EnumerableProxy(keys);
+        }
+
+        public PgpSecretKey GetSecretKey(
+            long keyId)
+        {
+			foreach (PgpSecretKey k in keys)
+			{
+				if (keyId == k.KeyId)
+				{
+					return k;
+				}
+			}
+
+			return null;
+        }
+
+		/// <summary>
+		/// Return an iterator of the public keys in the secret key ring that
+		/// have no matching private key. At the moment only personal certificate data
+		/// appears in this fashion.
+		/// </summary>
+		/// <returns>An <c>IEnumerable</c> of unattached, or extra, public keys.</returns>
+		public IEnumerable GetExtraPublicKeys()
+		{
+			return new EnumerableProxy(extraPubKeys);
+		}
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+
+            Encode(bOut);
+
+            return bOut.ToArray();
+        }
+
+        public void Encode(
+            Stream outStr)
+        {
+			if (outStr == null)
+				throw new ArgumentNullException("outStr");
+
+			foreach (PgpSecretKey key in keys)
+			{
+				key.Encode(outStr);
+			}
+			foreach (PgpPublicKey extraPubKey in extraPubKeys)
+			{
+				extraPubKey.Encode(outStr);
+			}
+        }
+
+		/// <summary>
+		/// Replace the public key set on the secret ring with the corresponding key off the public ring.
+		/// </summary>
+		/// <param name="secretRing">Secret ring to be changed.</param>
+		/// <param name="publicRing">Public ring containing the new public key set.</param>
+		public static PgpSecretKeyRing ReplacePublicKeys(
+			PgpSecretKeyRing	secretRing,
+			PgpPublicKeyRing	publicRing)
+		{
+            IList newList = Platform.CreateArrayList(secretRing.keys.Count);
+
+			foreach (PgpSecretKey sk in secretRing.keys)
+			{
+				PgpPublicKey pk = publicRing.GetPublicKey(sk.KeyId);
+
+				newList.Add(PgpSecretKey.ReplacePublicKey(sk, pk));
+			}
+
+			return new PgpSecretKeyRing(newList);
+		}
+
+		/// <summary>
+		/// Return a copy of the passed in secret key ring, with the master key and sub keys encrypted
+		/// using a new password and the passed in algorithm.
+		/// </summary>
+		/// <param name="ring">The <c>PgpSecretKeyRing</c> to be copied.</param>
+		/// <param name="oldPassPhrase">The current password for key.</param>
+		/// <param name="newPassPhrase">The new password for the key.</param>
+		/// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+		/// <param name="rand">Source of randomness.</param>
+		public static PgpSecretKeyRing CopyWithNewPassword(
+			PgpSecretKeyRing			ring,
+			char[]						oldPassPhrase,
+			char[]						newPassPhrase,
+			SymmetricKeyAlgorithmTag	newEncAlgorithm,
+			SecureRandom				rand)
+		{
+            IList newKeys = Platform.CreateArrayList(ring.keys.Count);
+			foreach (PgpSecretKey secretKey in ring.GetSecretKeys())
+			{
+				newKeys.Add(PgpSecretKey.CopyWithNewPassword(secretKey, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand));
+			}
+
+			return new PgpSecretKeyRing(newKeys, ring.extraPubKeys);
+		}
+
+		/// <summary>
+		/// Returns a new key ring with the secret key passed in either added or
+		/// replacing an existing one with the same key ID.
+		/// </summary>
+		/// <param name="secRing">The secret key ring to be modified.</param>
+		/// <param name="secKey">The secret key to be inserted.</param>
+		/// <returns>A new <c>PgpSecretKeyRing</c></returns>
+		public static PgpSecretKeyRing InsertSecretKey(
+            PgpSecretKeyRing  secRing,
+            PgpSecretKey      secKey)
+        {
+            IList keys = Platform.CreateArrayList(secRing.keys);
+            bool found = false;
+			bool masterFound = false;
+
+			for (int i = 0; i != keys.Count; i++)
+            {
+                PgpSecretKey key = (PgpSecretKey) keys[i];
+
+				if (key.KeyId == secKey.KeyId)
+                {
+                    found = true;
+                    keys[i] = secKey;
+                }
+				if (key.IsMasterKey)
+				{
+					masterFound = true;
+				}
+			}
+
+            if (!found)
+            {
+				if (secKey.IsMasterKey)
+				{
+					if (masterFound)
+						throw new ArgumentException("cannot add a master key to a ring that already has one");
+
+					keys.Insert(0, secKey);
+				}
+				else
+				{
+					keys.Add(secKey);
+				}
+            }
+
+			return new PgpSecretKeyRing(keys, secRing.extraPubKeys);
+		}
+
+		/// <summary>Returns a new key ring with the secret key passed in removed from the key ring.</summary>
+		/// <param name="secRing">The secret key ring to be modified.</param>
+		/// <param name="secKey">The secret key to be removed.</param>
+		/// <returns>A new <c>PgpSecretKeyRing</c>, or null if secKey is not found.</returns>
+        public static PgpSecretKeyRing RemoveSecretKey(
+            PgpSecretKeyRing  secRing,
+            PgpSecretKey      secKey)
+        {
+            IList keys = Platform.CreateArrayList(secRing.keys);
+            bool found = false;
+
+			for (int i = 0; i < keys.Count; i++)
+            {
+                PgpSecretKey key = (PgpSecretKey)keys[i];
+
+				if (key.KeyId == secKey.KeyId)
+                {
+                    found = true;
+                    keys.RemoveAt(i);
+                }
+            }
+
+			return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null;
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpSecretKeyRingBundle.cs b/crypto/src/openpgp/PgpSecretKeyRingBundle.cs
new file mode 100644
index 000000000..18636dd65
--- /dev/null
+++ b/crypto/src/openpgp/PgpSecretKeyRingBundle.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>
+	/// Often a PGP key ring file is made up of a succession of master/sub-key key rings.
+	/// If you want to read an entire secret key file in one hit this is the class for you.
+	/// </remarks>
+    public class PgpSecretKeyRingBundle
+    {
+        private readonly IDictionary secretRings;
+        private readonly IList order;
+
+		private PgpSecretKeyRingBundle(
+            IDictionary	secretRings,
+            IList       order)
+        {
+            this.secretRings = secretRings;
+            this.order = order;
+        }
+
+		public PgpSecretKeyRingBundle(
+            byte[] encoding)
+            : this(new MemoryStream(encoding, false))
+		{
+        }
+
+		/// <summary>Build a PgpSecretKeyRingBundle from the passed in input stream.</summary>
+		/// <param name="inputStream">Input stream containing data.</param>
+		/// <exception cref="IOException">If a problem parsing the stream occurs.</exception>
+		/// <exception cref="PgpException">If an object is encountered which isn't a PgpSecretKeyRing.</exception>
+		public PgpSecretKeyRingBundle(
+            Stream inputStream)
+			: this(new PgpObjectFactory(inputStream).AllPgpObjects())
+        {
+        }
+
+		public PgpSecretKeyRingBundle(
+            IEnumerable e)
+        {
+			this.secretRings = Platform.CreateHashtable();
+            this.order = Platform.CreateArrayList();
+
+			foreach (object obj in e)
+			{
+				PgpSecretKeyRing pgpSecret = obj as PgpSecretKeyRing;
+
+				if (pgpSecret == null)
+				{
+					throw new PgpException(obj.GetType().FullName + " found where PgpSecretKeyRing expected");
+				}
+
+				long key = pgpSecret.GetPublicKey().KeyId;
+				secretRings.Add(key, pgpSecret);
+				order.Add(key);
+			}
+        }
+
+		[Obsolete("Use 'Count' property instead")]
+		public int Size
+		{
+			get { return order.Count; }
+		}
+
+		/// <summary>Return the number of rings in this collection.</summary>
+		public int Count
+        {
+			get { return order.Count; }
+        }
+
+		/// <summary>Allow enumeration of the secret key rings making up this collection.</summary>
+		public IEnumerable GetKeyRings()
+        {
+            return new EnumerableProxy(secretRings.Values);
+        }
+
+		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+		/// <param name="userId">The user ID to be matched.</param>
+		/// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+		public IEnumerable GetKeyRings(
+			string userId)
+		{
+			return GetKeyRings(userId, false, false);
+		}
+
+		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+		/// <param name="userId">The user ID to be matched.</param>
+		/// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+		/// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+		public IEnumerable GetKeyRings(
+            string	userId,
+            bool	matchPartial)
+        {
+			return GetKeyRings(userId, matchPartial, false);
+        }
+
+		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+		/// <param name="userId">The user ID to be matched.</param>
+		/// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+		/// <param name="ignoreCase">If true, case is ignored in user ID comparisons.</param>
+		/// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+		public IEnumerable GetKeyRings(
+			string	userId,
+			bool	matchPartial,
+			bool	ignoreCase)
+		{
+            IList rings = Platform.CreateArrayList();
+
+			if (ignoreCase)
+			{
+				userId = userId.ToLowerInvariant();
+			}
+
+			foreach (PgpSecretKeyRing secRing in GetKeyRings())
+			{
+				foreach (string nextUserID in secRing.GetSecretKey().UserIds)
+				{
+					string next = nextUserID;
+					if (ignoreCase)
+					{
+						next = next.ToLowerInvariant();
+					}
+
+					if (matchPartial)
+					{
+						if (next.IndexOf(userId) > -1)
+						{
+							rings.Add(secRing);
+						}
+					}
+					else
+					{
+						if (next.Equals(userId))
+						{
+							rings.Add(secRing);
+						}
+					}
+				}
+			}
+
+			return new EnumerableProxy(rings);
+		}
+
+		/// <summary>Return the PGP secret key associated with the given key id.</summary>
+		/// <param name="keyId">The ID of the secret key to return.</param>
+		public PgpSecretKey GetSecretKey(
+            long keyId)
+        {
+            foreach (PgpSecretKeyRing secRing in GetKeyRings())
+            {
+                PgpSecretKey sec = secRing.GetSecretKey(keyId);
+
+				if (sec != null)
+                {
+                    return sec;
+                }
+            }
+
+            return null;
+        }
+
+		/// <summary>Return the secret key ring which contains the key referred to by keyId</summary>
+		/// <param name="keyId">The ID of the secret key</param>
+		public PgpSecretKeyRing GetSecretKeyRing(
+            long keyId)
+        {
+            long id = keyId;
+
+            if (secretRings.Contains(id))
+            {
+                return (PgpSecretKeyRing) secretRings[id];
+            }
+
+			foreach (PgpSecretKeyRing secretRing in GetKeyRings())
+            {
+                PgpSecretKey secret = secretRing.GetSecretKey(keyId);
+
+                if (secret != null)
+                {
+                    return secretRing;
+                }
+            }
+
+            return null;
+        }
+
+		/// <summary>
+		/// Return true if a key matching the passed in key ID is present, false otherwise.
+		/// </summary>
+		/// <param name="keyID">key ID to look for.</param>
+		public bool Contains(
+			long keyID)
+		{
+			return GetSecretKey(keyID) != null;
+		}
+
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+
+			Encode(bOut);
+
+			return bOut.ToArray();
+        }
+
+		public void Encode(
+			Stream outStr)
+        {
+			BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+			foreach (long key in order)
+            {
+                PgpSecretKeyRing pub = (PgpSecretKeyRing) secretRings[key];
+
+				pub.Encode(bcpgOut);
+            }
+        }
+
+		/// <summary>
+		/// Return a new bundle containing the contents of the passed in bundle and
+		/// the passed in secret key ring.
+		/// </summary>
+		/// <param name="bundle">The <c>PgpSecretKeyRingBundle</c> the key ring is to be added to.</param>
+		/// <param name="secretKeyRing">The key ring to be added.</param>
+		/// <returns>A new <c>PgpSecretKeyRingBundle</c> merging the current one with the passed in key ring.</returns>
+		/// <exception cref="ArgumentException">If the keyId for the passed in key ring is already present.</exception>
+        public static PgpSecretKeyRingBundle AddSecretKeyRing(
+            PgpSecretKeyRingBundle  bundle,
+            PgpSecretKeyRing        secretKeyRing)
+        {
+            long key = secretKeyRing.GetPublicKey().KeyId;
+
+            if (bundle.secretRings.Contains(key))
+            {
+                throw new ArgumentException("Collection already contains a key with a keyId for the passed in ring.");
+            }
+
+            IDictionary newSecretRings = Platform.CreateHashtable(bundle.secretRings);
+            IList newOrder = Platform.CreateArrayList(bundle.order);
+
+            newSecretRings[key] = secretKeyRing;
+            newOrder.Add(key);
+
+            return new PgpSecretKeyRingBundle(newSecretRings, newOrder);
+        }
+
+		/// <summary>
+		/// Return a new bundle containing the contents of the passed in bundle with
+		/// the passed in secret key ring removed.
+		/// </summary>
+		/// <param name="bundle">The <c>PgpSecretKeyRingBundle</c> the key ring is to be removed from.</param>
+		/// <param name="secretKeyRing">The key ring to be removed.</param>
+		/// <returns>A new <c>PgpSecretKeyRingBundle</c> not containing the passed in key ring.</returns>
+		/// <exception cref="ArgumentException">If the keyId for the passed in key ring is not present.</exception>
+        public static PgpSecretKeyRingBundle RemoveSecretKeyRing(
+            PgpSecretKeyRingBundle  bundle,
+            PgpSecretKeyRing        secretKeyRing)
+        {
+            long key = secretKeyRing.GetPublicKey().KeyId;
+
+			if (!bundle.secretRings.Contains(key))
+            {
+                throw new ArgumentException("Collection does not contain a key with a keyId for the passed in ring.");
+            }
+
+            IDictionary newSecretRings = Platform.CreateHashtable(bundle.secretRings);
+            IList newOrder = Platform.CreateArrayList(bundle.order);
+
+			newSecretRings.Remove(key);
+			newOrder.Remove(key);
+
+			return new PgpSecretKeyRingBundle(newSecretRings, newOrder);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
new file mode 100644
index 000000000..cbe0d83b4
--- /dev/null
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -0,0 +1,422 @@
+using System;
+using System.IO;
+using Org.BouncyCastle.Asn1;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>A PGP signature object.</remarks>
+    public class PgpSignature
+    {
+        public const int BinaryDocument = 0x00;
+        public const int CanonicalTextDocument = 0x01;
+        public const int StandAlone = 0x02;
+
+        public const int DefaultCertification = 0x10;
+        public const int NoCertification = 0x11;
+        public const int CasualCertification = 0x12;
+        public const int PositiveCertification = 0x13;
+
+        public const int SubkeyBinding = 0x18;
+		public const int PrimaryKeyBinding = 0x19;
+		public const int DirectKey = 0x1f;
+        public const int KeyRevocation = 0x20;
+        public const int SubkeyRevocation = 0x28;
+        public const int CertificationRevocation = 0x30;
+        public const int Timestamp = 0x40;
+
+        private readonly SignaturePacket	sigPck;
+        private readonly int				signatureType;
+        private readonly TrustPacket		trustPck;
+
+		private ISigner	sig;
+		private byte	lastb; // Initial value anything but '\r'
+
+		internal PgpSignature(
+            BcpgInputStream bcpgInput)
+            : this((SignaturePacket)bcpgInput.ReadPacket())
+        {
+        }
+
+		internal PgpSignature(
+            SignaturePacket sigPacket)
+			: this(sigPacket, null)
+        {
+        }
+
+        internal PgpSignature(
+            SignaturePacket	sigPacket,
+            TrustPacket		trustPacket)
+        {
+			if (sigPacket == null)
+				throw new ArgumentNullException("sigPacket");
+
+			this.sigPck = sigPacket;
+			this.signatureType = sigPck.SignatureType;
+			this.trustPck = trustPacket;
+        }
+
+		private void GetSig()
+        {
+            this.sig = SignerUtilities.GetSigner(
+				PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
+        }
+
+		/// <summary>The OpenPGP version number for this signature.</summary>
+		public int Version
+		{
+			get { return sigPck.Version; }
+		}
+
+		/// <summary>The key algorithm associated with this signature.</summary>
+		public PublicKeyAlgorithmTag KeyAlgorithm
+		{
+			get { return sigPck.KeyAlgorithm; }
+		}
+
+		/// <summary>The hash algorithm associated with this signature.</summary>
+		public HashAlgorithmTag HashAlgorithm
+		{
+			get { return sigPck.HashAlgorithm; }
+		}
+
+		public void InitVerify(
+            PgpPublicKey pubKey)
+        {
+			lastb = 0;
+            if (sig == null)
+            {
+                GetSig();
+            }
+            try
+            {
+                sig.Init(false, pubKey.GetKey());
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new PgpException("invalid key.", e);
+            }
+        }
+
+        public void Update(
+            byte b)
+        {
+            if (signatureType == CanonicalTextDocument)
+            {
+				doCanonicalUpdateByte(b);
+            }
+            else
+            {
+                sig.Update(b);
+            }
+        }
+
+		private void doCanonicalUpdateByte(
+			byte b)
+		{
+			if (b == '\r')
+			{
+				doUpdateCRLF();
+			}
+			else if (b == '\n')
+			{
+				if (lastb != '\r')
+				{
+					doUpdateCRLF();
+				}
+			}
+			else
+			{
+				sig.Update(b);
+			}
+
+			lastb = b;
+		}
+
+		private void doUpdateCRLF()
+		{
+			sig.Update((byte)'\r');
+			sig.Update((byte)'\n');
+		}
+
+		public void Update(
+            params byte[] bytes)
+        {
+			Update(bytes, 0, bytes.Length);
+        }
+
+		public void Update(
+            byte[]	bytes,
+            int		off,
+            int		length)
+        {
+            if (signatureType == CanonicalTextDocument)
+            {
+                int finish = off + length;
+
+				for (int i = off; i != finish; i++)
+                {
+                    doCanonicalUpdateByte(bytes[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(bytes, off, length);
+            }
+        }
+
+		public bool Verify()
+        {
+            byte[] trailer = GetSignatureTrailer();
+            sig.BlockUpdate(trailer, 0, trailer.Length);
+
+			return sig.VerifySignature(GetSignature());
+        }
+
+		private void UpdateWithIdData(
+			int		header,
+			byte[]	idBytes)
+		{
+			this.Update(
+				(byte) header,
+				(byte)(idBytes.Length >> 24),
+				(byte)(idBytes.Length >> 16),
+				(byte)(idBytes.Length >> 8),
+				(byte)(idBytes.Length));
+			this.Update(idBytes);
+		}
+
+		private void UpdateWithPublicKey(
+			PgpPublicKey key)
+		{
+			byte[] keyBytes = GetEncodedPublicKey(key);
+
+			this.Update(
+				(byte) 0x99,
+				(byte)(keyBytes.Length >> 8),
+				(byte)(keyBytes.Length));
+			this.Update(keyBytes);
+		}
+
+		/// <summary>
+		/// Verify the signature as certifying the passed in public key as associated
+		/// with the passed in user attributes.
+		/// </summary>
+		/// <param name="userAttributes">User attributes the key was stored under.</param>
+		/// <param name="key">The key to be verified.</param>
+		/// <returns>True, if the signature matches, false otherwise.</returns>
+		public bool VerifyCertification(
+			PgpUserAttributeSubpacketVector	userAttributes,
+			PgpPublicKey					key)
+		{
+			UpdateWithPublicKey(key);
+
+			//
+			// hash in the userAttributes
+			//
+			try
+			{
+				MemoryStream bOut = new MemoryStream();
+				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
+				{
+					packet.Encode(bOut);
+				}
+				UpdateWithIdData(0xd1, bOut.ToArray());
+			}
+			catch (IOException e)
+			{
+				throw new PgpException("cannot encode subpacket array", e);
+			}
+
+			this.Update(sigPck.GetSignatureTrailer());
+
+			return sig.VerifySignature(this.GetSignature());
+		}
+
+		/// <summary>
+		/// Verify the signature as certifying the passed in public key as associated
+		/// with the passed in ID.
+		/// </summary>
+		/// <param name="id">ID the key was stored under.</param>
+		/// <param name="key">The key to be verified.</param>
+		/// <returns>True, if the signature matches, false otherwise.</returns>
+        public bool VerifyCertification(
+            string			id,
+            PgpPublicKey	key)
+        {
+			UpdateWithPublicKey(key);
+
+			//
+            // hash in the id
+            //
+			UpdateWithIdData(0xb4, Strings.ToByteArray(id));
+
+			Update(sigPck.GetSignatureTrailer());
+
+			return sig.VerifySignature(GetSignature());
+        }
+
+		/// <summary>Verify a certification for the passed in key against the passed in master key.</summary>
+		/// <param name="masterKey">The key we are verifying against.</param>
+		/// <param name="pubKey">The key we are verifying.</param>
+		/// <returns>True, if the certification is valid, false otherwise.</returns>
+        public bool VerifyCertification(
+            PgpPublicKey	masterKey,
+            PgpPublicKey	pubKey)
+        {
+			UpdateWithPublicKey(masterKey);
+			UpdateWithPublicKey(pubKey);
+
+			Update(sigPck.GetSignatureTrailer());
+
+			return sig.VerifySignature(GetSignature());
+        }
+
+		/// <summary>Verify a key certification, such as revocation, for the passed in key.</summary>
+		/// <param name="pubKey">The key we are checking.</param>
+		/// <returns>True, if the certification is valid, false otherwise.</returns>
+        public bool VerifyCertification(
+            PgpPublicKey pubKey)
+        {
+            if (SignatureType != KeyRevocation
+                && SignatureType != SubkeyRevocation)
+            {
+                throw new InvalidOperationException("signature is not a key signature");
+            }
+
+			UpdateWithPublicKey(pubKey);
+
+            Update(sigPck.GetSignatureTrailer());
+
+			return sig.VerifySignature(GetSignature());
+        }
+
+		public int SignatureType
+        {
+			get { return sigPck.SignatureType; }
+        }
+
+		/// <summary>The ID of the key that created the signature.</summary>
+        public long KeyId
+        {
+            get { return sigPck.KeyId; }
+        }
+
+		[Obsolete("Use 'CreationTime' property instead")]
+		public DateTime GetCreationTime()
+		{
+			return CreationTime;
+		}
+
+		/// <summary>The creation time of this signature.</summary>
+        public DateTime CreationTime
+        {
+			get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
+        }
+
+		public byte[] GetSignatureTrailer()
+        {
+            return sigPck.GetSignatureTrailer();
+        }
+
+		/// <summary>
+		/// Return true if the signature has either hashed or unhashed subpackets.
+		/// </summary>
+		public bool HasSubpackets
+		{
+			get
+			{
+				return sigPck.GetHashedSubPackets() != null
+					|| sigPck.GetUnhashedSubPackets() != null;
+			}
+		}
+
+		public PgpSignatureSubpacketVector GetHashedSubPackets()
+        {
+            return createSubpacketVector(sigPck.GetHashedSubPackets());
+        }
+
+		public PgpSignatureSubpacketVector GetUnhashedSubPackets()
+        {
+            return createSubpacketVector(sigPck.GetUnhashedSubPackets());
+        }
+
+		private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
+		{
+			return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
+		}
+
+		public byte[] GetSignature()
+        {
+            MPInteger[] sigValues = sigPck.GetSignature();
+            byte[] signature;
+
+			if (sigValues != null)
+			{
+				if (sigValues.Length == 1)    // an RSA signature
+				{
+					signature = sigValues[0].Value.ToByteArrayUnsigned();
+				}
+				else
+				{
+					try
+					{
+						signature = new DerSequence(
+							new DerInteger(sigValues[0].Value),
+							new DerInteger(sigValues[1].Value)).GetEncoded();
+					}
+					catch (IOException e)
+					{
+						throw new PgpException("exception encoding DSA sig.", e);
+					}
+				}
+			}
+			else
+			{
+				signature = sigPck.GetSignatureBytes();
+			}
+
+			return signature;
+        }
+
+		// TODO Handle the encoding stuff by subclassing BcpgObject?
+		public byte[] GetEncoded()
+        {
+            MemoryStream bOut = new MemoryStream();
+
+			Encode(bOut);
+
+			return bOut.ToArray();
+        }
+
+		public void Encode(
+            Stream outStream)
+        {
+            BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);
+
+			bcpgOut.WritePacket(sigPck);
+
+			if (trustPck != null)
+            {
+                bcpgOut.WritePacket(trustPck);
+            }
+        }
+
+		private byte[] GetEncodedPublicKey(
+			PgpPublicKey pubKey) 
+		{
+			try
+			{
+				return pubKey.publicPk.GetEncodedContents();
+			}
+			catch (IOException e)
+			{
+				throw new PgpException("exception preparing key.", e);
+			}
+		}
+    }
+}
diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs
new file mode 100644
index 000000000..891397267
--- /dev/null
+++ b/crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -0,0 +1,393 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Generator for PGP signatures.</remarks>
+	// TODO Should be able to implement ISigner?
+    public class PgpSignatureGenerator
+    {
+		private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
+
+		private PublicKeyAlgorithmTag	keyAlgorithm;
+        private HashAlgorithmTag		hashAlgorithm;
+        private PgpPrivateKey			privKey;
+        private ISigner					sig;
+        private IDigest					dig;
+        private int						signatureType;
+        private byte					lastb;
+
+		private SignatureSubpacket[] unhashed = EmptySignatureSubpackets;
+        private SignatureSubpacket[] hashed = EmptySignatureSubpackets;
+
+		/// <summary>Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.</summary>
+        public PgpSignatureGenerator(
+            PublicKeyAlgorithmTag	keyAlgorithm,
+            HashAlgorithmTag		hashAlgorithm)
+        {
+            this.keyAlgorithm = keyAlgorithm;
+            this.hashAlgorithm = hashAlgorithm;
+
+			dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
+            sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+        }
+
+		/// <summary>Initialise the generator for signing.</summary>
+        public void InitSign(
+            int				sigType,
+            PgpPrivateKey	key)
+        {
+			InitSign(sigType, key, null);
+        }
+
+		/// <summary>Initialise the generator for signing.</summary>
+		public void InitSign(
+			int				sigType,
+			PgpPrivateKey	key,
+			SecureRandom	random)
+		{
+			this.privKey = key;
+			this.signatureType = sigType;
+
+			try
+			{
+				ICipherParameters cp = key.Key;
+				if (random != null)
+				{
+					cp = new ParametersWithRandom(key.Key, random);
+				}
+
+				sig.Init(true, cp);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new PgpException("invalid key.", e);
+			}
+
+			dig.Reset();
+			lastb = 0;
+		}
+
+		public void Update(
+            byte b)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+				doCanonicalUpdateByte(b);
+            }
+            else
+            {
+				doUpdateByte(b);
+            }
+        }
+
+		private void doCanonicalUpdateByte(
+			byte b)
+		{
+			if (b == '\r')
+			{
+				doUpdateCRLF();
+			}
+			else if (b == '\n')
+			{
+				if (lastb != '\r')
+				{
+					doUpdateCRLF();
+				}
+			}
+			else
+			{
+				doUpdateByte(b);
+			}
+
+			lastb = b;
+		}
+
+		private void doUpdateCRLF()
+		{
+			doUpdateByte((byte)'\r');
+			doUpdateByte((byte)'\n');
+		}
+
+		private void doUpdateByte(
+			byte b)
+		{
+			sig.Update(b);
+			dig.Update(b);
+		}
+
+		public void Update(
+            params byte[] b)
+        {
+			Update(b, 0, b.Length);
+        }
+
+		public void Update(
+            byte[]	b,
+            int		off,
+            int		len)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                int finish = off + len;
+
+				for (int i = off; i != finish; i++)
+                {
+                    doCanonicalUpdateByte(b[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(b, off, len);
+                dig.BlockUpdate(b, off, len);
+            }
+        }
+
+		public void SetHashedSubpackets(
+            PgpSignatureSubpacketVector hashedPackets)
+        {
+			hashed = hashedPackets == null
+				?	EmptySignatureSubpackets
+				:	hashedPackets.ToSubpacketArray();
+        }
+
+		public void SetUnhashedSubpackets(
+            PgpSignatureSubpacketVector unhashedPackets)
+        {
+			unhashed = unhashedPackets == null
+				?	EmptySignatureSubpackets
+				:	unhashedPackets.ToSubpacketArray();
+        }
+
+		/// <summary>Return the one pass header associated with the current signature.</summary>
+        public PgpOnePassSignature GenerateOnePassVersion(
+            bool isNested)
+        {
+            return new PgpOnePassSignature(
+				new OnePassSignaturePacket(
+					signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested));
+        }
+
+		/// <summary>Return a signature object containing the current signature state.</summary>
+        public PgpSignature Generate()
+        {
+			SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed;
+
+			if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime))
+			{
+				hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
+			}
+
+			if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
+				&& !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
+			{
+				unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
+			}
+
+			int version = 4;
+			byte[] hData;
+
+			try
+            {
+				MemoryStream hOut = new MemoryStream();
+
+				for (int i = 0; i != hPkts.Length; i++)
+				{
+					hPkts[i].Encode(hOut);
+				}
+
+				byte[] data = hOut.ToArray();
+
+				MemoryStream sOut = new MemoryStream(data.Length + 6);
+				sOut.WriteByte((byte)version);
+                sOut.WriteByte((byte)signatureType);
+                sOut.WriteByte((byte)keyAlgorithm);
+                sOut.WriteByte((byte)hashAlgorithm);
+				sOut.WriteByte((byte)(data.Length >> 8));
+                sOut.WriteByte((byte)data.Length);
+                sOut.Write(data, 0, data.Length);
+
+				hData = sOut.ToArray();
+			}
+            catch (IOException e)
+            {
+                throw new PgpException("exception encoding hashed data.", e);
+            }
+
+			sig.BlockUpdate(hData, 0, hData.Length);
+            dig.BlockUpdate(hData, 0, hData.Length);
+
+			hData = new byte[]
+			{
+				(byte) version,
+				0xff,
+				(byte)(hData.Length >> 24),
+				(byte)(hData.Length >> 16),
+				(byte)(hData.Length >> 8),
+				(byte) hData.Length
+			};
+
+			sig.BlockUpdate(hData, 0, hData.Length);
+            dig.BlockUpdate(hData, 0, hData.Length);
+
+			byte[] sigBytes = sig.GenerateSignature();
+			byte[] digest = DigestUtilities.DoFinal(dig);
+			byte[] fingerPrint = new byte[] { digest[0], digest[1] };
+
+			// an RSA signature
+			bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign
+				|| keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral;
+
+			MPInteger[] sigValues = isRsa
+				?	PgpUtilities.RsaSigToMpi(sigBytes)
+				:	PgpUtilities.DsaSigToMpi(sigBytes);
+
+			return new PgpSignature(
+				new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm,
+					hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
+        }
+
+		/// <summary>Generate a certification for the passed in ID and key.</summary>
+		/// <param name="id">The ID we are certifying against the public key.</param>
+		/// <param name="pubKey">The key we are certifying against the ID.</param>
+		/// <returns>The certification.</returns>
+        public PgpSignature GenerateCertification(
+            string			id,
+            PgpPublicKey	pubKey)
+        {
+			UpdateWithPublicKey(pubKey);
+
+			//
+            // hash in the id
+            //
+			UpdateWithIdData(0xb4, Strings.ToByteArray(id));
+
+			return Generate();
+        }
+
+		/// <summary>Generate a certification for the passed in userAttributes.</summary>
+		/// <param name="userAttributes">The ID we are certifying against the public key.</param>
+		/// <param name="pubKey">The key we are certifying against the ID.</param>
+		/// <returns>The certification.</returns>
+		public PgpSignature GenerateCertification(
+			PgpUserAttributeSubpacketVector	userAttributes,
+			PgpPublicKey					pubKey)
+		{
+			UpdateWithPublicKey(pubKey);
+
+			//
+			// hash in the attributes
+			//
+			try
+			{
+				MemoryStream bOut = new MemoryStream();
+				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
+				{
+					packet.Encode(bOut);
+				}
+				UpdateWithIdData(0xd1, bOut.ToArray());
+			}
+			catch (IOException e)
+			{
+				throw new PgpException("cannot encode subpacket array", e);
+			}
+
+			return this.Generate();
+		}
+
+		/// <summary>Generate a certification for the passed in key against the passed in master key.</summary>
+		/// <param name="masterKey">The key we are certifying against.</param>
+		/// <param name="pubKey">The key we are certifying.</param>
+		/// <returns>The certification.</returns>
+        public PgpSignature GenerateCertification(
+            PgpPublicKey	masterKey,
+            PgpPublicKey	pubKey)
+        {
+			UpdateWithPublicKey(masterKey);
+			UpdateWithPublicKey(pubKey);
+
+			return Generate();
+        }
+
+		/// <summary>Generate a certification, such as a revocation, for the passed in key.</summary>
+		/// <param name="pubKey">The key we are certifying.</param>
+		/// <returns>The certification.</returns>
+        public PgpSignature GenerateCertification(
+            PgpPublicKey pubKey)
+        {
+			UpdateWithPublicKey(pubKey);
+
+			return Generate();
+        }
+
+		private byte[] GetEncodedPublicKey(
+			PgpPublicKey pubKey) 
+		{
+			try
+			{
+				return pubKey.publicPk.GetEncodedContents();
+			}
+			catch (IOException e)
+			{
+				throw new PgpException("exception preparing key.", e);
+			}
+		}
+
+		private bool packetPresent(
+			SignatureSubpacket[]	packets,
+			SignatureSubpacketTag	type)
+		{
+			for (int i = 0; i != packets.Length; i++)
+			{
+				if (packets[i].SubpacketType == type)
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		private SignatureSubpacket[] insertSubpacket(
+			SignatureSubpacket[]	packets,
+			SignatureSubpacket		subpacket)
+		{
+			SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1];
+			tmp[0] = subpacket;
+			packets.CopyTo(tmp, 1);
+			return tmp;
+		}
+
+		private void UpdateWithIdData(
+			int		header,
+			byte[]	idBytes)
+		{
+			this.Update(
+				(byte) header,
+				(byte)(idBytes.Length >> 24),
+				(byte)(idBytes.Length >> 16),
+				(byte)(idBytes.Length >> 8),
+				(byte)(idBytes.Length));
+			this.Update(idBytes);
+		}
+
+		private void UpdateWithPublicKey(
+			PgpPublicKey key)
+		{
+			byte[] keyBytes = GetEncodedPublicKey(key);
+
+			this.Update(
+				(byte) 0x99,
+				(byte)(keyBytes.Length >> 8),
+				(byte)(keyBytes.Length));
+			this.Update(keyBytes);
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpSignatureList.cs b/crypto/src/openpgp/PgpSignatureList.cs
new file mode 100644
index 000000000..61976fc4f
--- /dev/null
+++ b/crypto/src/openpgp/PgpSignatureList.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>A list of PGP signatures - normally in the signature block after literal data.</remarks>
+    public class PgpSignatureList
+		: PgpObject
+    {
+        private PgpSignature[] sigs;
+
+		public PgpSignatureList(
+            PgpSignature[] sigs)
+        {
+            this.sigs = (PgpSignature[]) sigs.Clone();
+        }
+
+		public PgpSignatureList(
+            PgpSignature sig)
+        {
+			this.sigs = new PgpSignature[]{ sig };
+        }
+
+		public PgpSignature this[int index]
+		{
+			get { return sigs[index]; }
+		}
+
+		[Obsolete("Use 'object[index]' syntax instead")]
+		public PgpSignature Get(
+            int index)
+        {
+            return this[index];
+        }
+
+		[Obsolete("Use 'Count' property instead")]
+		public int Size
+        {
+			get { return sigs.Length; }
+        }
+
+		public int Count
+		{
+			get { return sigs.Length; }
+		}
+
+		public bool IsEmpty
+        {
+			get { return (sigs.Length == 0); }
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
new file mode 100644
index 000000000..4adf64012
--- /dev/null
+++ b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Generator for signature subpackets.</remarks>
+    public class PgpSignatureSubpacketGenerator
+    {
+        private IList list = Platform.CreateArrayList();
+
+		public void SetRevocable(
+            bool	isCritical,
+            bool	isRevocable)
+        {
+            list.Add(new Revocable(isCritical, isRevocable));
+        }
+
+		public void SetExportable(
+            bool	isCritical,
+            bool	isExportable)
+        {
+            list.Add(new Exportable(isCritical, isExportable));
+        }
+
+		/// <summary>
+		/// Add a TrustSignature packet to the signature. The values for depth and trust are largely
+		/// installation dependent but there are some guidelines in RFC 4880 - 5.2.3.13.
+		/// </summary>
+		/// <param name="isCritical">true if the packet is critical.</param>
+		/// <param name="depth">depth level.</param>
+		/// <param name="trustAmount">trust amount.</param>
+		public void SetTrust(
+            bool	isCritical,
+            int		depth,
+            int		trustAmount)
+        {
+            list.Add(new TrustSignature(isCritical, depth, trustAmount));
+        }
+
+		/// <summary>
+		/// Set the number of seconds a key is valid for after the time of its creation.
+		/// A value of zero means the key never expires.
+		/// </summary>
+		/// <param name="isCritical">True, if should be treated as critical, false otherwise.</param>
+		/// <param name="seconds">The number of seconds the key is valid, or zero if no expiry.</param>
+        public void SetKeyExpirationTime(
+            bool	isCritical,
+            long	seconds)
+        {
+            list.Add(new KeyExpirationTime(isCritical, seconds));
+        }
+
+		/// <summary>
+		/// Set the number of seconds a signature is valid for after the time of its creation.
+		/// A value of zero means the signature never expires.
+		/// </summary>
+		/// <param name="isCritical">True, if should be treated as critical, false otherwise.</param>
+		/// <param name="seconds">The number of seconds the signature is valid, or zero if no expiry.</param>
+        public void SetSignatureExpirationTime(
+            bool	isCritical,
+            long	seconds)
+        {
+            list.Add(new SignatureExpirationTime(isCritical, seconds));
+        }
+
+		/// <summary>
+		/// Set the creation time for the signature.
+		/// <p>
+		/// Note: this overrides the generation of a creation time when the signature
+		/// is generated.</p>
+		/// </summary>
+		public void SetSignatureCreationTime(
+			bool		isCritical,
+			DateTime	date)
+		{
+			list.Add(new SignatureCreationTime(isCritical, date));
+		}
+
+		public void SetPreferredHashAlgorithms(
+            bool	isCritical,
+            int[]	algorithms)
+        {
+            list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredHashAlgorithms, isCritical, algorithms));
+        }
+
+		public void SetPreferredSymmetricAlgorithms(
+            bool	isCritical,
+            int[]	algorithms)
+        {
+            list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredSymmetricAlgorithms, isCritical, algorithms));
+        }
+
+		public void SetPreferredCompressionAlgorithms(
+            bool	isCritical,
+            int[]	algorithms)
+        {
+            list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredCompressionAlgorithms, isCritical, algorithms));
+        }
+
+		public void SetKeyFlags(
+            bool	isCritical,
+            int		flags)
+        {
+            list.Add(new KeyFlags(isCritical, flags));
+        }
+
+		public void SetSignerUserId(
+            bool	isCritical,
+            string	userId)
+        {
+            if (userId == null)
+                throw new ArgumentNullException("userId");
+
+			list.Add(new SignerUserId(isCritical, userId));
+        }
+
+		public void SetEmbeddedSignature(
+			bool			isCritical,
+			PgpSignature	pgpSignature)
+		{
+			byte[] sig = pgpSignature.GetEncoded();
+			byte[] data;
+
+			// TODO Should be >= ?
+			if (sig.Length - 1 > 256)
+			{
+				data = new byte[sig.Length - 3];
+			}
+			else
+			{
+				data = new byte[sig.Length - 2];
+			}
+
+			Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length);
+
+			list.Add(new EmbeddedSignature(isCritical, data));
+		}
+
+		public void SetPrimaryUserId(
+            bool	isCritical,
+            bool	isPrimaryUserId)
+        {
+            list.Add(new PrimaryUserId(isCritical, isPrimaryUserId));
+        }
+
+		public void SetNotationData(
+			bool	isCritical,
+			bool	isHumanReadable,
+			string	notationName,
+			string	notationValue)
+		{
+			list.Add(new NotationData(isCritical, isHumanReadable, notationName, notationValue));
+		}
+
+		/// <summary>
+		/// Sets revocation reason sub packet
+		/// </summary>	    
+		public void SetRevocationReason(bool isCritical, RevocationReasonTag reason,
+			string description)
+		{
+			list.Add(new RevocationReason(isCritical, reason, description));
+		}
+
+		/// <summary>
+		/// Sets revocation key sub packet
+		/// </summary>	
+		public void SetRevocationKey(bool isCritical, PublicKeyAlgorithmTag keyAlgorithm, byte[] fingerprint)
+		{
+			list.Add(new RevocationKey(isCritical, RevocationKeyTag.ClassDefault, keyAlgorithm, fingerprint));
+		}
+
+		/// <summary>
+		/// Sets issuer key sub packet
+		/// </summary>	
+		public void SetIssuerKeyID(bool isCritical, long keyID)
+		{
+			list.Add(new IssuerKeyId(isCritical, keyID));
+		}    
+
+		public PgpSignatureSubpacketVector Generate()
+        {
+            SignatureSubpacket[] a = new SignatureSubpacket[list.Count];
+            for (int i = 0; i < list.Count; ++i)
+            {
+                a[i] = (SignatureSubpacket)list[i];
+            }
+            return new PgpSignatureSubpacketVector(a);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
new file mode 100644
index 000000000..68fe4b594
--- /dev/null
+++ b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Container for a list of signature subpackets.</remarks>
+    public class PgpSignatureSubpacketVector
+    {
+        private readonly SignatureSubpacket[] packets;
+
+		internal PgpSignatureSubpacketVector(
+            SignatureSubpacket[] packets)
+        {
+            this.packets = packets;
+        }
+
+		public SignatureSubpacket GetSubpacket(
+            SignatureSubpacketTag type)
+        {
+            for (int i = 0; i != packets.Length; i++)
+            {
+                if (packets[i].SubpacketType == type)
+                {
+                    return packets[i];
+                }
+            }
+
+			return null;
+        }
+
+		/**
+		 * Return true if a particular subpacket type exists.
+		 *
+		 * @param type type to look for.
+		 * @return true if present, false otherwise.
+		 */
+		public bool HasSubpacket(
+			SignatureSubpacketTag type)
+		{
+			return GetSubpacket(type) != null;
+		}
+
+		/**
+		 * Return all signature subpackets of the passed in type.
+		 * @param type subpacket type code
+		 * @return an array of zero or more matching subpackets.
+		 */
+		public SignatureSubpacket[] GetSubpackets(
+			SignatureSubpacketTag type)
+		{
+            int count = 0;
+            for (int i = 0; i < packets.Length; ++i)
+            {
+                if (packets[i].SubpacketType == type)
+                {
+                    ++count;
+                }
+            }
+
+            SignatureSubpacket[] result = new SignatureSubpacket[count];
+
+            int pos = 0;
+            for (int i = 0; i < packets.Length; ++i)
+            {
+                if (packets[i].SubpacketType == type)
+                {
+                    result[pos++] = packets[i];
+                }
+            }
+
+            return result;
+        }
+
+        public NotationData[] GetNotationDataOccurences()
+		{
+			SignatureSubpacket[] notations = GetSubpackets(SignatureSubpacketTag.NotationData);
+			NotationData[] vals = new NotationData[notations.Length];
+
+			for (int i = 0; i < notations.Length; i++)
+			{
+				vals[i] = (NotationData) notations[i];
+			}
+
+			return vals;
+		}
+
+		public long GetIssuerKeyId()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerKeyId);
+
+            return p == null ? 0 : ((IssuerKeyId) p).KeyId;
+        }
+
+		public bool HasSignatureCreationTime()
+		{
+			return GetSubpacket(SignatureSubpacketTag.CreationTime) != null;
+		}
+
+		public DateTime GetSignatureCreationTime()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime);
+
+            if (p == null)
+            {
+                throw new PgpException("SignatureCreationTime not available");
+            }
+
+            return ((SignatureCreationTime)p).GetTime();
+        }
+
+		/// <summary>
+		/// Return the number of seconds a signature is valid for after its creation date.
+		/// A value of zero means the signature never expires.
+		/// </summary>
+		/// <returns>Seconds a signature is valid for.</returns>
+        public long GetSignatureExpirationTime()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.ExpireTime);
+
+			return p == null ? 0 : ((SignatureExpirationTime) p).Time;
+        }
+
+		/// <summary>
+		/// Return the number of seconds a key is valid for after its creation date.
+		/// A value of zero means the key never expires.
+		/// </summary>
+		/// <returns>Seconds a signature is valid for.</returns>
+        public long GetKeyExpirationTime()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyExpireTime);
+
+			return p == null ? 0 : ((KeyExpirationTime) p).Time;
+        }
+
+		public int[] GetPreferredHashAlgorithms()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredHashAlgorithms);
+
+			return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+        }
+
+		public int[] GetPreferredSymmetricAlgorithms()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredSymmetricAlgorithms);
+
+            return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+        }
+
+		public int[] GetPreferredCompressionAlgorithms()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredCompressionAlgorithms);
+
+            return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+        }
+
+		public int GetKeyFlags()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyFlags);
+
+            return p == null ? 0 : ((KeyFlags) p).Flags;
+        }
+
+		public string GetSignerUserId()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignerUserId);
+
+			return p == null ? null : ((SignerUserId) p).GetId();
+        }
+
+		public bool IsPrimaryUserId()
+		{
+			PrimaryUserId primaryId = (PrimaryUserId)
+				this.GetSubpacket(SignatureSubpacketTag.PrimaryUserId);
+
+			if (primaryId != null)
+			{
+				return primaryId.IsPrimaryUserId();
+			}
+
+			return false;
+		}
+
+		public SignatureSubpacketTag[] GetCriticalTags()
+        {
+            int count = 0;
+            for (int i = 0; i != packets.Length; i++)
+            {
+                if (packets[i].IsCritical())
+                {
+                    count++;
+                }
+            }
+
+			SignatureSubpacketTag[] list = new SignatureSubpacketTag[count];
+
+			count = 0;
+
+			for (int i = 0; i != packets.Length; i++)
+            {
+                if (packets[i].IsCritical())
+                {
+                    list[count++] = packets[i].SubpacketType;
+                }
+            }
+
+			return list;
+        }
+
+		[Obsolete("Use 'Count' property instead")]
+		public int Size
+		{
+			get { return packets.Length; }
+		}
+
+		/// <summary>Return the number of packets this vector contains.</summary>
+		public int Count
+		{
+			get { return packets.Length; }
+		}
+
+		internal SignatureSubpacket[] ToSubpacketArray()
+        {
+            return packets;
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs b/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
new file mode 100644
index 000000000..4cdbeda54
--- /dev/null
+++ b/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
@@ -0,0 +1,81 @@
+using Org.BouncyCastle.Bcpg.Attr;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Container for a list of user attribute subpackets.</remarks>
+    public class PgpUserAttributeSubpacketVector
+    {
+        private readonly UserAttributeSubpacket[] packets;
+
+		internal PgpUserAttributeSubpacketVector(
+            UserAttributeSubpacket[] packets)
+        {
+            this.packets = packets;
+        }
+
+		public UserAttributeSubpacket GetSubpacket(
+            UserAttributeSubpacketTag type)
+        {
+            for (int i = 0; i != packets.Length; i++)
+            {
+                if (packets[i].SubpacketType == type)
+                {
+                    return packets[i];
+                }
+            }
+
+			return null;
+        }
+
+		public ImageAttrib GetImageAttribute()
+        {
+            UserAttributeSubpacket p = GetSubpacket(UserAttributeSubpacketTag.ImageAttribute);
+
+            return p == null ? null : (ImageAttrib) p;
+        }
+
+		internal UserAttributeSubpacket[] ToSubpacketArray()
+        {
+            return packets;
+        }
+
+		public override bool Equals(
+            object obj)
+        {
+            if (obj == this)
+                return true;
+
+			PgpUserAttributeSubpacketVector other = obj as PgpUserAttributeSubpacketVector;
+
+			if (other == null)
+				return false;
+
+			if (other.packets.Length != packets.Length)
+            {
+                return false;
+            }
+
+			for (int i = 0; i != packets.Length; i++)
+            {
+                if (!other.packets[i].Equals(packets[i]))
+                {
+                    return false;
+                }
+            }
+
+			return true;
+        }
+
+		public override int GetHashCode()
+        {
+            int code = 0;
+
+			foreach (object o in packets)
+			{
+				code ^= o.GetHashCode();
+			}
+
+			return code;
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
new file mode 100644
index 000000000..e22381bb1
--- /dev/null
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -0,0 +1,428 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Basic utility class.</remarks>
+    public sealed class PgpUtilities
+    {
+        private PgpUtilities()
+        {
+        }
+
+		public static MPInteger[] DsaSigToMpi(
+			byte[] encoding)
+		{
+			DerInteger i1, i2;
+
+			try
+			{
+				Asn1Sequence s = (Asn1Sequence) Asn1Object.FromByteArray(encoding);
+
+				i1 = (DerInteger) s[0];
+				i2 = (DerInteger) s[1];
+			}
+			catch (IOException e)
+			{
+				throw new PgpException("exception encoding signature", e);
+			}
+
+			return new MPInteger[]{ new MPInteger(i1.Value), new MPInteger(i2.Value) };
+		}
+
+		public static MPInteger[] RsaSigToMpi(
+			byte[] encoding)
+		{
+			return new MPInteger[]{ new MPInteger(new BigInteger(1, encoding)) };
+		}
+
+		public static string GetDigestName(
+            HashAlgorithmTag hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+				case HashAlgorithmTag.Sha1:
+					return "SHA1";
+				case HashAlgorithmTag.MD2:
+					return "MD2";
+				case HashAlgorithmTag.MD5:
+					return "MD5";
+				case HashAlgorithmTag.RipeMD160:
+					return "RIPEMD160";
+				case HashAlgorithmTag.Sha224:
+					return "SHA224";
+				case HashAlgorithmTag.Sha256:
+					return "SHA256";
+				case HashAlgorithmTag.Sha384:
+					return "SHA384";
+				case HashAlgorithmTag.Sha512:
+					return "SHA512";
+				default:
+					throw new PgpException("unknown hash algorithm tag in GetDigestName: " + hashAlgorithm);
+			}
+        }
+
+		public static string GetSignatureName(
+            PublicKeyAlgorithmTag	keyAlgorithm,
+            HashAlgorithmTag		hashAlgorithm)
+        {
+            string encAlg;
+			switch (keyAlgorithm)
+            {
+				case PublicKeyAlgorithmTag.RsaGeneral:
+				case PublicKeyAlgorithmTag.RsaSign:
+					encAlg = "RSA";
+					break;
+				case PublicKeyAlgorithmTag.Dsa:
+					encAlg = "DSA";
+					break;
+				case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
+				case PublicKeyAlgorithmTag.ElGamalGeneral:
+					encAlg = "ElGamal";
+					break;
+				default:
+					throw new PgpException("unknown algorithm tag in signature:" + keyAlgorithm);
+            }
+
+			return GetDigestName(hashAlgorithm) + "with" + encAlg;
+        }
+
+		public static string GetSymmetricCipherName(
+            SymmetricKeyAlgorithmTag algorithm)
+        {
+            switch (algorithm)
+            {
+				case SymmetricKeyAlgorithmTag.Null:
+					return null;
+				case SymmetricKeyAlgorithmTag.TripleDes:
+					return "DESEDE";
+				case SymmetricKeyAlgorithmTag.Idea:
+					return "IDEA";
+				case SymmetricKeyAlgorithmTag.Cast5:
+					return "CAST5";
+				case SymmetricKeyAlgorithmTag.Blowfish:
+					return "Blowfish";
+				case SymmetricKeyAlgorithmTag.Safer:
+					return "SAFER";
+				case SymmetricKeyAlgorithmTag.Des:
+					return "DES";
+				case SymmetricKeyAlgorithmTag.Aes128:
+					return "AES";
+				case SymmetricKeyAlgorithmTag.Aes192:
+					return "AES";
+				case SymmetricKeyAlgorithmTag.Aes256:
+					return "AES";
+				case SymmetricKeyAlgorithmTag.Twofish:
+					return "Twofish";
+				default:
+					throw new PgpException("unknown symmetric algorithm: " + algorithm);
+            }
+        }
+
+		public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
+        {
+            int keySize;
+            switch (algorithm)
+            {
+                case SymmetricKeyAlgorithmTag.Des:
+                    keySize = 64;
+                    break;
+                case SymmetricKeyAlgorithmTag.Idea:
+                case SymmetricKeyAlgorithmTag.Cast5:
+                case SymmetricKeyAlgorithmTag.Blowfish:
+                case SymmetricKeyAlgorithmTag.Safer:
+                case SymmetricKeyAlgorithmTag.Aes128:
+                    keySize = 128;
+                    break;
+                case SymmetricKeyAlgorithmTag.TripleDes:
+                case SymmetricKeyAlgorithmTag.Aes192:
+                    keySize = 192;
+                    break;
+                case SymmetricKeyAlgorithmTag.Aes256:
+                case SymmetricKeyAlgorithmTag.Twofish:
+                    keySize = 256;
+                    break;
+                default:
+                    throw new PgpException("unknown symmetric algorithm: " + algorithm);
+            }
+
+			return keySize;
+        }
+
+		public static KeyParameter MakeKey(
+			SymmetricKeyAlgorithmTag	algorithm,
+			byte[]						keyBytes)
+		{
+			string algName = GetSymmetricCipherName(algorithm);
+
+			return ParameterUtilities.CreateKeyParameter(algName, keyBytes);
+		}
+
+		public static KeyParameter MakeRandomKey(
+            SymmetricKeyAlgorithmTag	algorithm,
+            SecureRandom				random)
+        {
+            int keySize = GetKeySize(algorithm);
+            byte[] keyBytes = new byte[(keySize + 7) / 8];
+            random.NextBytes(keyBytes);
+			return MakeKey(algorithm, keyBytes);
+        }
+
+		public static KeyParameter MakeKeyFromPassPhrase(
+            SymmetricKeyAlgorithmTag	algorithm,
+            S2k							s2k,
+            char[]						passPhrase)
+        {
+			int keySize = GetKeySize(algorithm);
+			byte[] pBytes = Strings.ToByteArray(new string(passPhrase));
+			byte[] keyBytes = new byte[(keySize + 7) / 8];
+
+			int generatedBytes = 0;
+            int loopCount = 0;
+
+			while (generatedBytes < keyBytes.Length)
+            {
+				IDigest digest;
+				if (s2k != null)
+                {
+					string digestName = GetDigestName(s2k.HashAlgorithm);
+
+                    try
+                    {
+						digest = DigestUtilities.GetDigest(digestName);
+                    }
+                    catch (Exception e)
+                    {
+                        throw new PgpException("can't find S2k digest", e);
+                    }
+
+					for (int i = 0; i != loopCount; i++)
+                    {
+                        digest.Update(0);
+                    }
+
+					byte[] iv = s2k.GetIV();
+
+					switch (s2k.Type)
+                    {
+						case S2k.Simple:
+							digest.BlockUpdate(pBytes, 0, pBytes.Length);
+							break;
+						case S2k.Salted:
+							digest.BlockUpdate(iv, 0, iv.Length);
+							digest.BlockUpdate(pBytes, 0, pBytes.Length);
+							break;
+						case S2k.SaltedAndIterated:
+							long count = s2k.IterationCount;
+							digest.BlockUpdate(iv, 0, iv.Length);
+							digest.BlockUpdate(pBytes, 0, pBytes.Length);
+
+							count -= iv.Length + pBytes.Length;
+
+							while (count > 0)
+							{
+								if (count < iv.Length)
+								{
+									digest.BlockUpdate(iv, 0, (int)count);
+									break;
+								}
+								else
+								{
+									digest.BlockUpdate(iv, 0, iv.Length);
+									count -= iv.Length;
+								}
+
+								if (count < pBytes.Length)
+								{
+									digest.BlockUpdate(pBytes, 0, (int)count);
+									count = 0;
+								}
+								else
+								{
+									digest.BlockUpdate(pBytes, 0, pBytes.Length);
+									count -= pBytes.Length;
+								}
+							}
+							break;
+						default:
+							throw new PgpException("unknown S2k type: " + s2k.Type);
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        digest = DigestUtilities.GetDigest("MD5");
+
+						for (int i = 0; i != loopCount; i++)
+                        {
+                            digest.Update(0);
+                        }
+
+						digest.BlockUpdate(pBytes, 0, pBytes.Length);
+                    }
+                    catch (Exception e)
+                    {
+                        throw new PgpException("can't find MD5 digest", e);
+                    }
+                }
+
+				byte[] dig = DigestUtilities.DoFinal(digest);
+
+				if (dig.Length > (keyBytes.Length - generatedBytes))
+                {
+                    Array.Copy(dig, 0, keyBytes, generatedBytes, keyBytes.Length - generatedBytes);
+                }
+                else
+                {
+                    Array.Copy(dig, 0, keyBytes, generatedBytes, dig.Length);
+                }
+
+				generatedBytes += dig.Length;
+
+				loopCount++;
+            }
+
+			Array.Clear(pBytes, 0, pBytes.Length);
+
+			return MakeKey(algorithm, keyBytes);
+        }
+
+#if !PORTABLE
+		/// <summary>Write out the passed in file as a literal data packet.</summary>
+        public static void WriteFileToLiteralData(
+            Stream		output,
+            char		fileType,
+            FileInfo	file)
+        {
+            PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
+			Stream pOut = lData.Open(output, fileType, file.Name, file.Length, file.LastWriteTime);
+			PipeFileContents(file, pOut, 4096);
+        }
+
+		/// <summary>Write out the passed in file as a literal data packet in partial packet format.</summary>
+        public static void WriteFileToLiteralData(
+            Stream		output,
+            char		fileType,
+            FileInfo	file,
+            byte[]		buffer)
+        {
+            PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
+            Stream pOut = lData.Open(output, fileType, file.Name, file.LastWriteTime, buffer);
+			PipeFileContents(file, pOut, buffer.Length);
+        }
+
+		private static void PipeFileContents(FileInfo file, Stream pOut, int bufSize)
+		{
+			FileStream inputStream = file.OpenRead();
+			byte[] buf = new byte[bufSize];
+
+			int len;
+            while ((len = inputStream.Read(buf, 0, buf.Length)) > 0)
+            {
+                pOut.Write(buf, 0, len);
+            }
+
+			pOut.Close();
+			inputStream.Close();
+		}
+#endif
+
+		private const int ReadAhead = 60;
+
+		private static bool IsPossiblyBase64(
+            int ch)
+        {
+            return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
+                    || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
+                    || (ch == '\r') || (ch == '\n');
+        }
+
+		/// <summary>
+		/// Return either an ArmoredInputStream or a BcpgInputStream based on whether
+		/// the initial characters of the stream are binary PGP encodings or not.
+		/// </summary>
+        public static Stream GetDecoderStream(
+            Stream inputStream)
+        {
+			// TODO Remove this restriction?
+			if (!inputStream.CanSeek)
+				throw new ArgumentException("inputStream must be seek-able", "inputStream");
+
+			long markedPos = inputStream.Position;
+
+			int ch = inputStream.ReadByte();
+            if ((ch & 0x80) != 0)
+            {
+                inputStream.Position = markedPos;
+
+				return inputStream;
+            }
+            else
+            {
+                if (!IsPossiblyBase64(ch))
+                {
+                    inputStream.Position = markedPos;
+
+					return new ArmoredInputStream(inputStream);
+                }
+
+				byte[]	buf = new byte[ReadAhead];
+                int		count = 1;
+                int		index = 1;
+
+				buf[0] = (byte)ch;
+                while (count != ReadAhead && (ch = inputStream.ReadByte()) >= 0)
+                {
+                    if (!IsPossiblyBase64(ch))
+                    {
+                        inputStream.Position = markedPos;
+
+						return new ArmoredInputStream(inputStream);
+                    }
+
+					if (ch != '\n' && ch != '\r')
+                    {
+                        buf[index++] = (byte)ch;
+                    }
+
+					count++;
+                }
+
+				inputStream.Position = markedPos;
+
+				//
+                // nothing but new lines, little else, assume regular armoring
+                //
+                if (count < 4)
+                {
+                    return new ArmoredInputStream(inputStream);
+                }
+
+				//
+                // test our non-blank data
+                //
+                byte[] firstBlock = new byte[8];
+				Array.Copy(buf, 0, firstBlock, 0, firstBlock.Length);
+				byte[] decoded = Base64.Decode(firstBlock);
+
+				//
+                // it's a base64 PGP block.
+                //
+				bool hasHeaders = (decoded[0] & 0x80) == 0;
+
+				return new ArmoredInputStream(inputStream, hasHeaders);
+            }
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpV3SignatureGenerator.cs b/crypto/src/openpgp/PgpV3SignatureGenerator.cs
new file mode 100644
index 000000000..fc8b42df2
--- /dev/null
+++ b/crypto/src/openpgp/PgpV3SignatureGenerator.cs
@@ -0,0 +1,199 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+	/// <remarks>Generator for old style PGP V3 Signatures.</remarks>
+	// TODO Should be able to implement ISigner?
+	public class PgpV3SignatureGenerator
+    {
+        private PublicKeyAlgorithmTag keyAlgorithm;
+        private HashAlgorithmTag hashAlgorithm;
+        private PgpPrivateKey privKey;
+        private ISigner sig;
+        private IDigest    dig;
+        private int signatureType;
+        private byte lastb;
+
+		/// <summary>Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.</summary>
+        public PgpV3SignatureGenerator(
+            PublicKeyAlgorithmTag	keyAlgorithm,
+            HashAlgorithmTag		hashAlgorithm)
+        {
+            this.keyAlgorithm = keyAlgorithm;
+            this.hashAlgorithm = hashAlgorithm;
+
+            dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
+            sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+        }
+
+		/// <summary>Initialise the generator for signing.</summary>
+		public void InitSign(
+			int				sigType,
+			PgpPrivateKey	key)
+		{
+			InitSign(sigType, key, null);
+		}
+
+		/// <summary>Initialise the generator for signing.</summary>
+        public void InitSign(
+            int				sigType,
+            PgpPrivateKey	key,
+			SecureRandom	random)
+        {
+            this.privKey = key;
+            this.signatureType = sigType;
+
+			try
+            {
+				ICipherParameters cp = key.Key;
+				if (random != null)
+				{
+					cp = new ParametersWithRandom(key.Key, random);
+				}
+
+				sig.Init(true, cp);
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new PgpException("invalid key.", e);
+            }
+
+			dig.Reset();
+            lastb = 0;
+        }
+
+		public void Update(
+            byte b)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+				doCanonicalUpdateByte(b);
+            }
+            else
+            {
+				doUpdateByte(b);
+            }
+        }
+
+		private void doCanonicalUpdateByte(
+			byte b)
+		{
+			if (b == '\r')
+			{
+				doUpdateCRLF();
+			}
+			else if (b == '\n')
+			{
+				if (lastb != '\r')
+				{
+					doUpdateCRLF();
+				}
+			}
+			else
+			{
+				doUpdateByte(b);
+			}
+
+			lastb = b;
+		}
+
+		private void doUpdateCRLF()
+		{
+			doUpdateByte((byte)'\r');
+			doUpdateByte((byte)'\n');
+		}
+
+		private void doUpdateByte(
+			byte b)
+		{
+			sig.Update(b);
+			dig.Update(b);
+		}
+
+		public void Update(
+            byte[] b)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                for (int i = 0; i != b.Length; i++)
+                {
+                    doCanonicalUpdateByte(b[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(b, 0, b.Length);
+                dig.BlockUpdate(b, 0, b.Length);
+            }
+        }
+
+		public void Update(
+            byte[]	b,
+            int		off,
+            int		len)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                int finish = off + len;
+
+				for (int i = off; i != finish; i++)
+                {
+                    doCanonicalUpdateByte(b[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(b, off, len);
+                dig.BlockUpdate(b, off, len);
+            }
+        }
+
+		/// <summary>Return the one pass header associated with the current signature.</summary>
+        public PgpOnePassSignature GenerateOnePassVersion(
+            bool isNested)
+        {
+            return new PgpOnePassSignature(
+				new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested));
+        }
+
+		/// <summary>Return a V3 signature object containing the current signature state.</summary>
+        public PgpSignature Generate()
+        {
+            long creationTime = DateTimeUtilities.CurrentUnixMs() / 1000L;
+
+			byte[] hData = new byte[]
+			{
+				(byte) signatureType,
+				(byte)(creationTime >> 24),
+				(byte)(creationTime >> 16),
+				(byte)(creationTime >> 8),
+				(byte) creationTime
+			};
+
+			sig.BlockUpdate(hData, 0, hData.Length);
+            dig.BlockUpdate(hData, 0, hData.Length);
+
+			byte[] sigBytes = sig.GenerateSignature();
+			byte[] digest = DigestUtilities.DoFinal(dig);
+			byte[] fingerPrint = new byte[]{ digest[0], digest[1] };
+
+			// an RSA signature
+			bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign
+                || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral;
+
+			MPInteger[] sigValues = isRsa
+				?	PgpUtilities.RsaSigToMpi(sigBytes)
+				:	PgpUtilities.DsaSigToMpi(sigBytes);
+
+			return new PgpSignature(
+				new SignaturePacket(3, signatureType, privKey.KeyId, keyAlgorithm,
+					hashAlgorithm, creationTime * 1000L, fingerPrint, sigValues));
+        }
+    }
+}
diff --git a/crypto/src/openpgp/WrappedGeneratorStream.cs b/crypto/src/openpgp/WrappedGeneratorStream.cs
new file mode 100644
index 000000000..baad0d429
--- /dev/null
+++ b/crypto/src/openpgp/WrappedGeneratorStream.cs
@@ -0,0 +1,28 @@
+using System.IO;
+
+using Org.BouncyCastle.Asn1.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    public class WrappedGeneratorStream
+        : FilterStream
+    {
+        private readonly IStreamGenerator gen;
+
+        public WrappedGeneratorStream(
+            IStreamGenerator gen,
+            Stream str)
+            : base(str)
+        {
+            this.gen = gen;
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                gen.Close();
+            }
+        }
+    }
+}