/// <summary> /// Build PGP-encrypting (raw/binary format) stream around the provided output stream /// </summary> /// <param name="publickeysource">Input stream containing public keyring bundle</param> /// <param name="publickeyuserid">UserID of key to be used within keyring bundle (if null/empty, first available key in ring will be used)</param> /// <param name="encryptedoutput">Output stream to receive raw/binary encrypted data</param> /// <returns> /// A StreamStack object, into which cleartext data can be written (resulting in encrypted data being written to encryptedoutput) /// </returns> /// <remarks> /// - Caller is responsible for disposing of returned StreamStack (BEFORE disposing of original encryptedoutput Stream) /// - Caller is also still responsible for disposing of encryptedinput stream (AFTER disposing of returned StreamStack) /// </remarks> public static async Task <StreamStack> GetEncryptionStreamRaw(Stream publickeysource, string publickeyuserid, Stream encryptedoutput) { var encryptionstream = new StreamStack(); try { // Create encrypted data generator using public key extracted from provided source: var pgpEncDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom()); pgpEncDataGen.AddMethod(await ExtractPublicKey(publickeysource, publickeyuserid)); // Create encrypted data generator stream around destination stream and push on to return value stack: encryptionstream.PushStream(pgpEncDataGen.Open(encryptedoutput, new byte[1024])); // Create compressed data generator stream around encryption stream and push on to return value stack: var comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip); encryptionstream.PushStream(comData.Open(encryptionstream.GetStream())); // Create literal data generator stream around compression stream and push on to return value stack: var lData = new PgpLiteralDataGenerator(); encryptionstream.PushStream(lData.Open(encryptionstream.GetStream(), PgpLiteralData.Binary, string.Empty, DateTime.UtcNow, new byte[1024])); // Return stream object (data written to stream at top of stack will be literalized -> compressed -> encrypted): return(encryptionstream); } catch { encryptionstream.Dispose(); throw; } }
/// <summary> /// Create PGP-decrypting stream around a PGP-encrypted input source stream /// </summary> /// <param name="privatekeysource">Input stream containing private keyring bundle</param> /// <param name="privatekeypassphrase">Passphrase to access private key (if required)</param> /// <param name="encryptedinput">Input stream containing source data to be decrypted</param> /// <returns> /// A StreamStack object, from which decrypted data can be read /// </returns> /// <remarks> /// - Caller is responsible for disposing of returned StreamStack (BEFORE disposing of original encryptedinput Stream) /// - Caller is also still responsible for disposing of encryptedinput stream (AFTER disposing of returned StreamStack) /// - Source data stream will be read synchronously (due to limitation in PGP library; all source data must be available to perform decryption, /// but source library does not provide async option). If this is a concern, caller should consider asynchronously pulling source data into local /// stream first and passing local stream to this function (if increased use of memory outweighs waiting for blocked thread to perform I/O) /// </remarks> public static async Task <StreamStack> GetDecryptionStream(Stream privatekeysource, string privatekeypassphrase, Stream encryptedinput) { var decryptionstream = new StreamStack(); try { // Open decoder stream and push on to return value stack: decryptionstream.PushStream(PgpUtilities.GetDecoderStream(encryptedinput)); // Create object factory (using stream at top of stack) to extract encrypted data: var factory = new PgpObjectFactory(decryptionstream.GetStream()); var encrypteddata = factory.GetEncryptedData() ?? throw new ArgumentException("No PGP-encrypted data found"); // Extract private key from key source (using key ID required by encrypted data object), use to open // clear stream and push on to return value stack: decryptionstream.PushStream(encrypteddata.GetDataStream(await ExtractPrivateKey(privatekeysource, privatekeypassphrase, encrypteddata.KeyId))); // Create new factory from clear stream and extract first PGP object: factory = new PgpObjectFactory(decryptionstream.GetStream()); var message = factory.NextPgpObject(); while (message != null) { // If object is literal data, push de-literalization stream on to return value stack and return: if (message is PgpLiteralData cleardata) { decryptionstream.PushStream(cleardata.GetInputStream()); return(decryptionstream); } // Otherwise if this is compressed data, we need to push decompression stream on to return value // stack, then create a new object factory to extract data from decompressed source: else if (message is PgpCompressedData compresseddata) { decryptionstream.PushStream(compresseddata.GetDataStream()); factory = new PgpObjectFactory(decryptionstream.GetStream()); } // Extract next message object from factory and continue: message = factory.NextPgpObject(); } // If this point is reached, no literal packet was ever found throw new ArgumentException("Invalid PGP data (no payload found)"); } catch { decryptionstream.Dispose(); throw; } }