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