<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />

ArmoredOutputStream

using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.IO; using System; using System.Collections.Generic; using System.IO; using System.Reflection; namespace Org.BouncyCastle.Bcpg { public class ArmoredOutputStream : BaseOutputStream { public static readonly string HeaderVersion = "Version"; private static readonly byte[] encodingTable = new byte[64] { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 }; private readonly Stream outStream; private byte[] buf = new byte[3]; private int bufPtr; private Crc24 crc = new Crc24(); private int chunkCount; private int lastb; private bool start = true; private bool clearText; private bool newLine; private string type; private static readonly string NewLine = Environment.NewLine; private static readonly string headerStart = "-----BEGIN PGP "; private static readonly string headerTail = "-----"; private static readonly string footerStart = "-----END PGP "; private static readonly string footerTail = "-----"; private static readonly string Version = CreateVersion(); private readonly Dictionary<string, List<string>> m_headers; private unsafe static void Encode(Stream outStream, byte[] data, int len) { Span<byte> span = new Span<byte>(stackalloc byte[4], 4); int num = data[0]; span[0] = encodingTable[(num >> 2) & 63]; switch (len) { case 1: span[1] = encodingTable[(num << 4) & 63]; span[2] = 61; span[3] = 61; break; case 2: { int num4 = data[1]; span[1] = encodingTable[((num << 4) | (num4 >> 4)) & 63]; span[2] = encodingTable[(num4 << 2) & 63]; span[3] = 61; break; } case 3: { int num2 = data[1]; int num3 = data[2]; span[1] = encodingTable[((num << 4) | (num2 >> 4)) & 63]; span[2] = encodingTable[((num2 << 2) | (num3 >> 6)) & 63]; span[3] = encodingTable[num3 & 63]; break; } } outStream.Write(span); } private unsafe static void Encode3(Stream outStream, byte[] data) { int num = data[0]; int num2 = data[1]; int num3 = data[2]; Span<byte> span = new Span<byte>(stackalloc byte[4], 4); span[0] = encodingTable[(num >> 2) & 63]; span[1] = encodingTable[((num << 4) | (num2 >> 4)) & 63]; span[2] = encodingTable[((num2 << 2) | (num3 >> 6)) & 63]; span[3] = encodingTable[num3 & 63]; outStream.Write(span); } private static string CreateVersion() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); AssemblyTitleAttribute customAttribute = executingAssembly.GetCustomAttribute<AssemblyTitleAttribute>(); AssemblyInformationalVersionAttribute customAttribute2 = executingAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>(); if (customAttribute == null || customAttribute2 == null) return "BouncyCastle (unknown version)"; return customAttribute.Title + " v" + customAttribute2.InformationalVersion; } public ArmoredOutputStream(Stream outStream) : this(outStream, true) { } public ArmoredOutputStream(Stream outStream, bool addVersionHeader) { this.outStream = outStream; m_headers = new Dictionary<string, List<string>>(); if (addVersionHeader) SetHeader(HeaderVersion, Version); } public ArmoredOutputStream(Stream outStream, IDictionary<string, string> headers) : this(outStream, headers, true) { } public ArmoredOutputStream(Stream outStream, IDictionary<string, string> headers, bool addVersionHeader) : this(outStream, addVersionHeader && !headers.ContainsKey(HeaderVersion)) { foreach (KeyValuePair<string, string> header in headers) { m_headers.Add(header.Key, new List<string> { header.Value }); } } public void SetHeader(string name, string val) { if (val == null) m_headers.Remove(name); else { if (m_headers.TryGetValue(name, out List<string> value)) value.Clear(); else { value = new List<string>(1); m_headers[name] = value; } value.Add(val); } } public void AddHeader(string name, string val) { if (val != null && name != null) { if (!m_headers.TryGetValue(name, out List<string> value)) { value = new List<string>(1); m_headers[name] = value; } value.Add(val); } } public void ResetHeaders() { List<string> value; bool num = m_headers.TryGetValue(HeaderVersion, out value); m_headers.Clear(); if (num) m_headers.Add(HeaderVersion, value); } public void BeginClearText(HashAlgorithmTag hashAlgorithm) { string str; switch (hashAlgorithm) { case HashAlgorithmTag.Sha1: str = "SHA1"; break; case HashAlgorithmTag.Sha256: str = "SHA256"; break; case HashAlgorithmTag.Sha384: str = "SHA384"; break; case HashAlgorithmTag.Sha512: str = "SHA512"; break; case HashAlgorithmTag.MD2: str = "MD2"; break; case HashAlgorithmTag.MD5: str = "MD5"; break; case HashAlgorithmTag.RipeMD160: str = "RIPEMD160"; break; default: throw new IOException("unknown hash algorithm tag in beginClearText: " + hashAlgorithm.ToString()); } DoWrite("-----BEGIN PGP SIGNED MESSAGE-----" + NewLine); DoWrite("Hash: " + str + NewLine + NewLine); clearText = true; newLine = true; lastb = 0; } public void EndClearText() { clearText = false; } public override void WriteByte(byte value) { if (clearText) { outStream.WriteByte(value); if (newLine) { if (value != 10 || lastb != 13) newLine = false; if (value == 45) { outStream.WriteByte(32); outStream.WriteByte(45); } } switch (value) { case 10: if (lastb == 13) break; goto case 13; case 13: newLine = true; break; } lastb = value; } else { if (start) { switch (((value & 64) == 0) ? ((value & 63) >> 2) : (value & 63)) { case 6: type = "PUBLIC KEY BLOCK"; break; case 5: type = "PRIVATE KEY BLOCK"; break; case 2: type = "SIGNATURE"; break; default: type = "MESSAGE"; break; } DoWrite(headerStart + type + headerTail + NewLine); if (m_headers.TryGetValue(HeaderVersion, out List<string> value2)) WriteHeaderEntry(HeaderVersion, value2[0]); foreach (KeyValuePair<string, List<string>> header in m_headers) { string key = header.Key; if (key != HeaderVersion) { foreach (string item in header.Value) { WriteHeaderEntry(key, item); } } } DoWrite(NewLine); start = false; } if (bufPtr == 3) { crc.Update3(buf, 0); Encode3(outStream, buf); bufPtr = 0; if ((++chunkCount & 15) == 0) DoWrite(NewLine); } buf[bufPtr++] = value; } } protected override void Dispose(bool disposing) { if (disposing && type != null) { DoClose(); type = null; start = true; } base.Dispose(disposing); } private void DoClose() { if (bufPtr > 0) { for (int i = 0; i < bufPtr; i++) { crc.Update(buf[i]); } Encode(outStream, buf, bufPtr); } DoWrite(NewLine + "="); Pack.UInt24_To_BE((uint)crc.Value, buf); Encode3(outStream, buf); DoWrite(NewLine); DoWrite(footerStart); DoWrite(type); DoWrite(footerTail); DoWrite(NewLine); outStream.Flush(); } private void WriteHeaderEntry(string name, string v) { DoWrite(name + ": " + v + NewLine); } private void DoWrite(string s) { byte[] array = Strings.ToAsciiByteArray(s); outStream.Write(array, 0, array.Length); } } }