From 25ae5348623524d542f03060ff5a49a82160abd7 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 8 Feb 2024 18:31:11 +0100
Subject: [PATCH 01/37] Initial implementation of Argon2 (ported from bc-java)

---
 crypto/src/crypto/ICharToByteConverter.cs     |  35 +
 crypto/src/crypto/PasswordConverter.cs        |  44 +
 .../crypto/generators/Argon2BytesGenerator.cs | 762 ++++++++++++++++++
 .../src/crypto/parameters/Argon2Parameters.cs | 208 +++++
 crypto/test/src/crypto/test/Argon2Test.cs     | 377 +++++++++
 5 files changed, 1426 insertions(+)
 create mode 100644 crypto/src/crypto/ICharToByteConverter.cs
 create mode 100644 crypto/src/crypto/PasswordConverter.cs
 create mode 100644 crypto/src/crypto/generators/Argon2BytesGenerator.cs
 create mode 100644 crypto/src/crypto/parameters/Argon2Parameters.cs
 create mode 100644 crypto/test/src/crypto/test/Argon2Test.cs

diff --git a/crypto/src/crypto/ICharToByteConverter.cs b/crypto/src/crypto/ICharToByteConverter.cs
new file mode 100644
index 0000000000..94e0279e98
--- /dev/null
+++ b/crypto/src/crypto/ICharToByteConverter.cs
@@ -0,0 +1,35 @@
+namespace Org.BouncyCastle.Crypto
+{
+    public interface ICharToByteConverter
+    {
+        /**
+         * Return the type of the conversion.
+         *
+         * @return a type name for the conversion.
+         */
+        string GetName();
+
+        /**
+         * Return a byte encoded representation of the passed in password.
+         *
+         * @param password the characters to encode.
+         * @return a byte encoding of password.
+         */
+        byte[] Convert(char[] password);
+    }
+
+    public static class CharToByteConverterExtensions
+    {
+
+        /**
+         * Return a byte encoded representation of the passed in password.
+         *
+         * @param password the string to encode.
+         * @return a byte encoding of password.
+         */
+        public static byte[] Convert(this ICharToByteConverter converter, string password)
+        {
+            return converter.Convert(password.ToCharArray());
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/crypto/PasswordConverter.cs b/crypto/src/crypto/PasswordConverter.cs
new file mode 100644
index 0000000000..c2786152c0
--- /dev/null
+++ b/crypto/src/crypto/PasswordConverter.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Text;
+
+namespace Org.BouncyCastle.Crypto
+{
+    public class PasswordConverter
+        : ICharToByteConverter
+    {
+        private readonly string name;
+        private readonly Func<char[], byte[]> converterFunction;
+
+        public PasswordConverter(string name, Func<char[], byte[]> converterFunction)
+        {
+            this.name = name;
+            this.converterFunction = converterFunction;
+        }
+
+        public byte[] Convert(char[] password)
+        {
+            return converterFunction.Invoke(password);
+        }
+
+        public string GetName()
+        {
+            return name;
+        }
+
+        public readonly static ICharToByteConverter ASCII = new PasswordConverter("ASCII", PbeParametersGenerator.Pkcs5PasswordToBytes);
+
+        public readonly static ICharToByteConverter UTF8 = new PasswordConverter("UTF8", PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes);
+
+        public readonly static ICharToByteConverter PKCS12 = new PasswordConverter("PKCS12", PbeParametersGenerator.Pkcs12PasswordToBytes);
+
+        public readonly static ICharToByteConverter UTF32 = new PasswordConverter("UTF32", Encoding.UTF32.GetBytes);
+
+        public readonly static ICharToByteConverter Unicode = new PasswordConverter("Unicode", Encoding.Unicode.GetBytes);
+
+        public readonly static ICharToByteConverter BigEndianUnicode = new PasswordConverter("BigEndianUnicode", Encoding.BigEndianUnicode.GetBytes);
+
+#if NET6_0_OR_GREATER
+        public readonly static ICharToByteConverter Latin1 = new PasswordConverter("Latin1", Encoding.Latin1.GetBytes);
+#endif
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/crypto/generators/Argon2BytesGenerator.cs b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
new file mode 100644
index 0000000000..3fb0ea7f9f
--- /dev/null
+++ b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
@@ -0,0 +1,762 @@
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+using System;
+
+namespace Org.BouncyCastle.Crypto.Generators
+{
+    public sealed class Argon2BytesGenerator
+    {
+        private const int ARGON2_BLOCK_SIZE = 1024;
+        private const int ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8;
+
+        private const int ARGON2_ADDRESSES_IN_BLOCK = 128;
+
+        private const int ARGON2_PREHASH_DIGEST_LENGTH = 64;
+        private const int ARGON2_PREHASH_SEED_LENGTH = 72;
+
+        private const int ARGON2_SYNC_POINTS = 4;
+
+        /* Minimum and maximum number of lanes (degree of parallelism) */
+        private const int MIN_PARALLELISM = 1;
+        private const int MAX_PARALLELISM = 16777216;
+
+        /* Minimum and maximum digest size in bytes */
+        private const int MIN_OUTLEN = 4;
+
+        /* Minimum and maximum number of passes */
+        private const int MIN_ITERATIONS = 1;
+
+        private const long M32L = 0xFFFFFFFFL;
+
+        private readonly byte[] ZERO_BYTES = new byte[4];
+
+        private Argon2Parameters parameters;
+        private Block[] memory;
+        private int segmentLength;
+        private int laneLength;
+
+        public Argon2BytesGenerator()
+        {
+        }
+
+        /**
+         * Initialise the Argon2BytesGenerator from the parameters.
+         *
+         * @param parameters Argon2 configuration.
+         */
+        public void Init(Argon2Parameters parameters)
+        {
+            this.parameters = parameters;
+
+            if (parameters.GetLanes() < MIN_PARALLELISM)
+            {
+                throw new InvalidOperationException($"lanes must be greater than " + MIN_PARALLELISM);
+            }
+            else if (parameters.GetLanes() > MAX_PARALLELISM)
+            {
+                throw new InvalidOperationException("lanes must be less than " + MAX_PARALLELISM);
+            }
+            else if (parameters.GetMemory() < 2 * parameters.GetLanes())
+            {
+                throw new InvalidOperationException("memory is less than: " + (2 * parameters.GetLanes()) + " expected " + (2 * parameters.GetLanes()));
+            }
+            else if (parameters.GetIterations() < MIN_ITERATIONS)
+            {
+                throw new InvalidOperationException("iterations is less than: " + MIN_ITERATIONS);
+            }
+
+            DoInit(parameters);
+        }
+
+        public int GenerateBytes(string password, byte[] output)
+        {
+            return GenerateBytes(password.ToCharArray(), output);
+        }
+
+        public int GenerateBytes(string password, byte[] output, int outOff, int outLen)
+        {
+            return GenerateBytes(password.ToCharArray(), output, outOff, outLen);
+        }
+
+        public int GenerateBytes(char[] password, byte[] output)
+        {
+            return GenerateBytes(parameters.GetCharToByteConverter().Convert(password), output);
+        }
+
+        public int GenerateBytes(char[] password, byte[] output, int outOff, int outLen)
+        {
+            return GenerateBytes(parameters.GetCharToByteConverter().Convert(password), output, outOff, outLen);
+        }
+
+        public int GenerateBytes(byte[] password, byte[] output)
+        {
+            return GenerateBytes(password, output, 0, output.Length);
+        }
+
+        public int GenerateBytes(byte[] password, byte[] output, int outOff, int outLen)
+        {
+            if (outLen < MIN_OUTLEN)
+            {
+                throw new InvalidOperationException("output length less than " + MIN_OUTLEN);
+            }
+
+            byte[] tmpBlockBytes = new byte[ARGON2_BLOCK_SIZE];
+
+            Initialize(tmpBlockBytes, password, outLen);
+            FillMemoryBlocks();
+            Digest(tmpBlockBytes, output, outOff, outLen);
+
+            Reset();
+
+            return outLen;
+        }
+
+        // Clear memory.
+        private void Reset()
+        {
+            // Reset memory.
+            if (null != memory)
+            {
+                for (int i = 0; i < memory.Length; i++)
+                {
+                    Block b = memory[i];
+                    b?.Clear();
+                }
+            }
+        }
+
+        private void DoInit(Argon2Parameters parameters)
+        {
+            /* 2. Align memory size */
+            /* Minimum memoryBlocks = 8L blocks, where L is the number of lanes */
+            int memoryBlocks = parameters.GetMemory();
+
+            if (memoryBlocks < 2 * ARGON2_SYNC_POINTS * parameters.GetLanes())
+            {
+                memoryBlocks = 2 * ARGON2_SYNC_POINTS * parameters.GetLanes();
+            }
+
+            this.segmentLength = memoryBlocks / (parameters.GetLanes() * ARGON2_SYNC_POINTS);
+            this.laneLength = segmentLength * ARGON2_SYNC_POINTS;
+
+            /* Ensure that all segments have equal length */
+            memoryBlocks = segmentLength * (parameters.GetLanes() * ARGON2_SYNC_POINTS);
+
+            InitMemory(memoryBlocks);
+        }
+
+        private void InitMemory(int memoryBlocks)
+        {
+            this.memory = new Block[memoryBlocks];
+
+            for (int i = 0; i < memory.Length; i++)
+            {
+                memory[i] = new Block();
+            }
+        }
+
+        private void FillMemoryBlocks()
+        {
+            FillBlock filler = new FillBlock();
+            Position position = new Position();
+            for (int pass = 0; pass < parameters.GetIterations(); ++pass)
+            {
+                position.pass = pass;
+
+                for (int slice = 0; slice < ARGON2_SYNC_POINTS; ++slice)
+                {
+                    position.slice = slice;
+
+                    for (int lane = 0; lane < parameters.GetLanes(); ++lane)
+                    {
+                        position.lane = lane;
+
+                        FillSegment(filler, position);
+                    }
+                }
+            }
+        }
+
+        private void FillSegment(FillBlock filler, Position position)
+        {
+            Block addressBlock = null, inputBlock = null;
+
+            bool dataIndependentAddressing = IsDataIndependentAddressing(position);
+            int startingIndex = GetStartingIndex(position);
+            int currentOffset = position.lane * laneLength + position.slice * segmentLength + startingIndex;
+            int prevOffset = GetPrevOffset(currentOffset);
+
+            if (dataIndependentAddressing)
+            {
+                addressBlock = filler.addressBlock.Clear();
+                inputBlock = filler.inputBlock.Clear();
+
+                InitAddressBlocks(filler, position, inputBlock, addressBlock);
+            }
+
+            bool withXor = IsWithXor(position);
+
+            for (int index = startingIndex; index < segmentLength; ++index)
+            {
+                long pseudoRandom = GetPseudoRandom(
+                    filler,
+                    index,
+                    addressBlock,
+                    inputBlock,
+                    prevOffset,
+                    dataIndependentAddressing);
+
+                int refLane = GetRefLane(position, pseudoRandom);
+                int refColumn = GetRefColumn(position, index, pseudoRandom, refLane == position.lane);
+
+                /* 2 Creating a new block */
+                Block prevBlock = memory[prevOffset];
+                Block refBlock = memory[((laneLength) * refLane + refColumn)];
+                Block currentBlock = memory[currentOffset];
+
+                if (withXor)
+                {
+                    filler.FillBlockWithXor(prevBlock, refBlock, currentBlock);
+                }
+                else
+                {
+                    filler.Fill(prevBlock, refBlock, currentBlock);
+                }
+
+                prevOffset = currentOffset;
+                currentOffset++;
+            }
+        }
+
+        private bool IsDataIndependentAddressing(Position position)
+        {
+            return (parameters.GetArgonType() == Argon2Parameters.ARGON2_i) ||
+                (parameters.GetArgonType() == Argon2Parameters.ARGON2_id
+                    && (position.pass == 0)
+                    && (position.slice < ARGON2_SYNC_POINTS / 2)
+                );
+        }
+
+        private void InitAddressBlocks(FillBlock filler, Position position, Block inputBlock, Block addressBlock)
+        {
+            inputBlock.v[0] = (long)position.pass;
+            inputBlock.v[1] = (long)position.lane;
+            inputBlock.v[2] = (long)position.slice;
+            inputBlock.v[3] = (long)memory.Length;
+            inputBlock.v[4] = (long)parameters.GetIterations();
+            inputBlock.v[5] = (long)parameters.GetArgonType();
+
+            if ((position.pass == 0) && (position.slice == 0))
+            {
+                /* Don't forget to generate the first block of addresses: */
+                NextAddresses(filler, inputBlock, addressBlock);
+            }
+        }
+
+        private bool IsWithXor(Position position)
+        {
+            return !(position.pass == 0 || parameters.GetVersion() == Argon2Parameters.ARGON2_VERSION_10);
+        }
+
+        private int GetPrevOffset(int currentOffset)
+        {
+            if (currentOffset % laneLength == 0)
+            {
+                /* Last block in this lane */
+                return currentOffset + laneLength - 1;
+            }
+            else
+            {
+                /* Previous block */
+                return currentOffset - 1;
+            }
+        }
+
+        private static int GetStartingIndex(Position position)
+        {
+            if ((position.pass == 0) && (position.slice == 0))
+            {
+                return 2; /* we have already generated the first two blocks */
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        private static void NextAddresses(FillBlock filler, Block inputBlock, Block addressBlock)
+        {
+            inputBlock.v[6]++;
+            filler.Fill(inputBlock, addressBlock);
+            filler.Fill(addressBlock, addressBlock);
+        }
+
+        /* 1.2 Computing the index of the reference block */
+        /* 1.2.1 Taking pseudo-random value from the previous block */
+        private long GetPseudoRandom(
+            FillBlock filler,
+            int index,
+            Block addressBlock,
+            Block inputBlock,
+            int prevOffset,
+            bool dataIndependentAddressing)
+        {
+            if (dataIndependentAddressing)
+            {
+                int addressIndex = index % ARGON2_ADDRESSES_IN_BLOCK;
+                if (addressIndex == 0)
+                {
+                    NextAddresses(filler, inputBlock, addressBlock);
+                }
+                return addressBlock.v[addressIndex];
+            }
+            else
+            {
+                return memory[prevOffset].v[0];
+            }
+        }
+
+        private int GetRefLane(Position position, long pseudoRandom)
+        {
+            // Double-casting to/from ulong required because unsigned right shift operator
+            // >>> is supported only in C# 11 (.NET 7 or greater)
+            int refLane = (int)(((ulong)pseudoRandom >> 32) % (ulong)parameters.GetLanes());
+
+            if ((position.pass == 0) && (position.slice == 0))
+            {
+                /* Can not reference other lanes yet */
+                refLane = position.lane;
+            }
+            return refLane;
+        }
+
+        private int GetRefColumn(Position position, int index, long pseudoRandom, bool sameLane)
+        {
+            long referenceAreaSize;
+            long startPosition;
+
+            if (position.pass == 0)
+            {
+                startPosition = 0;
+
+                if (sameLane)
+                {
+                    /* The same lane => add current segment */
+                    referenceAreaSize = position.slice * segmentLength + index - 1;
+                }
+                else
+                {
+                    /* pass == 0 && !sameLane => position.slice > 0*/
+                    referenceAreaSize = position.slice * segmentLength + ((index == 0) ? (-1) : 0);
+                }
+            }
+            else
+            {
+                startPosition = ((position.slice + 1) * segmentLength) % laneLength;
+
+                if (sameLane)
+                {
+                    referenceAreaSize = laneLength - segmentLength + index - 1;
+                }
+                else
+                {
+                    referenceAreaSize = laneLength - segmentLength + ((index == 0) ? (-1) : 0);
+                }
+            }
+
+            long relativePosition = pseudoRandom & 0xFFFFFFFFL;
+
+            // Double-casting to/from ulong required because unsigned right shift operator
+            // >>> is supported only in C# 11 (.NET 7 or greater)
+            relativePosition = (long)((ulong)(relativePosition * relativePosition) >> 32);
+            relativePosition = referenceAreaSize - 1 - ((referenceAreaSize * relativePosition) >> 32);
+
+            return (int)(startPosition + relativePosition) % laneLength;
+        }
+
+        private void Digest(byte[] tmpBlockBytes, byte[] output, int outOff, int outLen)
+        {
+            Block finalBlock = memory[laneLength - 1];
+
+            /* XOR the last blocks */
+            for (int i = 1; i < parameters.GetLanes(); i++)
+            {
+                int lastBlockInLane = i * laneLength + (laneLength - 1);
+                finalBlock.XorWith(memory[lastBlockInLane]);
+            }
+
+            finalBlock.ToBytes(tmpBlockBytes);
+
+            Hash(tmpBlockBytes, output, outOff, outLen);
+        }
+
+        /**
+         * H' - hash - variable length hash function
+         */
+        private static void Hash(byte[] input, byte[] output, int outOff, int outLen)
+        {
+            byte[] outLenBytes = new byte[4];
+            Pack.UInt32_To_LE((uint)outLen, outLenBytes, 0);
+
+            int blake2bLength = 64;
+
+            if (outLen <= blake2bLength)
+            {
+                IDigest blake = new Blake2bDigest(outLen * 8);
+
+                blake.BlockUpdate(outLenBytes, 0, outLenBytes.Length);
+                blake.BlockUpdate(input, 0, input.Length);
+                blake.DoFinal(output, outOff);
+            }
+            else
+            {
+                IDigest digest = new Blake2bDigest(blake2bLength * 8);
+                byte[] outBuffer = new byte[blake2bLength];
+
+                /* V1 */
+                digest.BlockUpdate(outLenBytes, 0, outLenBytes.Length);
+                digest.BlockUpdate(input, 0, input.Length);
+                digest.DoFinal(outBuffer, 0);
+
+                int halfLen = blake2bLength / 2, outPos = outOff;
+                Array.Copy(outBuffer, 0, output, outPos, halfLen);
+                outPos += halfLen;
+
+                int r = ((outLen + 31) / 32) - 2;
+
+                for (int i = 2; i <= r; i++, outPos += halfLen)
+                {
+                    /* V2 to Vr */
+                    digest.BlockUpdate(outBuffer, 0, outBuffer.Length);
+                    digest.DoFinal(outBuffer, 0);
+
+                    Array.Copy(outBuffer, 0, output, outPos, halfLen);
+                }
+
+                int lastLength = outLen - 32 * r;
+
+                /* Vr+1 */
+                digest = new Blake2bDigest(lastLength * 8);
+
+                digest.BlockUpdate(outBuffer, 0, outBuffer.Length);
+                digest.DoFinal(output, outPos);
+            }
+        }
+
+        private static void RoundFunction(Block block,
+                                          int v0, int v1, int v2, int v3,
+                                          int v4, int v5, int v6, int v7,
+                                          int v8, int v9, int v10, int v11,
+                                          int v12, int v13, int v14, int v15)
+        {
+            long[] v = block.v;
+
+            F(v, v0, v4, v8, v12);
+            F(v, v1, v5, v9, v13);
+            F(v, v2, v6, v10, v14);
+            F(v, v3, v7, v11, v15);
+
+            F(v, v0, v5, v10, v15);
+            F(v, v1, v6, v11, v12);
+            F(v, v2, v7, v8, v13);
+            F(v, v3, v4, v9, v14);
+        }
+
+        private static void F(long[] v, int a, int b, int c, int d)
+        {
+            QuarterRound(v, a, b, d, 32);
+            QuarterRound(v, c, d, b, 24);
+            QuarterRound(v, a, b, d, 16);
+            QuarterRound(v, c, d, b, 63);
+        }
+
+        private static void QuarterRound(long[] v, int x, int y, int z, int s)
+        {
+            //        fBlaMka(v, x, y);
+            //        rotr64(v, z, x, s);
+
+            long a = v[x], b = v[y], c = v[z];
+
+            a += b + 2 * (a & M32L) * (b & M32L);
+            c = Longs.RotateRight(c ^ a, s);
+
+            v[x] = a;
+            v[z] = c;
+        }
+
+        /*designed by the Lyra PHC team */
+        /* a <- a + b + 2*aL*bL
+         * + == addition modulo 2^64
+         * aL = least 32 bit */
+        //    private static void fBlaMka(long[] v, int x, int y)
+        //    {
+        //        final long a = v[x], b = v[y];
+        //        final long ab = (a & M32L) * (b & M32L);
+        //
+        //        v[x] = a + b + 2 * ab;
+        //    }
+        //
+        //    private static void rotr64(long[] v, int x, int y, int s)
+        //    {
+        //        v[x] = Longs.rotateRight(v[x] ^ v[y], s);
+        //    }
+
+        private void Initialize(byte[] tmpBlockBytes, byte[] password, int outputLength)
+        {
+            /*
+             * H0 = H64(p, τ, m, t, v, y, |P|, P, |S|, S, |L|, K, |X|, X)
+             * -> 64 byte (ARGON2_PREHASH_DIGEST_LENGTH)
+             */
+
+            Blake2bDigest blake = new Blake2bDigest(ARGON2_PREHASH_DIGEST_LENGTH * 8);
+
+            int[] values = {
+                parameters.GetLanes(),
+                outputLength,
+                parameters.GetMemory(),
+                parameters.GetIterations(),
+                parameters.GetVersion(),
+                parameters.GetArgonType()
+            };
+
+            Helpers.IntArrayToLittleEndian(values, tmpBlockBytes, 0);
+            blake.BlockUpdate(tmpBlockBytes, 0, values.Length * 4);
+
+            AddByteString(tmpBlockBytes, blake, password);
+            AddByteString(tmpBlockBytes, blake, parameters.GetSalt());
+            AddByteString(tmpBlockBytes, blake, parameters.GetSecret());
+            AddByteString(tmpBlockBytes, blake, parameters.GetAdditional());
+
+            byte[] initialHashWithZeros = new byte[ARGON2_PREHASH_SEED_LENGTH];
+            blake.DoFinal(initialHashWithZeros, 0);
+
+            FillFirstBlocks(tmpBlockBytes, initialHashWithZeros);
+        }
+
+        private void AddByteString(byte[] tmpBlockBytes, IDigest digest, byte[] octets)
+        {
+            if (null == octets)
+            {
+                digest.BlockUpdate(ZERO_BYTES, 0, 4);
+                return;
+            }
+
+            Pack.UInt32_To_LE((uint)octets.Length, tmpBlockBytes, 0);
+            digest.BlockUpdate(tmpBlockBytes, 0, 4);
+            digest.BlockUpdate(octets, 0, octets.Length);
+        }
+
+        /**
+         * (H0 || 0 || i) 72 byte -> 1024 byte
+         * (H0 || 1 || i) 72 byte -> 1024 byte
+         */
+        private void FillFirstBlocks(byte[] tmpBlockBytes, byte[] initialHashWithZeros)
+        {
+            byte[] initialHashWithOnes = new byte[ARGON2_PREHASH_SEED_LENGTH];
+            Array.Copy(initialHashWithZeros, 0, initialHashWithOnes, 0, ARGON2_PREHASH_DIGEST_LENGTH);
+            //        Pack.intToLittleEndian(1, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH);
+            initialHashWithOnes[ARGON2_PREHASH_DIGEST_LENGTH] = 1;
+
+            for (int i = 0; i < parameters.GetLanes(); i++)
+            {
+                Pack.UInt32_To_LE((uint)i, initialHashWithZeros, ARGON2_PREHASH_DIGEST_LENGTH + 4);
+                Pack.UInt32_To_LE((uint)i, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH + 4);
+
+                Hash(initialHashWithZeros, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE);
+                memory[i * laneLength + 0].FromBytes(tmpBlockBytes);
+
+                Hash(initialHashWithOnes, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE);
+                memory[i * laneLength + 1].FromBytes(tmpBlockBytes);
+            }
+        }
+
+        private sealed class FillBlock
+        {
+            private readonly Block R = new Block();
+            private readonly Block Z = new Block();
+
+            internal readonly Block addressBlock = new Block();
+            internal readonly Block inputBlock = new Block();
+
+            internal void ApplyBlake()
+            {
+                /* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then
+                (16,17,..31)... finally (112,113,...127) */
+                for (int i = 0; i < 8; i++)
+                {
+
+                    int i16 = 16 * i;
+                    RoundFunction(Z,
+                        i16, i16 + 1, i16 + 2,
+                        i16 + 3, i16 + 4, i16 + 5,
+                        i16 + 6, i16 + 7, i16 + 8,
+                        i16 + 9, i16 + 10, i16 + 11,
+                        i16 + 12, i16 + 13, i16 + 14,
+                        i16 + 15
+                    );
+                }
+
+                /* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then
+                (2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */
+                for (int i = 0; i < 8; i++)
+                {
+
+                    int i2 = 2 * i;
+                    RoundFunction(Z,
+                        i2, i2 + 1, i2 + 16,
+                        i2 + 17, i2 + 32, i2 + 33,
+                        i2 + 48, i2 + 49, i2 + 64,
+                        i2 + 65, i2 + 80, i2 + 81,
+                        i2 + 96, i2 + 97, i2 + 112,
+                        i2 + 113
+                    );
+                }
+            }
+
+            internal void Fill(Block Y, Block currentBlock)
+            {
+                Z.CopyBlock(Y);
+                ApplyBlake();
+                currentBlock.Xor(Y, Z);
+            }
+
+            internal void Fill(Block X, Block Y, Block currentBlock)
+            {
+                R.Xor(X, Y);
+                Z.CopyBlock(R);
+                ApplyBlake();
+                currentBlock.Xor(R, Z);
+            }
+
+            internal void FillBlockWithXor(Block X, Block Y, Block currentBlock)
+            {
+                R.Xor(X, Y);
+                Z.CopyBlock(R);
+                ApplyBlake();
+                currentBlock.XorWith(R, Z);
+            }
+        }
+
+        private sealed class Block
+        {
+            private const int SIZE = ARGON2_QWORDS_IN_BLOCK;
+
+            /* 128 * 8 Byte QWords */
+            internal readonly long[] v;
+
+            internal Block()
+            {
+                v = new long[SIZE];
+            }
+
+            internal void FromBytes(byte[] input)
+            {
+                if (input.Length < ARGON2_BLOCK_SIZE)
+                {
+                    throw new ArgumentException("input shorter than blocksize");
+                }
+
+                Helpers.LittleEndianToLongArray(input, 0, v);
+            }
+
+           internal void ToBytes(byte[] output)
+            {
+                if (output.Length < ARGON2_BLOCK_SIZE)
+                {
+                    throw new ArgumentException("output shorter than blocksize");
+                }
+
+                Helpers.LongArrayToLittleEndian(v, output, 0);
+            }
+
+            internal void CopyBlock(Block other)
+            {
+                Array.Copy(other.v, 0, v, 0, SIZE);
+            }
+
+            internal void Xor(Block b1, Block b2)
+            {
+                long[] v0 = v;
+                long[] v1 = b1.v;
+                long[] v2 = b2.v;
+
+                for (int i = 0; i < SIZE; i++)
+                {
+                    v0[i] = v1[i] ^ v2[i];
+                }
+            }
+
+            internal void XorWith(Block b1)
+            {
+                long[] v0 = v;
+                long[] v1 = b1.v;
+
+                for (int i = 0; i < SIZE; i++)
+                {
+                    v0[i] ^= v1[i];
+                }
+            }
+
+            internal void XorWith(Block b1, Block b2)
+            {
+                long[] v0 = v;
+                long[] v1 = b1.v;
+                long[] v2 = b2.v;
+                for (int i = 0; i < SIZE; i++)
+                {
+                    v0[i] ^= v1[i] ^ v2[i];
+                }
+            }
+
+            internal Block Clear()
+            {
+                Arrays.Fill(v, 0);
+                return this;
+            }
+        }
+
+        private sealed class Position
+        {
+            internal int pass;
+            internal int lane;
+            internal int slice;
+
+            internal Position()
+            {
+            }
+        }
+
+        private static class Helpers
+        {
+            internal static void LittleEndianToLongArray(byte[] bs, int off, long[] ns)
+            {
+                for (int i = 0; i < ns.Length; ++i)
+                {
+                    ns[i] = (long)Pack.LE_To_UInt64(bs, off);
+                    off += 8;
+                }
+            }
+
+            internal static void LongArrayToLittleEndian(long[] ns, byte[] bs, int off)
+            {
+                for (int i = 0; i < ns.Length; ++i)
+                {
+                    Pack.UInt64_To_LE((ulong)ns[i], bs, off);
+                    off += 8;
+                }
+            }
+
+            internal static void IntArrayToLittleEndian(int[] ns, byte[] bs, int off)
+            {
+                for (int i = 0; i < ns.Length; ++i)
+                {
+                    Pack.UInt32_To_LE((uint)ns[i], bs, off);
+                    off += 4;
+                }
+            }
+
+        }
+    }
+}
diff --git a/crypto/src/crypto/parameters/Argon2Parameters.cs b/crypto/src/crypto/parameters/Argon2Parameters.cs
new file mode 100644
index 0000000000..f514d862e1
--- /dev/null
+++ b/crypto/src/crypto/parameters/Argon2Parameters.cs
@@ -0,0 +1,208 @@
+using Org.BouncyCastle.Utilities;
+using System;
+
+namespace Org.BouncyCastle.Crypto.Parameters
+{
+    public sealed class Argon2Parameters
+    {
+        public const int ARGON2_d = 0x00;
+        public const int ARGON2_i = 0x01;
+        public const int ARGON2_id = 0x02;
+
+        public const int ARGON2_VERSION_10 = 0x10;
+        public const int ARGON2_VERSION_13 = 0x13;
+
+        private readonly byte[] salt;
+        private readonly byte[] secret;
+        private readonly byte[] additional;
+
+        private readonly int iterations;
+        private readonly int memory;
+        private readonly int lanes;
+
+        private readonly int version;
+        private readonly int type;
+        private readonly ICharToByteConverter converter;
+
+        public class Builder
+        {
+            private const int DEFAULT_ITERATIONS = 3;
+            private const int DEFAULT_MEMORY_COST = 12;
+            private const int DEFAULT_LANES = 1;
+            private const int DEFAULT_TYPE = ARGON2_i;
+            private const int DEFAULT_VERSION = ARGON2_VERSION_13;
+
+            private byte[] salt = Array.Empty<byte>();
+            private byte[] secret = Array.Empty<byte>();
+            private byte[] additional = Array.Empty<byte>();
+
+            private int iterations;
+            private int memory;
+            private int lanes;
+
+            private int version;
+            private readonly int type;
+
+            private ICharToByteConverter converter = PasswordConverter.UTF8;
+
+            public Builder()
+                : this(DEFAULT_TYPE)
+            {
+            }
+
+            public Builder(int type)
+            {
+                this.type = type;
+                lanes = DEFAULT_LANES;
+                memory = 1 << DEFAULT_MEMORY_COST;
+                iterations = DEFAULT_ITERATIONS;
+                version = DEFAULT_VERSION;
+            }
+
+            public Builder WithParallelism(int parallelism)
+            {
+                lanes = parallelism;
+                return this;
+            }
+
+            public Builder WithSalt(byte[] salt)
+            {
+                this.salt = Arrays.Clone(salt);
+                return this;
+            }
+
+            public Builder WithSecret(byte[] secret)
+            {
+                this.secret = Arrays.Clone(secret);
+                return this;
+            }
+
+            public Builder WithAdditional(byte[] additional)
+            {
+                this.additional = Arrays.Clone(additional);
+                return this;
+            }
+
+            public Builder WithIterations(int iterations)
+            {
+                this.iterations = iterations;
+                return this;
+            }
+
+            public Builder WithMemoryAsKB(int memory)
+            {
+                this.memory = memory;
+                return this;
+            }
+
+            public Builder WithMemoryPowOfTwo(int memory)
+            {
+                this.memory = 1 << memory;
+                return this;
+            }
+
+            public Builder WithVersion(int version)
+            {
+                this.version = version;
+                return this;
+            }
+
+            public Builder WithCharToByteConverter(ICharToByteConverter converter)
+            {
+                this.converter = converter;
+                return this;
+            }
+
+            public Builder WithCharToByteConverter(string name, Func<char[], byte[]> converterFunction)
+            {
+                return WithCharToByteConverter(new PasswordConverter(name, converterFunction));
+            }
+
+            public Builder WithCharToByteConverter(Func<char[], byte[]> converterFunction)
+            {
+                return WithCharToByteConverter("Custom", converterFunction);
+            }
+
+            public Argon2Parameters Build()
+            {
+                return new Argon2Parameters(type, salt, secret, additional, iterations, memory, lanes, version, converter);
+            }
+
+            public void Clear()
+            {
+                Arrays.Clear(salt);
+                Arrays.Clear(secret);
+                Arrays.Clear(additional);
+            }
+        }
+
+        private Argon2Parameters(
+            int type,
+            byte[] salt,
+            byte[] secret,
+            byte[] additional,
+            int iterations,
+            int memory,
+            int lanes,
+            int version,
+            ICharToByteConverter converter)
+        {
+
+            this.salt = Arrays.Clone(salt);
+            this.secret = Arrays.Clone(secret);
+            this.additional = Arrays.Clone(additional);
+            this.iterations = iterations;
+            this.memory = memory;
+            this.lanes = lanes;
+            this.version = version;
+            this.type = type;
+            this.converter = converter;
+        }
+
+        public byte[] GetSalt()
+        {
+            return Arrays.Clone(salt);
+        }
+
+        public byte[] GetSecret()
+        {
+            return Arrays.Clone(secret);
+        }
+
+        public byte[] GetAdditional()
+        {
+            return Arrays.Clone(additional);
+        }
+
+        public int GetIterations()
+        {
+            return iterations;
+        }
+
+        public int GetMemory()
+        {
+            return memory;
+        }
+
+        public int GetLanes()
+        {
+            return lanes;
+        }
+
+        public int GetVersion()
+        {
+            return version;
+        }
+
+        public int GetArgonType()
+        {
+            return type;
+        }
+
+        public ICharToByteConverter GetCharToByteConverter()
+        {
+            return converter;
+        }
+
+    }
+}
diff --git a/crypto/test/src/crypto/test/Argon2Test.cs b/crypto/test/src/crypto/test/Argon2Test.cs
new file mode 100644
index 0000000000..23a3561009
--- /dev/null
+++ b/crypto/test/src/crypto/test/Argon2Test.cs
@@ -0,0 +1,377 @@
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+    [TestFixture]
+    public class Argon2Test
+        : SimpleTest
+    {
+        private const int DEFAULT_OUTPUTLEN = 32;
+
+        public override string Name => "ArgonTest";
+        public override void PerformTest()
+        {
+            TestExceptions();
+            TestPermutations();
+            TestVectorsFromSpecs();
+            HashTestsVersion10();
+            HashTestsVersion13();
+        }
+
+        #region "Exception tests"
+        [Test]
+        public void TestExceptions()
+        {
+            // lanes less than MIN_PARALLELISM
+            Assert.Throws<InvalidOperationException>(() => {
+                Argon2BytesGenerator gen = new Argon2BytesGenerator();
+                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
+                    .WithParallelism(0);
+
+                gen.Init(builder.Build());
+            });
+
+            // lanes greater than MAX_PARALLELISM
+            Assert.Throws<InvalidOperationException>(() => {
+                Argon2BytesGenerator gen = new Argon2BytesGenerator();
+                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
+                    .WithParallelism(16777299);
+
+                gen.Init(builder.Build());
+            });
+
+            // iterations less than MIN_ITERATIONS
+            Assert.Throws<InvalidOperationException>(() => {
+                Argon2BytesGenerator gen = new Argon2BytesGenerator();
+                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
+                    .WithIterations(0);
+
+                gen.Init(builder.Build());
+            });
+
+            // memory less than 2 * lanes
+            Assert.Throws<InvalidOperationException>(() => {
+                Argon2BytesGenerator gen = new Argon2BytesGenerator();
+                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
+                    .WithMemoryAsKB(10)
+                    .WithParallelism(6);
+
+                gen.Init(builder.Build());
+            });
+
+            // output length less than MIN_OUTLEN
+            Assert.Throws<InvalidOperationException>(() => {
+                Argon2BytesGenerator gen = new Argon2BytesGenerator();
+                Argon2Parameters.Builder builder = new Argon2Parameters.Builder();
+
+                gen.Init(builder.Build());
+
+                byte[] result = new byte[3];
+                gen.GenerateBytes("password", result);
+            });
+
+        }
+        #endregion
+
+        #region "Permutation based tests"
+        [Test]
+        public void TestPermutations()
+        {
+            byte[] rootPassword = Encoding.ASCII.GetBytes("aac");
+            byte[] buf;
+            
+            byte[][] salts = new byte[][] {
+                new byte[16],
+                new byte[16],
+                new byte[16] 
+            };
+
+            for (int t = 0; t< 16; t++)
+            {
+                salts[1][t] = (byte) t;
+                salts[2][t] = (byte) (16 - t);
+            }
+            
+            //
+            // Permutation, starting with a shorter array, same length then one longer.
+            //
+            for (int j = rootPassword.Length - 1; j<rootPassword.Length + 2; j++)
+            {
+                buf = new byte[j];
+                
+                for (int a = 0; a<rootPassword.Length; a++)
+                {
+                    for (int b = 0; b<buf.Length; b++)
+                    {
+                        buf[b] = rootPassword[(a + b) % rootPassword.Length];
+                    }
+                    
+                    List<byte[]> permutations = new List<byte[]>();
+                    Permute(permutations, buf, 0, buf.Length - 1);
+
+                    for (int i = 0; i != permutations.Count; i++)
+                    {
+                        byte[] candidate = permutations[i];
+                        for (int k = 0; k != salts.Length; k++)
+                        {
+                            byte[] salt = salts[k];
+                            byte[] expected = Generate(Argon2Parameters.ARGON2_VERSION_10, 1, 8, 2, rootPassword, salt, 32);
+                            byte[] testValue = Generate(Argon2Parameters.ARGON2_VERSION_10, 1, 8, 2, candidate, salt, 32);
+
+                            //
+                            // If the passwords are the same for the same salt we should have the same string.
+                            //
+                            bool sameAsRoot = Arrays.AreEqual(rootPassword, candidate);
+                            IsTrue("expected same result", sameAsRoot == Arrays.AreEqual(expected, testValue));
+                        }
+                    }
+                }
+            }
+        }
+
+        private static void Swap(byte[] buf, int i, int j)
+        {
+            byte b = buf[i];
+            buf[i] = buf[j];
+            buf[j] = b;
+        }
+
+        private static void Permute(List<byte[]> permutation, byte[] a, int l, int r)
+        {
+            if (l == r)
+            {
+                permutation.Add(Arrays.Clone(a));
+            }
+            else
+            {
+
+                for (int i = l; i <= r; i++)
+                {
+                    // Swapping done
+                    Swap(a, l, i);
+
+                    // Recursion called
+                    Permute(permutation, a, l + 1, r);
+
+                    //backtrack
+                    Swap(a, l, i);
+                }
+            }
+        }
+        
+        private static byte[] Generate(int version, int iterations, int memory, int parallelism,
+                                byte[] password, byte[] salt, int outputLength)
+        {
+            Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i)
+                .WithVersion(version)
+                .WithIterations(iterations)
+                .WithMemoryPowOfTwo(memory)
+                .WithParallelism(parallelism)
+                .WithSalt(salt);
+
+            //
+            // Set the password.
+            //
+            Argon2BytesGenerator gen = new Argon2BytesGenerator();
+
+            gen.Init(builder.Build());
+
+            byte[] result = new byte[outputLength];
+
+            gen.GenerateBytes(password, result, 0, result.Length);
+            return result;
+        }
+        #endregion
+
+        #region "Hash tests"
+        /* Multiple test cases for various input values */
+        [Test]
+        public void HashTestsVersion10()
+        {
+            /* Multiple test cases for various input values */
+
+            int version = Argon2Parameters.ARGON2_VERSION_10;
+
+            HashTest(version, 2, 16, 1, "password", "somesalt",
+                "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 20, 1, "password", "somesalt",
+                "9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 18, 1, "password", "somesalt",
+                "3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 8, 1, "password", "somesalt",
+                "fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06",
+                DEFAULT_OUTPUTLEN);
+            HashTest(version, 2, 8, 2, "password", "somesalt",
+                "b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 1, 16, 1, "password", "somesalt",
+                "81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 4, 16, 1, "password", "somesalt",
+                "f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 16, 1, "differentpassword", "somesalt",
+                "e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 16, 1, "password", "diffsalt",
+                "79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 16, 1, "password", "diffsalt",
+                "1a097a5d1c80e579583f6e19c7e4763ccb7c522ca85b7d58143738e12ca39f8e6e42734c950ff2463675b97c37ba" +
+                    "39feba4a9cd9cc5b4c798f2aaf70eb4bd044c8d148decb569870dbd923430b82a083f284beae777812cce18cdac68ee8ccef" +
+                    "c6ec9789f30a6b5a034591f51af830f4",
+                112);
+
+        }
+
+        [Test]
+        public void HashTestsVersion13()
+        {
+            int version = Argon2Parameters.ARGON2_VERSION_13;
+
+            HashTest(version, 2, 16, 1, "password", "somesalt",
+                "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 20, 1, "password", "somesalt",
+                "d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 18, 1, "password", "somesalt",
+                "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 8, 1, "password", "somesalt",
+                "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 8, 2, "password", "somesalt",
+                "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 1, 16, 1, "password", "somesalt",
+                "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 4, 16, 1, "password", "somesalt",
+                "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 16, 1, "differentpassword", "somesalt",
+                "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
+                DEFAULT_OUTPUTLEN);
+
+            HashTest(version, 2, 16, 1, "password", "diffsalt",
+                "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
+                DEFAULT_OUTPUTLEN);
+
+        }
+
+        private void HashTest(int version, int iterations, int memory, int parallelism,
+                      string password, string salt, String passwordRef, int outputLength)
+        {
+            Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i)
+                .WithVersion(version)
+                .WithIterations(iterations)
+                .WithMemoryPowOfTwo(memory)
+                .WithParallelism(parallelism)
+                .WithSalt(Encoding.ASCII.GetBytes(salt));
+
+            Argon2BytesGenerator gen = new Argon2BytesGenerator();
+
+            gen.Init(builder.Build());
+
+            byte[] result = new byte[outputLength];
+            gen.GenerateBytes(password, result, 0, result.Length);
+
+            IsTrue(passwordRef + " Failed", AreEqual(result, Hex.Decode(passwordRef)));
+        }
+        #endregion
+
+        #region "Known Answer Tests (KATs) from specifications"
+        private void SpecsTest(int version, int type, string passwordRef)
+        {
+            byte[] ad = Hex.Decode("040404040404040404040404");
+            byte[] secret = Hex.Decode("0303030303030303");
+            byte[] salt = Hex.Decode("02020202020202020202020202020202");
+            byte[] password = Hex.Decode("0101010101010101010101010101010101010101010101010101010101010101");
+
+            byte[] expected = Hex.Decode(passwordRef);
+            byte[] result = new byte[32];
+
+            Argon2Parameters.Builder builder = new Argon2Parameters.Builder(type)
+                .WithVersion(version)
+                .WithIterations(3)
+                .WithMemoryAsKB(32)
+                .WithParallelism(4)
+                .WithAdditional(ad)
+                .WithSecret(secret)
+                .WithSalt(salt);
+
+            Argon2BytesGenerator gen = new Argon2BytesGenerator();
+
+            gen.Init(builder.Build());
+
+            gen.GenerateBytes(password, result, 0, result.Length);
+
+            IsTrue(passwordRef + " Failed", AreEqual(result, expected));
+        }
+
+        [Test]
+        public void TestVectorsFromSpecs()
+        {
+            /* Version 0x13 (19) from RFC 9106 https://datatracker.ietf.org/doc/html/rfc9106#name-test-vectors */
+            SpecsTest(
+                Argon2Parameters.ARGON2_VERSION_13,
+                Argon2Parameters.ARGON2_d,
+                "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb");
+
+            SpecsTest(
+                Argon2Parameters.ARGON2_VERSION_13,
+                Argon2Parameters.ARGON2_i,
+                "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8");
+
+            SpecsTest(
+                Argon2Parameters.ARGON2_VERSION_13,
+                Argon2Parameters.ARGON2_id,
+                "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659");
+
+            /* Version 0x10 (16) from reference C implementation https://github.com/P-H-C/phc-winner-argon2/tree/master/kats */
+            SpecsTest(
+                Argon2Parameters.ARGON2_VERSION_10,
+                Argon2Parameters.ARGON2_d,
+                "96a9d4e5a1734092c85e29f410a45914a5dd1f5cbf08b2670da68a0285abf32b");
+
+            SpecsTest(
+                Argon2Parameters.ARGON2_VERSION_10,
+                Argon2Parameters.ARGON2_i,
+                "87aeedd6517ab830cd9765cd8231abb2e647a5dee08f7c05e02fcb763335d0fd");
+
+            SpecsTest(
+                Argon2Parameters.ARGON2_VERSION_10,
+                Argon2Parameters.ARGON2_id,
+                "b64615f07789b66b645b67ee9ed3b377ae350b6bfcbb0fc95141ea8f322613c0");
+        }
+        #endregion
+    }
+}

From fa13755456017bc33e5167524098b2bd691af9ab Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sat, 10 Feb 2024 15:44:32 +0100
Subject: [PATCH 02/37] New algorithms and packet types

---
 crypto/src/bcpg/AeadAlgorithmTag.cs          | 12 ++++++
 crypto/src/bcpg/PacketTags.cs                |  8 +++-
 crypto/src/bcpg/PublicKeyAlgorithmTags.cs    | 13 +++++-
 crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs | 29 +++++++++----
 crypto/src/openpgp/PgpUtilities.cs           | 45 +++++++++++++-------
 5 files changed, 81 insertions(+), 26 deletions(-)

diff --git a/crypto/src/bcpg/AeadAlgorithmTag.cs b/crypto/src/bcpg/AeadAlgorithmTag.cs
index 632f888381..f02bc90aee 100644
--- a/crypto/src/bcpg/AeadAlgorithmTag.cs
+++ b/crypto/src/bcpg/AeadAlgorithmTag.cs
@@ -7,5 +7,17 @@ public enum AeadAlgorithmTag : byte
         Eax = 1,    // EAX (IV len: 16 octets, Tag len: 16 octets)
         Ocb = 2,    // OCB (IV len: 15 octets, Tag len: 16 octets)
         Gcm = 3,    // GCM (IV len: 12 octets, Tag len: 16 octets)
+
+        Experimental_1 = 100,
+        Experimental_2 = 101,
+        Experimental_3 = 102,
+        Experimental_4 = 103,
+        Experimental_5 = 104,
+        Experimental_6 = 105,
+        Experimental_7 = 106,
+        Experimental_8 = 107,
+        Experimental_9 = 108,
+        Experimental_10 = 109,
+        Experimental_11 = 110,
     }
 }
diff --git a/crypto/src/bcpg/PacketTags.cs b/crypto/src/bcpg/PacketTags.cs
index 5a53d4e957..d6c2fc0757 100644
--- a/crypto/src/bcpg/PacketTags.cs
+++ b/crypto/src/bcpg/PacketTags.cs
@@ -19,8 +19,12 @@ public enum PacketTag
         UserId = 13,								// User ID Packet
         PublicSubkey = 14,							// Public Subkey Packet
         UserAttribute = 17,							// User attribute
-        SymmetricEncryptedIntegrityProtected = 18,	// Symmetric encrypted, integrity protected
-        ModificationDetectionCode = 19,				// Modification detection code
+        SymmetricEncryptedIntegrityProtected = 18,  // Symmetric encrypted, integrity protected
+
+        ModificationDetectionCode = 19,		        // Reserved (formerly Modification Detection Code Packet)
+        ReservedAeadEncryptedData = 20,             // Reserved (defined as AEAD Encrypted Data Packet in retired draft [draft-koch-openpgp-rfc4880bis])
+
+        Padding = 21,                               // Padding Packet [https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#padding-packet]
 
         Experimental1 = 60,							// Private or Experimental Values
         Experimental2 = 61,
diff --git a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
index a309b65ae4..7fa4d728ae 100644
--- a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
@@ -12,7 +12,8 @@ public enum PublicKeyAlgorithmTag
         Dsa = 17,				// DSA (Digital Signature Standard)
         ECDH = 18,              // Reserved for Elliptic Curve (actual algorithm name)
         ECDsa = 19,				// Reserved for ECDSA
-        ElGamalGeneral = 20,	// Elgamal (Encrypt or Sign)
+
+        ElGamalGeneral = 20,	// Reserved (formerly Elgamal Encrypt or Sign)
         DiffieHellman = 21,		// Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME)
 
         // TODO Mark obsolete once Ed25519, Ed448 available
@@ -20,6 +21,16 @@ public enum PublicKeyAlgorithmTag
         EdDsa = 22,             // EdDSA - (internet draft, but appearing in use)
         EdDsa_Legacy = 22,      // new name for old EdDSA tag.
 
+        // defined as Reserver by crypto-refresh draft
+        AEDH = 23,
+        AEDSA = 24,
+
+        // https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/
+        X25519 = 25,
+        X448 = 26,
+        Ed25519 = 27,
+        Ed448 = 28,
+
         Experimental_1 = 100,
         Experimental_2 = 101,
         Experimental_3 = 102,
diff --git a/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs b/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs
index e05a486163..abd00fa218 100644
--- a/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs
@@ -10,14 +10,27 @@ public enum SymmetricKeyAlgorithmTag
         TripleDes = 2,    // Triple-DES (DES-EDE, as per spec -168 bit key derived from 192)
         Cast5 = 3,        // Cast5 (128 bit key, as per RFC 2144)
         Blowfish = 4,     // Blowfish (128 bit key, 16 rounds) [Blowfish]
-        Safer = 5,        // Safer-SK128 (13 rounds) [Safer]
+        Safer = 5,        // Reserved - formerly Safer-SK128 (13 rounds) [Safer]
         Des = 6,          // Reserved for DES/SK
-        Aes128 = 7,       // Reserved for AES with 128-bit key
-        Aes192 = 8,       // Reserved for AES with 192-bit key
-        Aes256 = 9,       // Reserved for AES with 256-bit key
-        Twofish = 10,     // Reserved for Twofish
-        Camellia128 = 11, // Reserved for AES with 128-bit key
-        Camellia192 = 12, // Reserved for AES with 192-bit key
-        Camellia256 = 13  // Reserved for AES with 256-bit key
+        Aes128 = 7,       // AES with 128-bit key
+        Aes192 = 8,       // AES with 192-bit key
+        Aes256 = 9,       // AES with 256-bit key
+        Twofish = 10,     // Twofish with 256-bit key [TWOFISH]
+        Camellia128 = 11, // Camellia with 128-bit key [RFC3713]
+        Camellia192 = 12, // Camellia with 192-bit key
+        Camellia256 = 13, // Camellia with 256-bit key
+
+
+        Experimental_1 = 100,
+        Experimental_2 = 101,
+        Experimental_3 = 102,
+        Experimental_4 = 103,
+        Experimental_5 = 104,
+        Experimental_6 = 105,
+        Experimental_7 = 106,
+        Experimental_8 = 107,
+        Experimental_9 = 108,
+        Experimental_10 = 109,
+        Experimental_11 = 110
     }
 }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index fa04f5f463..c760131e77 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -26,18 +26,22 @@ public sealed class PgpUtilities
 
         private static IDictionary<string, HashAlgorithmTag> CreateNameToHashID()
         {
-            var d = new Dictionary<string, HashAlgorithmTag>(StringComparer.OrdinalIgnoreCase);
-            d.Add("sha1", HashAlgorithmTag.Sha1);
-            d.Add("sha224", HashAlgorithmTag.Sha224);
-            d.Add("sha256", HashAlgorithmTag.Sha256);
-            d.Add("sha384", HashAlgorithmTag.Sha384);
-            d.Add("sha512", HashAlgorithmTag.Sha512);
-            d.Add("ripemd160", HashAlgorithmTag.RipeMD160);
-            d.Add("rmd160", HashAlgorithmTag.RipeMD160);
-            d.Add("md2", HashAlgorithmTag.MD2);
-            d.Add("tiger", HashAlgorithmTag.Tiger192);
-            d.Add("haval", HashAlgorithmTag.Haval5pass160);
-            d.Add("md5", HashAlgorithmTag.MD5);
+            var d = new Dictionary<string, HashAlgorithmTag>(StringComparer.OrdinalIgnoreCase)
+            {
+                { "sha1", HashAlgorithmTag.Sha1 },
+                { "sha224", HashAlgorithmTag.Sha224 },
+                { "sha256", HashAlgorithmTag.Sha256 },
+                { "sha384", HashAlgorithmTag.Sha384 },
+                { "sha512", HashAlgorithmTag.Sha512 },
+                { "ripemd160", HashAlgorithmTag.RipeMD160 },
+                { "rmd160", HashAlgorithmTag.RipeMD160 },
+                { "md2", HashAlgorithmTag.MD2 },
+                { "tiger", HashAlgorithmTag.Tiger192 },
+                { "haval", HashAlgorithmTag.Haval5pass160 },
+                { "md5", HashAlgorithmTag.MD5 },
+                { "Sha3_256" , HashAlgorithmTag.Sha3_256},
+                { "Sha3_512" , HashAlgorithmTag.Sha3_512},
+            };
             return d;
         }
 
@@ -102,7 +106,11 @@ public static string GetDigestName(
 				return "SHA384";
 			case HashAlgorithmTag.Sha512:
 				return "SHA512";
-			default:
+            case HashAlgorithmTag.Sha3_256:
+                return "SHA3-256";
+            case HashAlgorithmTag.Sha3_512:
+                return "SHA3-512";
+            default:
 				throw new PgpException("unknown hash algorithm tag in GetDigestName: " + hashAlgorithm);
 			}
         }
@@ -157,11 +165,18 @@ public static string GetSignatureName(
 			case PublicKeyAlgorithmTag.ElGamalGeneral:
 				encAlg = "ElGamal";
 				break;
-			default:
+            case PublicKeyAlgorithmTag.Ed25519:
+                encAlg = "Ed25519";
+                break;
+            case PublicKeyAlgorithmTag.Ed448:
+                encAlg = "Ed448";
+                break;
+            default:
 				throw new PgpException("unknown algorithm tag in signature:" + keyAlgorithm);
             }
 
-			return GetDigestName(hashAlgorithm) + "with" + encAlg;
+            string digestName = GetDigestName(hashAlgorithm);
+            return $"{digestName}with{encAlg}";
         }
 
 	    public static string GetSymmetricCipherName(

From 5dcbac68917755bbc05d382bed46b86257c9ec53 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sun, 11 Feb 2024 16:56:30 +0100
Subject: [PATCH 03/37] Padding Packet support

---
 crypto/src/bcpg/BcpgInputStream.cs            |   9 +-
 crypto/src/bcpg/PaddingPacket.cs              |  49 ++++++
 crypto/src/openpgp/PgpObjectFactory.cs        |   2 +
 crypto/src/openpgp/PgpPadding.cs              |  21 +++
 .../IgnoreMarkerPacketInCertificatesTest.cs   |  57 ++++++-
 .../src/openpgp/test/PaddingPacketTest.cs     | 142 ++++++++++++++++++
 6 files changed, 276 insertions(+), 4 deletions(-)
 create mode 100644 crypto/src/bcpg/PaddingPacket.cs
 create mode 100644 crypto/src/openpgp/PgpPadding.cs
 create mode 100644 crypto/test/src/openpgp/test/PaddingPacketTest.cs

diff --git a/crypto/src/bcpg/BcpgInputStream.cs b/crypto/src/bcpg/BcpgInputStream.cs
index 03aa717d33..efd258c9d7 100644
--- a/crypto/src/bcpg/BcpgInputStream.cs
+++ b/crypto/src/bcpg/BcpgInputStream.cs
@@ -241,6 +241,8 @@ public Packet ReadPacket()
                     return new SymmetricEncIntegrityPacket(objStream);
                 case PacketTag.ModificationDetectionCode:
                     return new ModDetectionCodePacket(objStream);
+                case PacketTag.Padding:
+                    return new PaddingPacket(objStream);
                 case PacketTag.Experimental1:
                 case PacketTag.Experimental2:
                 case PacketTag.Experimental3:
@@ -252,9 +254,14 @@ public Packet ReadPacket()
         }
 
         public PacketTag SkipMarkerPackets()
+        {
+            return SkipMarkerAndPaddingPackets();
+        }
+
+        public PacketTag SkipMarkerAndPaddingPackets()
         {
             PacketTag tag;
-            while ((tag = NextPacketTag()) == PacketTag.Marker)
+            while ((tag = NextPacketTag()) == PacketTag.Marker || tag == PacketTag.Padding)
             {
                 ReadPacket();
             }
diff --git a/crypto/src/bcpg/PaddingPacket.cs b/crypto/src/bcpg/PaddingPacket.cs
new file mode 100644
index 0000000000..4063c8a165
--- /dev/null
+++ b/crypto/src/bcpg/PaddingPacket.cs
@@ -0,0 +1,49 @@
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    public class PaddingPacket
+        : ContainedPacket
+    {
+        private readonly byte[] padding;
+
+        public PaddingPacket(BcpgInputStream bcpgIn)
+        {
+            padding = bcpgIn.ReadAll();
+        }
+
+        public PaddingPacket(int length, BcpgInputStream bcpgIn)
+        {
+            padding = new byte[length];
+            bcpgIn.ReadFully(padding);
+        }
+
+        public PaddingPacket(byte[] padding)
+        {
+            this.padding = Arrays.Clone(padding);
+        }
+
+        public PaddingPacket(int length, SecureRandom random)
+            : this(RandomBytes(length, random))
+        {
+        }
+
+        private static byte[] RandomBytes(int length, SecureRandom random)
+        {
+            byte[] bytes = new byte[length];
+            random.NextBytes(bytes);
+            return bytes;
+        }
+
+        public byte[] GetPadding()
+        {
+            return Arrays.Clone(padding);
+        }
+
+        public override void Encode(BcpgOutputStream bcpgOut)
+        {
+            bcpgOut.WritePacket(PacketTag.Padding, padding);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/openpgp/PgpObjectFactory.cs b/crypto/src/openpgp/PgpObjectFactory.cs
index 068b851542..3dabd5e5ab 100644
--- a/crypto/src/openpgp/PgpObjectFactory.cs
+++ b/crypto/src/openpgp/PgpObjectFactory.cs
@@ -101,6 +101,8 @@ public PgpObject NextPgpObject()
             }
             case PacketTag.Marker:
                 return new PgpMarker(bcpgIn);
+            case PacketTag.Padding:
+                return new PgpPadding(bcpgIn);
             case PacketTag.Experimental1:
             case PacketTag.Experimental2:
             case PacketTag.Experimental3:
diff --git a/crypto/src/openpgp/PgpPadding.cs b/crypto/src/openpgp/PgpPadding.cs
new file mode 100644
index 0000000000..0fd6019299
--- /dev/null
+++ b/crypto/src/openpgp/PgpPadding.cs
@@ -0,0 +1,21 @@
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    public class PgpPadding
+        : PgpObject
+    {
+        private readonly PaddingPacket data;
+
+        public PgpPadding(BcpgInputStream bcpgInput)
+        {
+            Packet packet = bcpgInput.ReadPacket();
+            if (!(packet is PaddingPacket paddingPacket))
+                throw new IOException("unexpected packet in stream: " + packet);
+
+            data = paddingPacket;
+        }
+
+        public byte[] GetPadding() => data.GetPadding();
+    }
+}
\ No newline at end of file
diff --git a/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs b/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs
index 71196edaef..de1ce16523 100644
--- a/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs
+++ b/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs
@@ -58,15 +58,66 @@ public class IgnoreMarkerPacketInCertificatesTest
             "=6mfA\n" +
             "-----END PGP PUBLIC KEY BLOCK-----";
 
-		public override string Name
+        // [PUBLIC KEY, MARKER, PADDING, USER-ID, SIGNATURE, SUBKEY, SIGNATURE]
+        private static readonly string CERT_WITH_MARKER_AND_PADDING = "" +
+            "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
+            "\n" +
+            "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
+            "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
+            "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
+            "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
+            "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
+            "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
+            "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
+            "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
+            "vLIwa3T4CyshfT0AEQEAAcoDUEdQ1QQo9HIzzSFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" +
+            "ZW5wZ3AuZXhhbXBsZT7CwQ4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" +
+            "F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" +
+            "2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" +
+            "yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" +
+            "doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" +
+            "BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" +
+            "sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" +
+            "4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" +
+            "L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" +
+            "ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikbO\n" +
+            "wM0EXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" +
+            "bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" +
+            "29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" +
+            "WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" +
+            "leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" +
+            "g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" +
+            "Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" +
+            "JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" +
+            "IRDMXDOPyzEfjwARAQABwsD2BBgBCgAgFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAF\n" +
+            "Al2lnPICGwwACgkQ+/zIKgFeczDp/wv/boLfh2SMF99PMyPkF3Obwy0Xrs5id4nh\n" +
+            "NAzDv7jUgvitVxIqEiGT/dR3mSdpG0/Z5/X7kXrqH39E9A4nn628HCEEBxRZK6kq\n" +
+            "dSt1VplBqdia1LFxVXY8v35ASI03e3OW6FpY7/+sALEn4r9ldCUjPBBVOk2F8bMB\n" +
+            "oxVX3Ol/e7STXiK1y/pqUpjz6stm87XAgh5FkuZTS1kMPke1YO9RXusgUjVa6gtv\n" +
+            "4pmBtifc5aMI8dV1Ot1nYKqdlsbdJfDprAf1vNEtX0ReRuEgx7PR14JV16j7AUWS\n" +
+            "WHz/lUrZvS+T/7CownF+lrWUe8kuhvM4/1++uzCyv3YwDb6T3TVZ4hJHuoTNwjQV\n" +
+            "2DwDIUATFoQrpXKM/tJcYvC9+KzDfg7G5mveqbHVK5+7i2gfdesHtAk3xfKqpuwb\n" +
+            "FQIGpaJ/1FIrjGPNFN7nqI96JIkk4hyIw/2LaV0j4qAvJzJ4O8agGPQcIs7eBVoF\n" +
+            "7i5tWuPkqOFfY9U0Ql3ddlHNpdkTZoAx\n" +
+            "=kgFK\n" +
+            "-----END PGP PUBLIC KEY BLOCK-----";
+
+
+        public override string Name
 		{
 			get { return "IgnoreMarkerPacketInCertificatesTest"; }
 		}
 
         public override void PerformTest()
+        {
+            PerformTestMarker(CERT_WITH_MARKER);
+            PerformTestMarker(CERT_WITH_MARKER_AND_PADDING);
+        }
+
+        private void PerformTestMarker(string cert)
         {
             ArmoredInputStream armorIn = new ArmoredInputStream(
-                new MemoryStream(Encoding.UTF8.GetBytes(CERT_WITH_MARKER), false));
+                new MemoryStream(Encoding.UTF8.GetBytes(cert), false));
             PgpObjectFactory objectFactory = new PgpObjectFactory(armorIn);
 
             PgpPublicKeyRing certificate = (PgpPublicKeyRing)objectFactory.NextPgpObject();
@@ -86,7 +137,7 @@ public override void PerformTest()
             IsEquals(1, Count(signatures));
         }
 
-		[Test]
+        [Test]
 		public void TestFunction()
 		{
 			string resultText = Perform().ToString();
diff --git a/crypto/test/src/openpgp/test/PaddingPacketTest.cs b/crypto/test/src/openpgp/test/PaddingPacketTest.cs
new file mode 100644
index 0000000000..8c607d7b93
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PaddingPacketTest.cs
@@ -0,0 +1,142 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class PaddingPacketTest
+        : SimpleTest
+    {
+        public override string Name => "PaddingPacketTest";
+
+        [Test]
+        public void PaddingPacketReadFromStreamTest()
+        {
+            /*
+             * Simple padding packet
+             * 
+             * 0xD5                   Packet tag (0xC0 | 0x15)
+             * 0x04                   Length
+             * 0x01 0x02 0x03 0x04    Padding content
+             */
+            byte[] packet = Hex.Decode("D50401020304");
+            byte[] expected = Hex.Decode("01020304");
+
+            using (Stream input = new MemoryStream(packet))
+            {
+                PgpObjectFactory objectFactory = new PgpObjectFactory(input);
+                PgpObject obj = objectFactory.NextPgpObject();
+
+                IsTrue(obj is PgpPadding);
+
+                byte[] padding = (obj as PgpPadding).GetPadding();
+
+                IsEquals($"unexpected padding length: expected {expected.Length} got {padding.Length}", padding.Length, expected.Length);
+                FailIf($"unexpected padding", !AreEqual(padding, expected));
+            }
+        }
+
+
+        [Test]
+        public void PaddingPacketReadThreePacketsFromStreamTest()
+        {
+            byte[] packet = Hex.Decode("D50401020304D503556677D503AABBCC");
+            byte[][] expected = new byte[][] {
+                Hex.Decode("01020304"),
+                Hex.Decode("556677"),
+                Hex.Decode("AABBCC")
+            };
+
+            using (Stream input = new MemoryStream(packet))
+            {
+                PgpObjectFactory objectFactory = new PgpObjectFactory(input);
+
+                int i = 0;
+                PgpObject obj;
+                while ((obj = objectFactory.NextPgpObject()) != null)
+                {
+                    IsTrue(obj is PgpPadding);
+                    byte[] padding = (obj as PgpPadding).GetPadding();
+
+                    IsEquals($"unexpected padding length: expected {expected[i].Length} got {padding.Length}", padding.Length, expected[i].Length);
+                    FailIf($"unexpected padding", !AreEqual(padding, expected[i]));
+                    ++i;
+                }
+
+                IsEquals(i, 3);
+            }
+        }
+
+        [Test]
+        public void PaddingPacketEncodeTest()
+        {
+            byte[] encoded = Hex.Decode("D50401020304");
+
+            byte[] padding = Hex.Decode("01020304");
+            PaddingPacket packet = new PaddingPacket(padding);
+
+            using (MemoryStream output = new MemoryStream())
+            {
+                BcpgOutputStream bcOut = new BcpgOutputStream(output);
+                packet.Encode(bcOut);
+                bcOut.Close();
+
+                FailIf("wrong encoding", !AreEqual(output.ToArray(), encoded));
+            }
+        }
+
+        [Test]
+        public void PaddingPacketEncodeThenDecodeTest()
+        {
+            SecureRandom random = new SecureRandom();
+            PaddingPacket packet = new PaddingPacket(32, random);
+
+            using (MemoryStream output = new MemoryStream())
+            {
+                BcpgOutputStream bcOut = new BcpgOutputStream(output);
+                packet.Encode(bcOut);
+                bcOut.Close();
+
+                using (Stream input = new MemoryStream(output.ToArray()))
+                {
+                    PgpObjectFactory factory = new PgpObjectFactory(input);
+
+                    PgpPadding padding = (PgpPadding)factory.NextPgpObject();
+                    IsTrue(Arrays.AreEqual(packet.GetPadding(), padding.GetPadding()));
+                }
+            }
+        }
+
+        [Test]
+        public void KnownPaddingBytesTest()
+        {
+            byte[] known = Strings.ToByteArray("thisIsKnownPadding");
+            PaddingPacket packet = new PaddingPacket(known);
+            IsTrue(Arrays.AreEqual(known, packet.GetPadding()));
+        }
+
+        [Test]
+        public void Random50BytesTest()
+        {
+            int len = 50;
+            SecureRandom random = new SecureRandom();
+            PaddingPacket packet = new PaddingPacket(len, random);
+            IsEquals(len, packet.GetPadding().Length);
+        }
+
+
+        public override void PerformTest()
+        {
+            PaddingPacketReadFromStreamTest();
+            PaddingPacketReadThreePacketsFromStreamTest();
+            PaddingPacketEncodeTest();
+            PaddingPacketEncodeThenDecodeTest();
+            KnownPaddingBytesTest();
+            Random50BytesTest();
+        }
+    }
+}
\ No newline at end of file

From 091b92ed8db447dfe11e2807e3d0c3e3d9821e7a Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Tue, 13 Feb 2024 19:23:37 +0100
Subject: [PATCH 04/37] Initial support for Version6 keys and signatures

- Ed25519Legacy with v4 keys/signatures
- classes for "native" (octet array) key material
- parsing v5/v6 packets (public key, unlocked secret key, signature)
- basic verification of v6 signatures
- test vectors from crypto-refresh draft
---
 crypto/src/bcpg/Ed25519PublicBCPGKey.cs       |  19 ++
 crypto/src/bcpg/Ed25519SecretBCPGKey.cs       |  19 ++
 crypto/src/bcpg/Ed448PublicBCPGKey.cs         |  19 ++
 crypto/src/bcpg/Ed448SecretBCPGKey.cs         |  19 ++
 crypto/src/bcpg/OctetArrayBCPGKey.cs          |  48 +++
 crypto/src/bcpg/PublicKeyAlgorithmTags.cs     |   2 +-
 crypto/src/bcpg/PublicKeyPacket.cs            |  50 +++-
 crypto/src/bcpg/SecretKeyPacket.cs            |  38 ++-
 crypto/src/bcpg/SignaturePacket.cs            | 147 ++++++++--
 crypto/src/bcpg/X25519PublicBCPGKey.cs        |  19 ++
 crypto/src/bcpg/X25519SecretBCPGKey.cs        |  19 ++
 crypto/src/bcpg/X448PublicBCPGKey.cs          |  19 ++
 crypto/src/bcpg/X448SecretBCPGKey.cs          |  19 ++
 crypto/src/openpgp/PgpPublicKey.cs            |  74 ++++-
 crypto/src/openpgp/PgpSecretKey.cs            |  24 ++
 crypto/src/openpgp/PgpSignature.cs            |  19 +-
 crypto/src/openpgp/PgpUtilities.cs            |   2 +
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 273 ++++++++++++++++++
 18 files changed, 761 insertions(+), 68 deletions(-)
 create mode 100644 crypto/src/bcpg/Ed25519PublicBCPGKey.cs
 create mode 100644 crypto/src/bcpg/Ed25519SecretBCPGKey.cs
 create mode 100644 crypto/src/bcpg/Ed448PublicBCPGKey.cs
 create mode 100644 crypto/src/bcpg/Ed448SecretBCPGKey.cs
 create mode 100644 crypto/src/bcpg/OctetArrayBCPGKey.cs
 create mode 100644 crypto/src/bcpg/X25519PublicBCPGKey.cs
 create mode 100644 crypto/src/bcpg/X25519SecretBCPGKey.cs
 create mode 100644 crypto/src/bcpg/X448PublicBCPGKey.cs
 create mode 100644 crypto/src/bcpg/X448SecretBCPGKey.cs
 create mode 100644 crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs

diff --git a/crypto/src/bcpg/Ed25519PublicBCPGKey.cs b/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
new file mode 100644
index 0000000000..8026579097
--- /dev/null
+++ b/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class Ed25519PublicBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed2
+        public const int length = 32;
+
+        public Ed25519PublicBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public Ed25519PublicBcpgKey(byte[] key)
+            :base(length, key)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/Ed25519SecretBCPGKey.cs b/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
new file mode 100644
index 0000000000..e161707e9b
--- /dev/null
+++ b/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class Ed25519SecretBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed2
+        public const int length = 32;
+
+        public Ed25519SecretBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public Ed25519SecretBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/Ed448PublicBCPGKey.cs b/crypto/src/bcpg/Ed448PublicBCPGKey.cs
new file mode 100644
index 0000000000..fc0283969a
--- /dev/null
+++ b/crypto/src/bcpg/Ed448PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class Ed448PublicBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed4
+        public const int length = 57;
+
+        public Ed448PublicBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public Ed448PublicBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
diff --git a/crypto/src/bcpg/Ed448SecretBCPGKey.cs b/crypto/src/bcpg/Ed448SecretBCPGKey.cs
new file mode 100644
index 0000000000..3b355ceb95
--- /dev/null
+++ b/crypto/src/bcpg/Ed448SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class Ed448SecretBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed4
+        public const int length = 57;
+
+        public Ed448SecretBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public Ed448SecretBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
diff --git a/crypto/src/bcpg/OctetArrayBCPGKey.cs b/crypto/src/bcpg/OctetArrayBCPGKey.cs
new file mode 100644
index 0000000000..1bf09958ec
--- /dev/null
+++ b/crypto/src/bcpg/OctetArrayBCPGKey.cs
@@ -0,0 +1,48 @@
+using Org.BouncyCastle.Utilities;
+using System;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    /**
+     * Public/Secret BcpgKey which is encoded as an array of octets rather than an MPI
+     * 
+     */
+    public abstract class OctetArrayBcpgKey
+        : BcpgObject, IBcpgKey
+    {
+        private readonly byte[] key;
+
+        protected OctetArrayBcpgKey(int length, BcpgInputStream bcpgIn)
+        {
+            key = new byte[length];
+            bcpgIn.ReadFully(key);
+        }
+
+        protected OctetArrayBcpgKey(int length, byte[] key)
+        {
+            if (key.Length != length)
+            {
+                throw new ArgumentException("unexpected key encoding length: expected " + length + " bytes, got " + key.Length);
+            }
+
+            this.key = Arrays.Clone(key);
+        }
+
+        /// <inheritdoc/>
+        public string Format
+        {
+            get { return "PGP"; }
+        }
+
+        /// <inheritdoc/>
+        public override void Encode(BcpgOutputStream bcpgOut)
+        {
+            bcpgOut.Write(key);
+        }
+
+        public byte[] GetKey()
+        {
+            return Arrays.Clone(key);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
index 7fa4d728ae..a88a84c0d6 100644
--- a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
@@ -21,7 +21,7 @@ public enum PublicKeyAlgorithmTag
         EdDsa = 22,             // EdDSA - (internet draft, but appearing in use)
         EdDsa_Legacy = 22,      // new name for old EdDSA tag.
 
-        // defined as Reserver by crypto-refresh draft
+        // defined as Reserved by crypto-refresh draft
         AEDH = 23,
         AEDSA = 24,
 
diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index 639d5595ca..26cc069d96 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -9,11 +9,19 @@ namespace Org.BouncyCastle.Bcpg
     public class PublicKeyPacket
         : ContainedPacket //, PublicKeyAlgorithmTag
     {
-        private int version;
-        private long time;
-        private int validDays;
-        private PublicKeyAlgorithmTag algorithm;
-        private IBcpgKey key;
+        public const int Version2 = 2;
+        public const int Version3 = 3;
+        public const int Version4 = 4;
+        public const int Version5 = 5;
+        public const int Version6 = 6;
+
+        private readonly int version;
+        private readonly long time;
+        private readonly int validDays;
+        private readonly PublicKeyAlgorithmTag algorithm;
+        private readonly IBcpgKey key;
+
+        private readonly long v6KeyLen;
 
         internal PublicKeyPacket(
             BcpgInputStream bcpgIn)
@@ -23,13 +31,19 @@ internal PublicKeyPacket(
             time = ((uint)bcpgIn.ReadByte() << 24) | ((uint)bcpgIn.ReadByte() << 16)
                 | ((uint)bcpgIn.ReadByte() << 8) | (uint)bcpgIn.ReadByte();
 
-            if (version <= 3)
+            if (version <= Version3)
             {
                 validDays = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte();
             }
 
             algorithm = (PublicKeyAlgorithmTag)bcpgIn.ReadByte();
 
+            if (version == Version5 || version == Version6)
+            {
+                v6KeyLen = ((uint)bcpgIn.ReadByte() << 24) | ((uint)bcpgIn.ReadByte() << 16)
+                    | ((uint)bcpgIn.ReadByte() << 8) | (uint)bcpgIn.ReadByte();
+            }
+
             switch (algorithm)
             {
             case PublicKeyAlgorithmTag.RsaEncrypt:
@@ -53,8 +67,20 @@ internal PublicKeyPacket(
             case PublicKeyAlgorithmTag.EdDsa_Legacy:
                 key = new EdDsaPublicBcpgKey(bcpgIn);
                 break;
+            case PublicKeyAlgorithmTag.Ed25519:
+                key = new Ed25519PublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.Ed448:
+                key = new Ed448PublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.X25519:
+                key = new X25519PublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.X448:
+                key = new X448PublicBcpgKey(bcpgIn);
+                break;
             default:
-                throw new IOException("unknown PGP public key algorithm encountered");
+            throw new IOException("unknown PGP public key algorithm encountered");
             }
         }
 
@@ -64,7 +90,7 @@ public PublicKeyPacket(
             DateTime				time,
             IBcpgKey				key)
         {
-            this.version = 4;
+            this.version = Version4;
             this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
             this.algorithm = algorithm;
             this.key = key;
@@ -103,12 +129,18 @@ public virtual byte[] GetEncodedContents()
                 pOut.WriteByte((byte)version);
                 pOut.WriteInt((int)time);
 
-                if (version <= 3)
+                if (version <= Version3)
                 {
                     pOut.WriteShort((short)validDays);
                 }
 
                 pOut.WriteByte((byte)algorithm);
+
+                if (version == Version5 || version == Version6)
+                {
+                    pOut.WriteInt((int)v6KeyLen);
+                }
+
                 pOut.WriteObject((BcpgObject)key);
             }
             return bOut.ToArray();
diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index 15b89b4049..c5b7e81266 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -1,5 +1,5 @@
+using System;
 using System.IO;
-
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg
@@ -11,15 +11,18 @@ public class SecretKeyPacket
 		public const int UsageNone = 0x00;
 		public const int UsageChecksum = 0xff;
 		public const int UsageSha1 = 0xfe;
+        public const int UsageAead = 0xfd;
+
 
-		private PublicKeyPacket pubKeyPacket;
+        private readonly PublicKeyPacket pubKeyPacket;
         private readonly byte[] secKeyData;
-		private int s2kUsage;
-		private SymmetricKeyAlgorithmTag encAlgorithm;
-        private S2k s2k;
-        private byte[] iv;
+		private readonly int s2kUsage;
+		private readonly SymmetricKeyAlgorithmTag encAlgorithm;
+        private readonly S2k s2k;
+        private readonly byte[] iv;
+        private readonly AeadAlgorithmTag aeadAlgo;
 
-		internal SecretKeyPacket(
+        internal SecretKeyPacket(
             BcpgInputStream bcpgIn)
         {
 			if (this is SecretSubkeyPacket)
@@ -33,17 +36,32 @@ internal SecretKeyPacket(
 
 			s2kUsage = bcpgIn.ReadByte();
 
-			if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1)
+            if (s2kUsage != UsageNone && pubKeyPacket.Version == PublicKeyPacket.Version6)
+            {
+                // TODO: Use length to parse unknown parameters
+                int conditionalParameterLength = bcpgIn.ReadByte();
+            }
+
+            if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1 || s2kUsage == UsageAead)
             {
                 encAlgorithm = (SymmetricKeyAlgorithmTag) bcpgIn.ReadByte();
+                if (s2kUsage == UsageAead)
+                {
+                    aeadAlgo = (AeadAlgorithmTag)bcpgIn.ReadByte();
+                }
+                if (pubKeyPacket.Version == 6 && (s2kUsage == UsageSha1 || s2kUsage == UsageAead))
+                {
+                    // TODO: Use length to parse unknown S2Ks
+                    int s2kLen = bcpgIn.ReadByte();
+                }
                 s2k = new S2k(bcpgIn);
             }
             else
             {
                 encAlgorithm = (SymmetricKeyAlgorithmTag) s2kUsage;
-			}
+            }
 
-			if (!(s2k != null && s2k.Type == S2k.GnuDummyS2K && s2k.ProtectionMode == 0x01))
+            if (!(s2k != null && s2k.Type == S2k.GnuDummyS2K && s2k.ProtectionMode == 0x01))
             {
 				if (s2kUsage != 0)
 				{
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index ff3e5d3f9c..79dc8cebe7 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -14,25 +14,33 @@ namespace Org.BouncyCastle.Bcpg
     public class SignaturePacket
         : ContainedPacket
     {
-		private int						version;
-        private int						signatureType;
-        private long					creationTime;
-        private long					keyId;
-        private PublicKeyAlgorithmTag	keyAlgorithm;
-        private HashAlgorithmTag		hashAlgorithm;
-        private MPInteger[]				signature;
-        private byte[]					fingerprint;
-        private SignatureSubpacket[]	hashedData;
-        private SignatureSubpacket[]	unhashedData;
-		private byte[]					signatureEncoding;
-
-		internal SignaturePacket(BcpgInputStream bcpgIn)
+        public const int Version2 = 2;
+        public const int Version3 = 3;
+        public const int Version4 = 4;
+        public const int Version5 = 5;
+        public const int Version6 = 6;
+
+        private readonly int                    version;
+        private readonly int                    signatureType;
+        private long                            creationTime;
+        private readonly long                   keyId;
+        private readonly PublicKeyAlgorithmTag  keyAlgorithm;
+        private readonly HashAlgorithmTag       hashAlgorithm;
+        private readonly MPInteger[]            signature;
+        private readonly byte[]                 fingerprint;
+        private readonly SignatureSubpacket[]   hashedData;
+        private readonly SignatureSubpacket[]   unhashedData;
+		private readonly byte[]                 signatureEncoding;
+
+        // fields for v6 signatures
+        private readonly byte[] salt;
+
+        internal SignaturePacket(BcpgInputStream bcpgIn)
         {
             version = bcpgIn.ReadByte();
 
-			if (version == 3 || version == 2)
+			if (version == Version2 || version == Version3)
             {
-//                int l =
                 bcpgIn.ReadByte();
 
 				signatureType = bcpgIn.ReadByte();
@@ -51,13 +59,26 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
 				keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte();
                 hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte();
             }
-            else if (version == 4)
+            else if (version >= Version4 && version <= Version6)
             {
                 signatureType = bcpgIn.ReadByte();
                 keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte();
                 hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte();
 
-				int hashedLength = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte();
+                int hashedLength;
+
+                if (version == Version6)
+                {
+                    hashedLength = (bcpgIn.ReadByte() << 24)
+                        | (bcpgIn.ReadByte() << 16)
+                        | (bcpgIn.ReadByte() << 8)
+                        | bcpgIn.ReadByte();
+                }
+                else
+                {
+                    hashedLength = (bcpgIn.ReadByte() << 8)
+                        | bcpgIn.ReadByte();
+                }
                 byte[] hashed = new byte[hashedLength];
 
 				bcpgIn.ReadFully(hashed);
@@ -90,7 +111,21 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
                     }
                 }
 
-				int unhashedLength = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte();
+                int unhashedLength;
+
+                if (version == Version6)
+                {
+                    unhashedLength = (bcpgIn.ReadByte() << 24)
+                        | (bcpgIn.ReadByte() << 16)
+                        | (bcpgIn.ReadByte() << 8)
+                        | bcpgIn.ReadByte();
+                }
+                else
+                {
+                    unhashedLength = (bcpgIn.ReadByte() << 8)
+                        | bcpgIn.ReadByte();
+                }
+
                 byte[] unhashed = new byte[unhashedLength];
 
 				bcpgIn.ReadFully(unhashed);
@@ -124,7 +159,14 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
 			fingerprint = new byte[2];
             bcpgIn.ReadFully(fingerprint);
 
-			switch (keyAlgorithm)
+            if (version == Version6)
+            {
+                int saltSize = bcpgIn.ReadByte();
+                salt = new byte[saltSize];
+                bcpgIn.ReadFully(salt);
+            }
+
+            switch (keyAlgorithm)
             {
             case PublicKeyAlgorithmTag.RsaGeneral:
             case PublicKeyAlgorithmTag.RsaSign:
@@ -149,6 +191,20 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
                 MPInteger ecS = new MPInteger(bcpgIn);
                 signature = new MPInteger[2]{ ecR, ecS };
                 break;
+
+            case PublicKeyAlgorithmTag.Ed25519:
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-ed2
+                signature = null;
+                signatureEncoding = new byte[64];
+                bcpgIn.ReadFully(signatureEncoding);
+                break;
+            case PublicKeyAlgorithmTag.Ed448:
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-ed4
+                signature = null;
+                signatureEncoding = new byte[114];
+                bcpgIn.ReadFully(signatureEncoding);
+                break;
+
             default:
 				if (keyAlgorithm < PublicKeyAlgorithmTag.Experimental_1 || keyAlgorithm > PublicKeyAlgorithmTag.Experimental_11)
                     throw new IOException("unknown signature key algorithm: " + keyAlgorithm);
@@ -179,7 +235,7 @@ public SignaturePacket(
             SignatureSubpacket[]	unhashedData,
             byte[]					fingerprint,
             MPInteger[]				signature)
-            : this(4, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, signature)
+            : this(Version4, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, signature)
         {
         }
 
@@ -260,7 +316,7 @@ public byte[] GetFingerprint()
         */
         public byte[] GetSignatureTrailer()
         {
-			if (version == 3)
+			if (version == Version3)
             {
                 long time = creationTime / 1000L;
 
@@ -277,8 +333,14 @@ public byte[] GetSignatureTrailer()
             sOut.WriteByte((byte)KeyAlgorithm);
             sOut.WriteByte((byte)HashAlgorithm);
 
-            // Mark position an reserve two bytes for length
+            // Mark position an reserve two bytes (version4) or four bytes (version6)
+            // for length
             long lengthPosition = sOut.Position;
+            if (version == Version6)
+            {
+                sOut.WriteByte(0x00);
+                sOut.WriteByte(0x00);
+            }
             sOut.WriteByte(0x00);
             sOut.WriteByte(0x00);
 
@@ -289,6 +351,11 @@ public byte[] GetSignatureTrailer()
             }
 
             ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2);
+            if (version == Version6)
+            {
+                dataLength -= 2;
+            }
+
             uint hDataLength = Convert.ToUInt32(sOut.Position);
 
 			sOut.WriteByte((byte)Version);
@@ -300,6 +367,11 @@ public byte[] GetSignatureTrailer()
 
             // Reset position and fill in length
             sOut.Position = lengthPosition;
+            if (version == Version6)
+            {
+                sOut.WriteByte((byte)(dataLength >> 24));
+                sOut.WriteByte((byte)(dataLength >> 16));
+            }
             sOut.WriteByte((byte)(dataLength >> 8));
             sOut.WriteByte((byte)(dataLength     ));
 
@@ -316,6 +388,11 @@ public byte[] GetSignatureTrailer()
         */
         public MPInteger[] GetSignature() => signature;
 
+        public byte[] GetSignatureSalt()
+        {
+            return Arrays.Clone(salt);
+        }
+
 		/**
 		 * Return the byte encoding of the signature section.
 		 * @return uninterpreted signature bytes.
@@ -323,7 +400,7 @@ public byte[] GetSignatureTrailer()
 		public byte[] GetSignatureBytes()
 		{
 			if (signatureEncoding != null)
-				return (byte[])signatureEncoding.Clone();
+				return Arrays.Clone(signatureEncoding);
 
 			MemoryStream bOut = new MemoryStream();
 
@@ -359,7 +436,7 @@ public override void Encode(BcpgOutputStream bcpgOut)
             {
                 pOut.WriteByte((byte)version);
 
-                if (version == 3 || version == 2)
+                if (version == Version3 || version == Version2)
                 {
                     byte nextBlockLength = 5;
                     pOut.Write(nextBlockLength, (byte)signatureType);
@@ -367,11 +444,11 @@ public override void Encode(BcpgOutputStream bcpgOut)
                     pOut.WriteLong(keyId);
                     pOut.Write((byte)keyAlgorithm, (byte)hashAlgorithm);
                 }
-                else if (version == 4)
+                else if (version >= Version4 && version <= Version6)
                 {
                     pOut.Write((byte)signatureType, (byte)keyAlgorithm, (byte)hashAlgorithm);
-                    EncodeLengthAndData(pOut, GetEncodedSubpackets(hashedData));
-                    EncodeLengthAndData(pOut, GetEncodedSubpackets(unhashedData));
+                    EncodeLengthAndData(version, pOut, GetEncodedSubpackets(hashedData));
+                    EncodeLengthAndData(version, pOut, GetEncodedSubpackets(unhashedData));
                 }
                 else
                 {
@@ -380,6 +457,12 @@ public override void Encode(BcpgOutputStream bcpgOut)
 
                 pOut.Write(fingerprint);
 
+                if (version == Version6)
+                {
+                    pOut.WriteByte((byte)salt.Length);
+                    pOut.Write(salt); 
+                }
+
                 if (signature != null)
                 {
                     pOut.WriteObjects(signature);
@@ -394,10 +477,18 @@ public override void Encode(BcpgOutputStream bcpgOut)
         }
 
 		private static void EncodeLengthAndData(
+            int version,
 			BcpgOutputStream	pOut,
 			byte[]				data)
 		{
-			pOut.WriteShort((short) data.Length);
+            if (version == Version6)
+            {
+                pOut.WriteInt(data.Length);
+            }
+            else
+            {
+                pOut.WriteShort((short)data.Length);
+            }
 			pOut.Write(data);
 		}
 
diff --git a/crypto/src/bcpg/X25519PublicBCPGKey.cs b/crypto/src/bcpg/X25519PublicBCPGKey.cs
new file mode 100644
index 0000000000..b8eb52a4c7
--- /dev/null
+++ b/crypto/src/bcpg/X25519PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class X25519PublicBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x
+        public const int length = 32;
+
+        public X25519PublicBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public X25519PublicBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/X25519SecretBCPGKey.cs b/crypto/src/bcpg/X25519SecretBCPGKey.cs
new file mode 100644
index 0000000000..3da6f01b6a
--- /dev/null
+++ b/crypto/src/bcpg/X25519SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class X25519SecretBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x
+        public const int length = 32;
+
+        public X25519SecretBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public X25519SecretBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/X448PublicBCPGKey.cs b/crypto/src/bcpg/X448PublicBCPGKey.cs
new file mode 100644
index 0000000000..9b35cbd3b5
--- /dev/null
+++ b/crypto/src/bcpg/X448PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class X448PublicBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x4
+        public const int length = 56;
+
+        public X448PublicBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public X448PublicBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/X448SecretBCPGKey.cs b/crypto/src/bcpg/X448SecretBCPGKey.cs
new file mode 100644
index 0000000000..a8ce5e8097
--- /dev/null
+++ b/crypto/src/bcpg/X448SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class X448SecretBcpgKey
+        : OctetArrayBcpgKey
+    {
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x4
+        public const int length = 56;
+
+        public X448SecretBcpgKey(BcpgInputStream bcpgIn)
+            : base(length, bcpgIn)
+        {
+        }
+
+        public X448SecretBcpgKey(byte[] key)
+            : base(length, key)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index fa924ff37f..06f9e22776 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -11,6 +11,7 @@
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Math.EC.Rfc7748;
@@ -25,6 +26,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
     public class PgpPublicKey
         : PgpObject
     {
+        private const byte v4FingerprintPreamble = 0x99;
+        private const byte v5FingerprintPreamble = 0x9A;
+        private const byte v6FingerprintPreamble = 0x9B;
+
         // We default to these as they are specified as mandatory in RFC 6631.
         private static readonly PgpKdfParameters DefaultKdfParameters = new PgpKdfParameters(HashAlgorithmTag.Sha256,
             SymmetricKeyAlgorithmTag.Aes128);
@@ -34,7 +39,7 @@ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
             IBcpgKey key = publicPk.Key;
             IDigest digest;
 
-            if (publicPk.Version <= 3)
+            if (publicPk.Version <= PublicKeyPacket.Version3)
             {
                 RsaPublicBcpgKey rK = (RsaPublicBcpgKey)key;
 
@@ -56,11 +61,29 @@ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
                 {
                     byte[] kBytes = publicPk.GetEncodedContents();
 
-                    digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
+                    if (publicPk.Version == PublicKeyPacket.Version4)
+                    {
+                        digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
+
+                        digest.Update(v4FingerprintPreamble);
+                        digest.Update((byte)(kBytes.Length >> 8));
+                        digest.Update((byte)kBytes.Length);
+                    }
+                    else if (publicPk.Version == PublicKeyPacket.Version5 || publicPk.Version == PublicKeyPacket.Version6)
+                    {
+                        digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+
+                        digest.Update(publicPk.Version == PublicKeyPacket.Version5 ? v5FingerprintPreamble : v6FingerprintPreamble);
+                        digest.Update((byte)(kBytes.Length >> 24));
+                        digest.Update((byte)(kBytes.Length >> 16));
+                        digest.Update((byte)(kBytes.Length >> 8));
+                        digest.Update((byte)kBytes.Length);
+                    }
+                    else
+                    {
+                        throw new PgpException("unsupported OpenPGP key packet version: " + publicPk.Version);
+                    }
 
-                    digest.Update(0x99);
-                    digest.Update((byte)(kBytes.Length >> 8));
-                    digest.Update((byte)kBytes.Length);
                     digest.BlockUpdate(kBytes, 0, kBytes.Length);
                 }
                 catch (Exception e)
@@ -106,7 +129,7 @@ private void Init()
 
             this.fingerprint = CalculateFingerprint(publicPk);
 
-            if (publicPk.Version <= 3)
+            if (publicPk.Version <= PublicKeyPacket.Version3)
             {
                 RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
 
@@ -115,14 +138,14 @@ private void Init()
             }
             else
             {
-                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 (publicPk.Version == PublicKeyPacket.Version4)
+                {
+                    this.keyId = (long)Pack.BE_To_UInt64(fingerprint, fingerprint.Length - 8);
+                }
+                else
+                {
+                    this.keyId = (long)Pack.BE_To_UInt64(fingerprint);
+                }
 
                 if (key is RsaPublicBcpgKey)
                 {
@@ -408,7 +431,7 @@ public byte[] GetTrustData()
         /// <summary>The number of valid seconds from creation time - zero means no expiry.</summary>
         public long GetValidSeconds()
         {
-            if (publicPk.Version <= 3)
+            if (publicPk.Version <= PublicKeyPacket.Version3)
             {
                 return (long)publicPk.ValidDays * (24 * 60 * 60);
             }
@@ -511,6 +534,8 @@ public bool IsEncryptionKey
                     case PublicKeyAlgorithmTag.ElGamalGeneral:
                     case PublicKeyAlgorithmTag.RsaEncrypt:
                     case PublicKeyAlgorithmTag.RsaGeneral:
+                    case PublicKeyAlgorithmTag.X25519:
+                    case PublicKeyAlgorithmTag.X448:
                         return true;
                     default:
                         return false;
@@ -643,8 +668,25 @@ public AsymmetricKeyParameter GetKey()
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
                     return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
+
+                case PublicKeyAlgorithmTag.Ed25519:
+                    Ed25519PublicBcpgKey ed25519key = (Ed25519PublicBcpgKey)publicPk.Key;
+                    return new Ed25519PublicKeyParameters(ed25519key.GetKey());
+
+                case PublicKeyAlgorithmTag.X25519:
+                    X25519PublicBcpgKey x25519key = (X25519PublicBcpgKey)publicPk.Key;
+                    return new X25519PublicKeyParameters(x25519key.GetKey());
+
+                case PublicKeyAlgorithmTag.Ed448:
+                    Ed448PublicBcpgKey ed448key = (Ed448PublicBcpgKey)publicPk.Key;
+                    return new Ed448PublicKeyParameters(ed448key.GetKey());
+
+                case PublicKeyAlgorithmTag.X448:
+                    X448PublicBcpgKey x448key = (X448PublicBcpgKey)publicPk.Key;
+                    return new X448PublicKeyParameters(x448key.GetKey());
+
                 default:
-                    throw new PgpException("unknown public key algorithm encountered");
+                throw new PgpException("unknown public key algorithm encountered");
                 }
             }
             catch (PgpException)
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 184621b5c1..320c2956d8 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -455,6 +455,8 @@ public bool IsSigningKey
                     case PublicKeyAlgorithmTag.ECDsa:
                     case PublicKeyAlgorithmTag.EdDsa_Legacy:
                     case PublicKeyAlgorithmTag.ElGamalGeneral:
+                    case PublicKeyAlgorithmTag.Ed25519:
+                    case PublicKeyAlgorithmTag.Ed448:
                         return true;
                     default:
                         return false;
@@ -777,6 +779,28 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP
                     ElGamalParameters elParams = new ElGamalParameters(elPub.P, elPub.G);
                     privateKey = new ElGamalPrivateKeyParameters(elPriv.X, elParams);
                     break;
+
+
+                case PublicKeyAlgorithmTag.Ed25519:
+                    Ed25519SecretBcpgKey ed25519key = new Ed25519SecretBcpgKey(bcpgIn);
+                    privateKey = new Ed25519PrivateKeyParameters(ed25519key.GetKey());
+                    break;
+
+                case PublicKeyAlgorithmTag.X25519:
+                    X25519SecretBcpgKey x25519key = new X25519SecretBcpgKey(bcpgIn);
+                    privateKey = new X25519PrivateKeyParameters(x25519key.GetKey());
+                    break;
+
+                case PublicKeyAlgorithmTag.Ed448:
+                    Ed448SecretBcpgKey ed448key = new Ed448SecretBcpgKey(bcpgIn);
+                    privateKey = new Ed448PrivateKeyParameters(ed448key.GetKey());
+                    break;
+
+                case PublicKeyAlgorithmTag.X448:
+                    X448SecretBcpgKey x448key = new X448SecretBcpgKey(bcpgIn);
+                    privateKey = new X448PrivateKeyParameters(x448key.GetKey());
+                    break;
+
                 default:
                     throw new PgpException("unknown public key algorithm encountered");
                 }
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index d6ffc0f747..4505c72c85 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -109,6 +109,12 @@ public void InitVerify(PgpPublicKey pubKey)
             try
             {
                 sig.Init(false, key);
+
+                if (Version == SignaturePacket.Version6)
+                {
+                    byte[] salt = GetSignatureSalt();
+                    sig.BlockUpdate(salt, 0, salt.Length);
+                }
             }
             catch (InvalidKeyException e)
             {
@@ -332,10 +338,15 @@ public byte[] GetSignatureTrailer()
             return sigPck.GetSignatureTrailer();
         }
 
-		/// <summary>
-		/// Return true if the signature has either hashed or unhashed subpackets.
-		/// </summary>
-		public bool HasSubpackets
+        public byte[] GetSignatureSalt()
+        {
+            return sigPck.GetSignatureSalt();
+        }
+
+        /// <summary>
+        /// Return true if the signature has either hashed or unhashed subpackets.
+        /// </summary>
+        public bool HasSubpackets
 		{
 			get
 			{
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index c760131e77..150b2fe2a6 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -562,6 +562,8 @@ internal static ISigner CreateSigner(PublicKeyAlgorithmTag publicKeyAlgorithm, H
             switch (publicKeyAlgorithm)
             {
             case PublicKeyAlgorithmTag.EdDsa_Legacy:
+            case PublicKeyAlgorithmTag.Ed25519:
+            case PublicKeyAlgorithmTag.Ed448:
             {
                 ISigner signer;
                 if (key is Ed25519PrivateKeyParameters || key is Ed25519PublicKeyParameters)
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
new file mode 100644
index 0000000000..f6029f1d4f
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -0,0 +1,273 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System;
+using System.Linq;
+using System.Text;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class PgpCryptoRefreshTest
+        : SimpleTest
+    {
+        public override string Name => "PgpCryptoRefreshTest";
+
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
+        private readonly byte[] v4Ed25519LegacyPubkeySample = Base64.Decode(
+            "xjMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" +
+            "Q+47JAY=");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-sig
+        private readonly byte[] v4Ed25519LegacySignatureSample = Base64.Decode(
+            "iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgD/VvkMypjiECY3vZg/2xbBMd/S" +
+            "ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+        private readonly byte[] v6Certificate = Base64.Decode(
+            "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf" +
+            "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy" +
+            "KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw" +
+            "gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE" +
+            "QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn" +
+            "+eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh" +
+            "BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8" +
+            "j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805" +
+            "I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
+        private readonly byte[] v6UnlockedSecretKey = Base64.Decode(
+            "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB" +
+            "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ" +
+            "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+            "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh" +
+            "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe" +
+            "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/" +
+            "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG" +
+            "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+            "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE" +
+            "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr" +
+            "k0mXubZvyl4GBg==");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-locked-v6-secret-key
+        private readonly byte[] v6LockedSecretKey = Base64.Decode(
+            "xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC" +
+            "FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS" +
+            "3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC" +
+            "Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW" +
+            "cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin" +
+            "7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/" +
+            "0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0" +
+            "gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf" +
+            "9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR" +
+            "v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr" +
+            "DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki" +
+            "Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt" +
+            "ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-cleartext-signed-mes
+        private readonly string v6SampleCleartextSignedMessage = "What we need from the grocery store:\r\n\r\n- tofu\r\n- vegetables\r\n- noodles\r\n";
+        private readonly byte[] v6SampleCleartextSignedMessageSignature = Base64.Decode(
+            "wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+            "2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo" +
+            "/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr" +
+            "NK2ay45cX1IVAQ==");
+
+        [Test]
+        public void Version4Ed25519LegacyPubkeySampleTest()
+        {
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v4Ed25519LegacyPubkeySample);
+            PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+            IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+            IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20140819142827");
+
+            byte[] expectedFingerprint = Hex.Decode("C959BDBAFA32A2F89A153B678CFDE12197965A9A");
+            IsEquals((ulong)pubKey.KeyId, 0x8CFDE12197965A9A);
+            IsTrue("wrong fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
+        }
+
+        [Test]
+        public void Version4Ed25519LegacyCreateTest()
+        {
+            var key = new Ed25519PublicKeyParameters(Hex.Decode("3f098994bdd916ed4053197934e4a87c80733a1280d62f8010992e43ee3b2406"));
+            var pubKey = new PgpPublicKey(PublicKeyAlgorithmTag.EdDsa_Legacy, key, DateTime.Parse("2014-08-19 14:28:27Z"));
+            IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+            IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20140819142827");
+
+            byte[] expectedFingerprint = Hex.Decode("C959BDBAFA32A2F89A153B678CFDE12197965A9A");
+            IsEquals((ulong)pubKey.KeyId, 0x8CFDE12197965A9A);
+            IsTrue("wrong fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
+        }
+
+        [Test]
+        public void Version4Ed25519LegacySignatureSampleTest()
+        {
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-sig
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v4Ed25519LegacyPubkeySample);
+            PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+            PgpObjectFactory factory = new PgpObjectFactory(v4Ed25519LegacySignatureSample);
+            PgpSignatureList sigList = (PgpSignatureList)factory.NextPgpObject();
+            PgpSignature signature = sigList[0];
+
+            IsEquals(signature.KeyId, pubKey.KeyId);
+            IsEquals(signature.KeyAlgorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+            IsEquals(signature.HashAlgorithm, HashAlgorithmTag.Sha256);
+            IsEquals(signature.CreationTime.ToString("yyyyMMddHHmmss"), "20150916122453");
+
+            byte[] original = Encoding.UTF8.GetBytes("OpenPGP");
+            signature.InitVerify(pubKey);
+            signature.Update(original);
+            
+            IsTrue("Failed generated signature check against original data", signature.Verify());
+        }
+
+        [Test]
+        public void Version6CertificateParsingTest()
+        {
+            /*
+             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+             * A Transferable Public Key consisting of:
+             *     A v6 Ed25519 Public-Key packet
+             *     A v6 direct key self-signature
+             *     A v6 X25519 Public-Subkey packet
+             *     A v6 subkey binding signature
+             */
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
+            PgpPublicKey[] publicKeys = pubRing.GetPublicKeys().ToArray();
+            IsEquals("wrong number of public keys", publicKeys.Length, 2);
+
+            // master key
+            PgpPublicKey masterKey = publicKeys[0];
+            FailIf("wrong detection of master key", !masterKey.IsMasterKey);
+            IsEquals(masterKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsEquals(masterKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+            byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
+            IsEquals((ulong)masterKey.KeyId, 0xCB186C4F0609A697);
+            IsTrue("wrong master key fingerprint", AreEqual(masterKey.GetFingerprint(), expectedFingerprint));
+
+            // TODO Verify self signatures
+
+            // subkey
+            PgpPublicKey subKey = publicKeys[1];
+            FailIf("wrong detection of encryption subkey", !subKey.IsEncryptionKey);
+            IsEquals(subKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+            expectedFingerprint = Hex.Decode("12C83F1E706F6308FE151A417743A1F033790E93E9978488D1DB378DA9930885");
+            IsEquals(subKey.KeyId, 0x12C83F1E706F6308);
+            IsTrue("wrong sub key fingerprint", AreEqual(subKey.GetFingerprint(), expectedFingerprint));
+
+            // TODO Verify subkey binding signature
+        }
+
+        [Test]
+        public void Version6UnlockedSecretKeyParsingTest()
+        {
+            /*
+             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
+             * A Transferable Secret Key consisting of:
+             *     A v6 Ed25519 Secret-Key packet
+             *     A v6 direct key self-signature
+             *     A v6 X25519 Secret-Subkey packet
+             *     A v6 subkey binding signature
+             */
+
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6UnlockedSecretKey);
+            PgpSecretKey[] secretKeys = secretKeyRing.GetSecretKeys().ToArray();
+            IsEquals("wrong number of secret keys", secretKeys.Length, 2);
+
+            // signing key
+            PgpSecretKey signingKey = secretKeys[0];
+            IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
+
+            AsymmetricCipherKeyPair signingKeyPair = GetKeyPair(signingKey);
+            IsTrue("signature test failed", SignThenVerifyEd25519Test(signingKeyPair));
+
+            // encryption key
+            PgpSecretKey encryptionKey = secretKeys[1];
+            IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+            IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
+
+            AsymmetricCipherKeyPair alice = GetKeyPair(encryptionKey);
+            IAsymmetricCipherKeyPairGenerator kpGen = new X25519KeyPairGenerator();
+            kpGen.Init(new X25519KeyGenerationParameters(new SecureRandom()));
+            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
+
+            IsTrue("X25519 agreement failed", EncryptThenDecryptX25519Test(alice, bob));
+        }
+
+        [Test]
+        public void Version6SampleCleartextSignedMessageVerifySignatureTest()
+        {
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-cleartext-signed-mes
+
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
+            PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+            PgpObjectFactory factory = new PgpObjectFactory(v6SampleCleartextSignedMessageSignature);
+            PgpSignatureList sigList = (PgpSignatureList)factory.NextPgpObject();
+            PgpSignature signature = sigList[0];
+
+            byte[] data = Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage);
+            signature.InitVerify(pubKey);
+            signature.Update(data);
+
+            IsTrue("Failed generated signature check against original data", signature.Verify());
+        }
+
+        private static AsymmetricCipherKeyPair GetKeyPair(PgpSecretKey secretKey, string password = "")
+        {
+            return new AsymmetricCipherKeyPair(
+                secretKey.PublicKey.GetKey(),
+                secretKey.ExtractPrivateKey(password.ToCharArray()).Key);
+        }
+
+        private static bool SignThenVerifyEd25519Test(AsymmetricCipherKeyPair signingKeyPair)
+        {
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+
+            ISigner signer = new Ed25519Signer();
+            signer.Init(true, signingKeyPair.Private);
+            signer.BlockUpdate(data, 0, data.Length);
+            byte[] signature = signer.GenerateSignature();
+
+            signer.Init(false, signingKeyPair.Public);
+            signer.BlockUpdate(data, 0, data.Length);
+            return signer.VerifySignature(signature);
+        }
+
+        private static bool EncryptThenDecryptX25519Test(AsymmetricCipherKeyPair alice, AsymmetricCipherKeyPair bob)
+        {
+            X25519Agreement agreeA = new X25519Agreement();
+            agreeA.Init(alice.Private);
+            byte[] secretA = new byte[agreeA.AgreementSize];
+            agreeA.CalculateAgreement(bob.Public, secretA, 0);
+
+            X25519Agreement agreeB = new X25519Agreement();
+            agreeB.Init(bob.Private);
+            byte[] secretB = new byte[agreeB.AgreementSize];
+            agreeB.CalculateAgreement(alice.Public, secretB, 0);
+
+            return Arrays.AreEqual(secretA, secretB);
+        }
+
+        public override void PerformTest()
+        {
+            Version4Ed25519LegacyPubkeySampleTest();
+            Version4Ed25519LegacySignatureSampleTest();
+            Version6CertificateParsingTest();
+            Version6UnlockedSecretKeyParsingTest();
+            Version6SampleCleartextSignedMessageVerifySignatureTest();
+        }
+    }
+}
\ No newline at end of file

From 182c2aef4ee320636dd4a9a38593f5175fdcf989 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Wed, 14 Feb 2024 20:47:46 +0100
Subject: [PATCH 05/37] basic creation of v6 public keys

---
 crypto/src/bcpg/PublicKeyPacket.cs            | 26 ++++++-
 crypto/src/bcpg/PublicSubkeyPacket.cs         | 14 +++-
 crypto/src/openpgp/PgpPublicKey.cs            | 74 ++++++++++++++-----
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 45 +++++++++--
 4 files changed, 128 insertions(+), 31 deletions(-)

diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index 26cc069d96..6449c03f4b 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -15,6 +15,8 @@ public class PublicKeyPacket
         public const int Version5 = 5;
         public const int Version6 = 6;
 
+        public const int DefaultVersion = Version4;
+
         private readonly int version;
         private readonly long time;
         private readonly int validDays;
@@ -84,16 +86,32 @@ internal PublicKeyPacket(
             }
         }
 
+
+        /// <summary>Construct a public key packet.</summary>
+        public PublicKeyPacket(
+            int version,
+            PublicKeyAlgorithmTag algorithm,
+            DateTime time,
+            IBcpgKey key)
+        {
+            this.version = version;
+            this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
+            this.algorithm = algorithm;
+            this.key = key;
+
+            if (version == Version5 || version == Version6)
+            {
+                v6KeyLen = ((BcpgObject)key).GetEncoded().Length;
+            }
+        }
+
         /// <summary>Construct a version 4 public key packet.</summary>
         public PublicKeyPacket(
             PublicKeyAlgorithmTag	algorithm,
             DateTime				time,
             IBcpgKey				key)
+            :this(DefaultVersion, algorithm, time, key)
         {
-            this.version = Version4;
-            this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
-            this.algorithm = algorithm;
-            this.key = key;
         }
 
         public virtual int Version
diff --git a/crypto/src/bcpg/PublicSubkeyPacket.cs b/crypto/src/bcpg/PublicSubkeyPacket.cs
index 0e1065b720..b9c7d73f54 100644
--- a/crypto/src/bcpg/PublicSubkeyPacket.cs
+++ b/crypto/src/bcpg/PublicSubkeyPacket.cs
@@ -13,12 +13,22 @@ internal PublicSubkeyPacket(
         {
         }
 
-		/// <summary>Construct a version 4 public subkey packet.</summary>
+        /// <summary>Construct a public subkey packet.</summary>
+        public PublicSubkeyPacket(
+            int version,
+            PublicKeyAlgorithmTag algorithm,
+            DateTime time,
+            IBcpgKey key)
+            : base(version, algorithm, time, key)
+        {
+        }
+
+        /// <summary>Construct a version 4 public subkey packet.</summary>
         public PublicSubkeyPacket(
             PublicKeyAlgorithmTag	algorithm,
             DateTime				time,
             IBcpgKey				key)
-            : base(algorithm, time, key)
+            : base(DefaultVersion, algorithm, time, key)
         {
         }
 
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 06f9e22776..a2689d4e5f 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -196,6 +196,12 @@ private void Init()
             }
         }
 
+
+        public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
+            :this(PublicKeyPacket.DefaultVersion, algorithm, pubKey, time)
+        {
+        }
+
         /// <summary>
         /// Create a PgpPublicKey from the passed in lightweight one.
         /// </summary>
@@ -208,7 +214,7 @@ private void Init()
         /// <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)
+        public PgpPublicKey(int version, PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
         {
             if (pubKey.IsPrivate)
                 throw new ArgumentException("Expected a public key", nameof(pubKey));
@@ -248,44 +254,72 @@ public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubK
             }
             else if (pubKey is Ed25519PublicKeyParameters ed25519PubKey)
             {
-                byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize];
-                pointEnc[0] = 0x40;
-                ed25519PubKey.Encode(pointEnc, 1);
-                bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc));
+                if (algorithm == PublicKeyAlgorithmTag.Ed25519)
+                {
+                    bcpgKey = new Ed25519PublicBcpgKey(ed25519PubKey.GetEncoded());
+                }
+                else
+                {
+                    byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize];
+                    pointEnc[0] = 0x40;
+                    ed25519PubKey.Encode(pointEnc, 1);
+                    bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc));
+                }
             }
             else if (pubKey is Ed448PublicKeyParameters ed448PubKey)
             {
-                byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize];
-                ed448PubKey.Encode(pointEnc, 0);
-                bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc));
+                if (algorithm == PublicKeyAlgorithmTag.Ed448)
+                {
+                    bcpgKey = new Ed448PublicBcpgKey(ed448PubKey.GetEncoded());
+                }
+                else
+                {
+                    byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize];
+                    ed448PubKey.Encode(pointEnc, 0);
+                    bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc));
+                }
             }
             else if (pubKey is X25519PublicKeyParameters x25519PubKey)
             {
-                byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize];
-                pointEnc[0] = 0x40;
-                x25519PubKey.Encode(pointEnc, 1);
+                if (algorithm == PublicKeyAlgorithmTag.X25519)
+                {
+                    bcpgKey = new X25519PublicBcpgKey(x25519PubKey.GetEncoded());
+                }
+                else
+                {
+                    byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize];
+                    pointEnc[0] = 0x40;
+                    x25519PubKey.Encode(pointEnc, 1);
 
-                PgpKdfParameters kdfParams = DefaultKdfParameters;
+                    PgpKdfParameters kdfParams = DefaultKdfParameters;
 
-                bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc),
-                    kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+                    bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc),
+                        kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+                }
             }
             else if (pubKey is X448PublicKeyParameters x448PubKey)
             {
-                byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize];
-                x448PubKey.Encode(pointEnc, 0);
+                if (algorithm == PublicKeyAlgorithmTag.X448)
+                {
+                    bcpgKey = new X448PublicBcpgKey(x448PubKey.GetEncoded());
+                }
+                else
+                {
+                    byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize];
+                    x448PubKey.Encode(pointEnc, 0);
 
-                PgpKdfParameters kdfParams = DefaultKdfParameters;
+                    PgpKdfParameters kdfParams = DefaultKdfParameters;
 
-                bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc),
-                    kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+                    bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc),
+                        kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+                }
             }
             else
             {
                 throw new PgpException("unknown key class");
             }
 
-            this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
+            this.publicPk = new PublicKeyPacket(version, algorithm, time, bcpgKey);
             this.ids = new List<IUserDataPacket>();
             this.idSigs = new List<IList<PgpSignature>>();
 
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index f6029f1d4f..2748d9233d 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -169,6 +169,32 @@ public void Version6CertificateParsingTest()
             // TODO Verify subkey binding signature
         }
 
+        [Test]
+        public void Version6PublicKeyCreationTest()
+        {
+            /* 
+             * Create a v6 Ed25519 pubkey with the same key material and creation datetime as the test vector
+             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+             * the check the fingerprint and verify a signature
+            */
+            byte[] keyMaterial = Hex.Decode("f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3");
+            var key = new Ed25519PublicKeyParameters(keyMaterial);
+            var pubKey = new PgpPublicKey(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed25519, key, DateTime.Parse("2022-11-30 16:08:03Z"));
+
+            IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+            byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
+            IsEquals((ulong)pubKey.KeyId, 0xCB186C4F0609A697);
+            IsTrue("wrong master key fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
+
+            bool signatureOk = VerifySignature(
+                v6SampleCleartextSignedMessageSignature,
+                Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
+                pubKey);
+
+            IsTrue("Failed generated signature check against original data", signatureOk);
+        }
+
         [Test]
         public void Version6UnlockedSecretKeyParsingTest()
         {
@@ -214,15 +240,23 @@ public void Version6SampleCleartextSignedMessageVerifySignatureTest()
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
-            PgpObjectFactory factory = new PgpObjectFactory(v6SampleCleartextSignedMessageSignature);
+            bool signatureOk = VerifySignature(
+                v6SampleCleartextSignedMessageSignature,
+                Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
+                pubKey);
+
+            IsTrue("Failed generated signature check against original data", signatureOk);
+        }
+
+        private static bool VerifySignature(byte[] sigPacket, byte[] data, PgpPublicKey signer)
+        {
+            PgpObjectFactory factory = new PgpObjectFactory(sigPacket);
             PgpSignatureList sigList = (PgpSignatureList)factory.NextPgpObject();
             PgpSignature signature = sigList[0];
 
-            byte[] data = Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage);
-            signature.InitVerify(pubKey);
+            signature.InitVerify(signer);
             signature.Update(data);
-
-            IsTrue("Failed generated signature check against original data", signature.Verify());
+            return signature.Verify();
         }
 
         private static AsymmetricCipherKeyPair GetKeyPair(PgpSecretKey secretKey, string password = "")
@@ -266,6 +300,7 @@ public override void PerformTest()
             Version4Ed25519LegacyPubkeySampleTest();
             Version4Ed25519LegacySignatureSampleTest();
             Version6CertificateParsingTest();
+            Version6PublicKeyCreationTest();
             Version6UnlockedSecretKeyParsingTest();
             Version6SampleCleartextSignedMessageVerifySignatureTest();
         }

From 31f2b9841e2603b658d9fc01532bf648556f7788 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 15 Feb 2024 08:47:22 +0100
Subject: [PATCH 06/37] check correct salt size in v6 signature packets

---
 crypto/src/bcpg/SignaturePacket.cs | 12 +++++++++++-
 crypto/src/openpgp/PgpUtilities.cs | 22 ++++++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 79dc8cebe7..4fcd27be19 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-
+using Org.BouncyCastle.Bcpg.OpenPgp;
 using Org.BouncyCastle.Bcpg.Sig;
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
@@ -162,6 +162,16 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
             if (version == Version6)
             {
                 int saltSize = bcpgIn.ReadByte();
+
+                if (saltSize != PgpUtilities.GetSaltSize(hashAlgorithm))
+                {
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-4-and-6-signature-p
+                    // The salt size MUST match the value defined for the hash algorithm as specified in Table 23
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#hash-algorithms-registry
+
+                    throw new IOException($"invalid salt size for v6 signature: expected {PgpUtilities.GetSaltSize(hashAlgorithm)} got {saltSize}");
+                }
+
                 salt = new byte[saltSize];
                 bcpgIn.ReadFully(salt);
             }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 150b2fe2a6..5560b00233 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -115,6 +115,28 @@ public static string GetDigestName(
 			}
         }
 
+        /// <summary>
+        /// Returns the V6 signature salt size for a hash algorithm.
+        /// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-hash-algorithms
+        /// </summary>
+        public static int GetSaltSize(HashAlgorithmTag hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+                case HashAlgorithmTag.Sha256:
+                case HashAlgorithmTag.Sha224:
+                case HashAlgorithmTag.Sha3_256:
+                    return 16;
+                case HashAlgorithmTag.Sha384:
+                    return 24;
+                case HashAlgorithmTag.Sha512:
+                case HashAlgorithmTag.Sha3_512:
+                    return 32;
+                default:
+                    return 0;
+            }
+        }
+
         public static int GetDigestIDForName(string name)
         {
             if (NameToHashID.TryGetValue(name, out var hashAlgorithmTag))

From 3edb70201e578da7d3a2ab634f8243f519e0a7b7 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 15 Feb 2024 17:40:50 +0100
Subject: [PATCH 07/37] refactored SecretKeyPacket (based on java version)

---
 crypto/src/bcpg/AeadUtils.cs                  |  75 +++++++
 crypto/src/bcpg/S2k.cs                        |   1 +
 crypto/src/bcpg/SecretKeyPacket.cs            | 205 +++++++++++++++---
 crypto/src/bcpg/SecretSubkeyPacket.cs         |  14 +-
 crypto/src/openpgp/PgpUtilities.cs            |   7 +-
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  |  25 +++
 6 files changed, 289 insertions(+), 38 deletions(-)
 create mode 100644 crypto/src/bcpg/AeadUtils.cs

diff --git a/crypto/src/bcpg/AeadUtils.cs b/crypto/src/bcpg/AeadUtils.cs
new file mode 100644
index 0000000000..922a4860ca
--- /dev/null
+++ b/crypto/src/bcpg/AeadUtils.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class AeadUtils
+    {
+        /**
+         * Return the length of the IV used by the given AEAD algorithm in octets.
+         * 
+         * @param aeadAlgorithmTag AEAD algorithm identifier
+         * @return length of the IV
+        */
+        public static int GetIVLength(AeadAlgorithmTag aeadAlgorithmTag)
+        {
+            switch (aeadAlgorithmTag)
+            {
+                case AeadAlgorithmTag.Eax:
+                    return 16;
+                case AeadAlgorithmTag.Ocb:
+                    return 15;
+                case AeadAlgorithmTag.Gcm:
+                    return 12;
+                default:
+                    throw new ArgumentException($"Invalid AEAD algorithm tag: {aeadAlgorithmTag}");
+            }
+        }
+
+        /**
+         * Return the length of the authentication tag used by the given AEAD algorithm in octets.
+         *
+         * @param aeadAlgorithmTag AEAD algorithm identifier
+         * @return length of the auth tag
+         */
+        public static int GetAuthTagLength(AeadAlgorithmTag aeadAlgorithmTag)
+        {
+            switch (aeadAlgorithmTag)
+            {
+                case AeadAlgorithmTag.Eax:
+                case AeadAlgorithmTag.Ocb:
+                case AeadAlgorithmTag.Gcm:
+                    return 16;
+                default:
+                    throw new ArgumentException($"Invalid AEAD algorithm tag: {aeadAlgorithmTag}");
+            }
+        }
+
+        /**
+         * Split a given byte array containing <pre>m</pre> bytes of key and <pre>n-8</pre> bytes of IV into
+         * two separate byte arrays.
+         * <pre>m</pre> is the key length of the cipher algorithm, while <pre>n</pre> is the IV length of the AEAD algorithm.
+         * Note, that the IV is filled with <pre>n-8</pre> bytes only, the remainder is left as 0s.
+         * Return an array of both arrays with the key and index 0 and the IV at index 1.
+         *
+         * @param messageKeyAndIv <pre>m+n-8</pre> bytes of concatenated message key and IV
+         * @param cipherAlgo      symmetric cipher algorithm
+         * @param aeadAlgo        AEAD algorithm
+         * @return array of arrays containing message key and IV
+         */
+        public static byte[][] SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgorithmTag cipherAlgo, AeadAlgorithmTag aeadAlgo)
+        {
+            int keyLen = PgpUtilities.GetKeySizeInOctets(cipherAlgo);
+            int ivLen = GetIVLength(aeadAlgo);
+            byte[] messageKey = new byte[keyLen];
+            byte[] iv = new byte[ivLen];
+
+            Array.Copy(messageKeyAndIv, messageKey, messageKey.Length);
+            Array.Copy(messageKeyAndIv, messageKey.Length, iv, 0, ivLen-8);
+
+            return new byte[][] { messageKey, iv };
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/S2k.cs b/crypto/src/bcpg/S2k.cs
index 8446fd1ec9..d5f31143d8 100644
--- a/crypto/src/bcpg/S2k.cs
+++ b/crypto/src/bcpg/S2k.cs
@@ -15,6 +15,7 @@ public class S2k
         public const int Simple = 0;
         public const int Salted = 1;
         public const int SaltedAndIterated = 3;
+        public const int Argon2 = 4;
         public const int GnuDummyS2K = 101;
         public const int GnuProtectionModeNoPrivateKey = 1;
         public const int GnuProtectionModeDivertToCard = 2;
diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index c5b7e81266..7e825c9456 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using Org.BouncyCastle.Pqc.Crypto.SphincsPlus;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg
@@ -8,9 +9,54 @@ namespace Org.BouncyCastle.Bcpg
     public class SecretKeyPacket
         : ContainedPacket //, PublicKeyAlgorithmTag
     {
-		public const int UsageNone = 0x00;
-		public const int UsageChecksum = 0xff;
-		public const int UsageSha1 = 0xfe;
+
+        /**
+         * Unprotected.
+         */
+        public const int UsageNone = 0x00;
+
+        /**
+         * Malleable CFB.
+         * Malleable-CFB-encrypted keys are vulnerable to corruption attacks
+         * that can cause leakage of secret data when the secret key is used.
+         *
+         * @see <a href="https://eprint.iacr.org/2002/076">
+         * Kl�ma, V. and T. Rosa,
+         * "Attack on Private Signature Keys of the OpenPGP Format,
+         * PGP(TM) Programs and Other Applications Compatible with OpenPGP"</a>
+         * @see <a href="https://www.kopenpgp.com/">
+         * Bruseghini, L., Paterson, K. G., and D. Huigens,
+         * "Victory by KO: Attacking OpenPGP Using Key Overwriting"</a>
+         * @deprecated Use of MalleableCFB is deprecated.
+         * For v4 keys, use {@link #USAGE_SHA1} instead.
+         * For v6 keys use {@link #USAGE_AEAD} instead.
+         */
+        public const int UsageChecksum = 0xff;
+
+        /**
+         * CFB.
+         * CFB-encrypted keys are vulnerable to corruption attacks that can
+         * cause leakage of secret data when the secret key is use.
+         *
+         * @see <a href="https://eprint.iacr.org/2002/076">
+         * Kl�ma, V. and T. Rosa,
+         * "Attack on Private Signature Keys of the OpenPGP Format,
+         * PGP(TM) Programs and Other Applications Compatible with OpenPGP"</a>
+         * @see <a href="https://www.kopenpgp.com/">
+         * Bruseghini, L., Paterson, K. G., and D. Huigens,
+         * "Victory by KO: Attacking OpenPGP Using Key Overwriting"</a>
+         */
+        public const int UsageSha1 = 0xfe;
+
+        /**
+         * AEAD.
+         * This usage protects against above-mentioned attacks.
+         * Passphrase-protected secret key material in a v6 Secret Key or
+         * v6 Secret Subkey packet SHOULD be protected with AEAD encryption
+         * unless it will be transferred to an implementation that is known
+         * to not support AEAD.
+         * Users should migrate to AEAD with all due speed.
+         */
         public const int UsageAead = 0xfd;
 
 
@@ -20,7 +66,10 @@ public class SecretKeyPacket
 		private readonly SymmetricKeyAlgorithmTag encAlgorithm;
         private readonly S2k s2k;
         private readonly byte[] iv;
-        private readonly AeadAlgorithmTag aeadAlgo;
+        private readonly AeadAlgorithmTag aeadAlgorithm;
+
+        private bool HasS2KSpecifier
+            => (s2kUsage == UsageChecksum || s2kUsage == UsageSha1 || s2kUsage == UsageAead);
 
         internal SecretKeyPacket(
             BcpgInputStream bcpgIn)
@@ -34,38 +83,53 @@ internal SecretKeyPacket(
 				pubKeyPacket = new PublicKeyPacket(bcpgIn);
 			}
 
-			s2kUsage = bcpgIn.ReadByte();
+            int version = pubKeyPacket.Version;
+            s2kUsage = bcpgIn.ReadByte();
 
-            if (s2kUsage != UsageNone && pubKeyPacket.Version == PublicKeyPacket.Version6)
+            if (version == PublicKeyPacket.Version6 && s2kUsage != UsageNone)
             {
                 // TODO: Use length to parse unknown parameters
                 int conditionalParameterLength = bcpgIn.ReadByte();
             }
 
-            if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1 || s2kUsage == UsageAead)
+            if (HasS2KSpecifier)
             {
-                encAlgorithm = (SymmetricKeyAlgorithmTag) bcpgIn.ReadByte();
-                if (s2kUsage == UsageAead)
-                {
-                    aeadAlgo = (AeadAlgorithmTag)bcpgIn.ReadByte();
-                }
-                if (pubKeyPacket.Version == 6 && (s2kUsage == UsageSha1 || s2kUsage == UsageAead))
+                encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
+            }
+            else
+            {
+                encAlgorithm = (SymmetricKeyAlgorithmTag)s2kUsage;
+            }
+
+            if (s2kUsage == UsageAead)
+            {
+                aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.ReadByte();
+            }
+
+            if (HasS2KSpecifier)
+            {
+                if (version == PublicKeyPacket.Version6)
                 {
                     // TODO: Use length to parse unknown S2Ks
                     int s2kLen = bcpgIn.ReadByte();
                 }
                 s2k = new S2k(bcpgIn);
             }
-            else
+            if (s2kUsage == UsageAead)
             {
-                encAlgorithm = (SymmetricKeyAlgorithmTag) s2kUsage;
+                iv = new byte[AeadUtils.GetIVLength(aeadAlgorithm)];
+                bcpgIn.ReadFully(iv);
             }
 
-            if (!(s2k != null && s2k.Type == S2k.GnuDummyS2K && s2k.ProtectionMode == 0x01))
+            bool isGNUDummyNoPrivateKey = s2k != null
+                    && s2k.Type == S2k.GnuDummyS2K
+                    && s2k.ProtectionMode == S2k.GnuProtectionModeNoPrivateKey;
+
+            if (!(isGNUDummyNoPrivateKey))
             {
-				if (s2kUsage != 0)
-				{
-                    if (((int) encAlgorithm) < 7)
+                if (s2kUsage != 0 && iv == null)
+                {
+                    if ((int)encAlgorithm < 7)
                     {
                         iv = new byte[8];
                     }
@@ -77,7 +141,7 @@ internal SecretKeyPacket(
                 }
             }
 
-			secKeyData = bcpgIn.ReadAll();
+            secKeyData = bcpgIn.ReadAll();
         }
 
 		public SecretKeyPacket(
@@ -120,12 +184,49 @@ public SecretKeyPacket(
 			this.secKeyData = secKeyData;
 		}
 
-		public SymmetricKeyAlgorithmTag EncAlgorithm
+
+        public SecretKeyPacket(
+            PublicKeyPacket pubKeyPacket,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            int s2kUsage,
+            S2k s2k,
+            byte[] iv,
+            byte[] secKeyData)
+        {
+            this.pubKeyPacket = pubKeyPacket;
+            this.encAlgorithm = encAlgorithm;
+            this.aeadAlgorithm = aeadAlgorithm;
+            this.s2kUsage = s2kUsage;
+            this.s2k = s2k;
+            this.iv = Arrays.Clone(iv);
+            this.secKeyData = secKeyData;
+
+            if (s2k != null && s2k.Type == S2k.Argon2 && s2kUsage != UsageAead)
+            {
+                throw new ArgumentException("Argon2 is only used with AEAD (S2K usage octet 253)");
+            }
+
+            if (pubKeyPacket.Version == PublicKeyPacket.Version6)
+            {
+                if (s2kUsage == UsageChecksum)
+                {
+                    throw new ArgumentException("Version 6 keys MUST NOT use S2K usage UsageChecksum");
+                }
+            }
+        }
+
+        public SymmetricKeyAlgorithmTag EncAlgorithm
         {
 			get { return encAlgorithm; }
         }
 
-		public int S2kUsage
+        public AeadAlgorithmTag GetAeadAlgorithm()
+        {
+            return aeadAlgorithm;
+        }
+
+        public int S2kUsage
 		{
 			get { return s2kUsage; }
 		}
@@ -150,31 +251,63 @@ public byte[] GetSecretKeyData()
             return secKeyData;
         }
 
-		public byte[] GetEncodedContents()
+
+
+        private byte[] EncodeConditionalParameters()
         {
-            MemoryStream bOut = new MemoryStream();
-            using (var pOut = new BcpgOutputStream(bOut))
+            using (MemoryStream conditionalParameters = new MemoryStream())
             {
-                pOut.Write(pubKeyPacket.GetEncodedContents());
-                pOut.WriteByte((byte)s2kUsage);
-
-                if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1)
+                if (HasS2KSpecifier)
                 {
-                    pOut.WriteByte((byte)encAlgorithm);
-                    pOut.WriteObject(s2k);
+                    conditionalParameters.WriteByte((byte)encAlgorithm);
+                    if (s2kUsage == UsageAead)
+                    {
+                        conditionalParameters.WriteByte((byte)aeadAlgorithm);
+                    }
+                    byte[] encodedS2K = s2k.GetEncoded();
+                    if (pubKeyPacket.Version == PublicKeyPacket.Version6)
+                    {
+                        conditionalParameters.WriteByte((byte)encodedS2K.Length);
+                    }
+                    conditionalParameters.Write(encodedS2K, 0, encodedS2K.Length);
                 }
-
                 if (iv != null)
                 {
-                    pOut.Write(iv);
+                    // since USAGE_AEAD and other types that use an IV are mutually exclusive,
+                    // we use the IV field for both v4 IVs and v6 AEAD nonces
+                    conditionalParameters.Write(iv, 0, iv.Length);
                 }
 
-                if (secKeyData != null && secKeyData.Length > 0)
+                return conditionalParameters.ToArray();
+            }
+        }
+
+		public byte[] GetEncodedContents()
+        {
+            using (MemoryStream bOut = new MemoryStream())
+            {
+                using (var pOut = new BcpgOutputStream(bOut))
                 {
-                    pOut.Write(secKeyData);
+                    pOut.Write(pubKeyPacket.GetEncodedContents());
+                    pOut.WriteByte((byte)s2kUsage);
+
+                    // conditional parameters
+                    byte[] conditionalParameters = EncodeConditionalParameters();
+                    if (pubKeyPacket.Version == PublicKeyPacket.Version6 && s2kUsage != UsageNone)
+                    {
+                        pOut.WriteByte((byte)conditionalParameters.Length);
+                    }
+                    pOut.Write(conditionalParameters);
+
+                    // encrypted secret key
+                    if (secKeyData != null && secKeyData.Length > 0)
+                    {
+                        pOut.Write(secKeyData);
+                    }
+                    pOut.Close();
                 }
+                return bOut.ToArray();
             }
-            return bOut.ToArray();
         }
 
         public override void Encode(BcpgOutputStream bcpgOut)
diff --git a/crypto/src/bcpg/SecretSubkeyPacket.cs b/crypto/src/bcpg/SecretSubkeyPacket.cs
index 2d405aec27..fd05f81b8b 100644
--- a/crypto/src/bcpg/SecretSubkeyPacket.cs
+++ b/crypto/src/bcpg/SecretSubkeyPacket.cs
@@ -34,7 +34,19 @@ public SecretSubkeyPacket(
 		{
 		}
 
-		public override void Encode(BcpgOutputStream bcpgOut)
+        public SecretSubkeyPacket(
+            PublicKeyPacket pubKeyPacket,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            int s2kUsage,
+            S2k s2k,
+            byte[] iv,
+            byte[] secKeyData)
+            :base(pubKeyPacket, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, secKeyData)
+        {
+        }
+
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
             bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents());
         }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 5560b00233..08418d126a 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -272,7 +272,12 @@ public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
 			return keySize;
         }
 
-		public static KeyParameter MakeKey(
+        public static int GetKeySizeInOctets(SymmetricKeyAlgorithmTag algorithm)
+        {
+            return (GetKeySize(algorithm) + 7) / 8;
+        }
+
+        public static KeyParameter MakeKey(
 			SymmetricKeyAlgorithmTag	algorithm,
 			byte[]						keyBytes)
 		{
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 2748d9233d..5e33823467 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -9,6 +9,7 @@
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.Test;
 using System;
+using System.IO;
 using System.Linq;
 using System.Text;
 
@@ -167,6 +168,18 @@ public void Version6CertificateParsingTest()
             IsTrue("wrong sub key fingerprint", AreEqual(subKey.GetFingerprint(), expectedFingerprint));
 
             // TODO Verify subkey binding signature
+
+            // Encode test
+            using (MemoryStream ms = new MemoryStream())
+            {
+                using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
+                {
+                    pubRing.Encode(bs);
+                }
+
+                byte[] encoded = ms.ToArray();
+                IsTrue(AreEqual(encoded, v6Certificate));
+            }
         }
 
         [Test]
@@ -230,6 +243,18 @@ public void Version6UnlockedSecretKeyParsingTest()
             AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
 
             IsTrue("X25519 agreement failed", EncryptThenDecryptX25519Test(alice, bob));
+
+            // Encode test
+            using (MemoryStream ms = new MemoryStream())
+            {
+                using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
+                {
+                    secretKeyRing.Encode(bs);
+                }
+
+                byte[] encoded = ms.ToArray();
+                IsTrue(AreEqual(encoded, v6UnlockedSecretKey));
+            }
         }
 
         [Test]

From ad9b9b2eaf6308bfe666ef097d8abf7b0fc6de2e Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 15 Feb 2024 20:17:02 +0100
Subject: [PATCH 08/37] refactored S2k class (based on java version)

---
 crypto/src/bcpg/S2k.cs                        | 178 +++++++++++++-----
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  |  42 +++++
 2 files changed, 168 insertions(+), 52 deletions(-)

diff --git a/crypto/src/bcpg/S2k.cs b/crypto/src/bcpg/S2k.cs
index d5f31143d8..3dafe87592 100644
--- a/crypto/src/bcpg/S2k.cs
+++ b/crypto/src/bcpg/S2k.cs
@@ -1,6 +1,5 @@
 using System;
 using System.IO;
-
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
@@ -20,71 +19,110 @@ public class S2k
         public const int GnuProtectionModeNoPrivateKey = 1;
         public const int GnuProtectionModeDivertToCard = 2;
 
-        internal int type;
-        internal HashAlgorithmTag algorithm;
-        internal byte[] iv;
-        internal int itCount = -1;
-        internal int protectionMode = -1;
+        private readonly int type;
+        private readonly HashAlgorithmTag algorithm;
+        private readonly byte[] iv;
+        
+        private readonly int itCount = -1;
+        private readonly int protectionMode = -1;
+
+        // params for Argon2
+        private readonly int passes;
+        private readonly int parallelism;
+        private readonly int memorySizeExponent;
 
         internal S2k(
             Stream inStr)
         {
 			type = inStr.ReadByte();
-            algorithm = (HashAlgorithmTag) inStr.ReadByte();
 
-            //
-            // if this happens we have a dummy-S2k packet.
-            //
-            if (type != GnuDummyS2K)
-            {
-                if (type != 0)
-                {
-					iv = new byte[8];
-					if (Streams.ReadFully(inStr, iv, 0, iv.Length) < iv.Length)
-						throw new EndOfStreamException();
-
-					if (type == 3)
-					{
-						itCount = inStr.ReadByte();
-					}
-				}
-            }
-            else
+            switch (type)
             {
-                inStr.ReadByte(); // G
-                inStr.ReadByte(); // N
-                inStr.ReadByte(); // U
-                protectionMode = inStr.ReadByte(); // protection mode
+                case Simple:
+                    algorithm = (HashAlgorithmTag)inStr.ReadByte();
+                    break;
+
+                case Salted:
+                    algorithm = (HashAlgorithmTag)inStr.ReadByte();
+                    iv = new byte[8];
+                    Streams.ReadFully(inStr, iv);
+                    break;
+
+                case SaltedAndIterated:
+                    algorithm = (HashAlgorithmTag)inStr.ReadByte();
+                    iv = new byte[8];
+                    Streams.ReadFully(inStr, iv);
+                    itCount = inStr.ReadByte();
+                    break;
+
+                case Argon2:
+                    iv = new byte[16];
+                    Streams.ReadFully(inStr, iv);
+                    passes = inStr.ReadByte();
+                    parallelism = inStr.ReadByte();
+                    memorySizeExponent = inStr.ReadByte();
+                    break;
+
+                case GnuDummyS2K:
+                    algorithm = (HashAlgorithmTag)inStr.ReadByte();
+                    inStr.ReadByte(); // G
+                    inStr.ReadByte(); // N
+                    inStr.ReadByte(); // U
+                    protectionMode = inStr.ReadByte(); // protection mode
+                    break;
+
+                default:
+                    throw new UnsupportedPacketVersionException($"Invalid S2K type: {type}");
             }
+
         }
 
+        /// <summary>Constructs a specifier for a simple S2K generation</summary>
+        /// <param name="algorithm">the digest algorithm to use.</param>
         public S2k(
             HashAlgorithmTag algorithm)
         {
-            this.type = 0;
+            this.type = Simple;
             this.algorithm = algorithm;
         }
 
+        /// <summary>Constructs a specifier for a salted S2K generation</summary>
+        /// <param name="algorithm">the digest algorithm to use.</param>
+        /// <param name="iv">the salt to apply to input to the key generation</param>
         public S2k(
             HashAlgorithmTag algorithm,
             byte[] iv)
         {
-            this.type = 1;
+            this.type = Salted;
             this.algorithm = algorithm;
-            this.iv = iv;
+            this.iv = Arrays.Clone(iv);
         }
 
+        /// <summary>Constructs a specifier for a salted and iterated S2K generation</summary>
+        /// <param name="algorithm">the digest algorithm to iterate.</param>
+        /// <param name="iv">the salt to apply to input to the key generation</param>
+        /// <param name="itCount">the single byte iteration count specifier</param>
         public S2k(
             HashAlgorithmTag algorithm,
             byte[] iv,
             int itCount)
         {
-            this.type = 3;
+            this.type = SaltedAndIterated;
             this.algorithm = algorithm;
-            this.iv = iv;
+            this.iv = Arrays.Clone(iv);
             this.itCount = itCount;
         }
 
+        /// <summary>Constructs a specifier for an S2K method using Argon2</summary>
+        public S2k(byte[] salt, int passes, int parallelism, int memorySizeExponent)
+        {
+            this.type = Argon2;
+            this.iv = Arrays.Clone(salt);
+            this.passes = passes;
+            this.parallelism = parallelism;
+            this.memorySizeExponent = memorySizeExponent;
+        }
+
         public virtual int Type
         {
 			get { return type; }
@@ -114,30 +152,66 @@ public virtual int ProtectionMode
 			get { return protectionMode; }
         }
 
+        /// <summary>The number of passes - only if Argon2</summary>
+        public int Passes
+        {
+            get { return passes; }
+        }
+
+        /// <summary>The degree of parallelism - only if Argon2</summary>
+        public int Parallelism
+        {
+            get { return parallelism; }
+        }
+
+        /// <summary>The memory size exponent - only if Argon2</summary>
+        public int MemorySizeExponent
+        {
+            get { return memorySizeExponent; }
+        }
+        
         public override void Encode(
             BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WriteByte((byte) type);
-            bcpgOut.WriteByte((byte) algorithm);
-
-            if (type != GnuDummyS2K)
+            switch (type)
             {
-                if (type != 0)
-                {
+                case Simple:
+                    bcpgOut.WriteByte((byte)type);
+                    bcpgOut.WriteByte((byte)algorithm);
+                    break;
+
+                case Salted:
+                    bcpgOut.WriteByte((byte)type);
+                    bcpgOut.WriteByte((byte)algorithm);
                     bcpgOut.Write(iv);
-                }
+                    break;
 
-                if (type == 3)
-                {
-                    bcpgOut.WriteByte((byte) itCount);
-                }
-            }
-            else
-            {
-                bcpgOut.WriteByte((byte) 'G');
-                bcpgOut.WriteByte((byte) 'N');
-                bcpgOut.WriteByte((byte) 'U');
-                bcpgOut.WriteByte((byte) protectionMode);
+                case SaltedAndIterated:
+                    bcpgOut.WriteByte((byte)type);
+                    bcpgOut.WriteByte((byte)algorithm);
+                    bcpgOut.Write(iv);
+                    bcpgOut.WriteByte((byte)itCount);
+                    break;
+
+                case Argon2:
+                    bcpgOut.WriteByte((byte)type);
+                    bcpgOut.Write(iv);
+                    bcpgOut.WriteByte((byte)passes);
+                    bcpgOut.WriteByte((byte)parallelism);
+                    bcpgOut.WriteByte((byte)memorySizeExponent);
+                    break;
+
+                case GnuDummyS2K:
+                    bcpgOut.WriteByte((byte)type);
+                    bcpgOut.WriteByte((byte)algorithm);
+                    bcpgOut.WriteByte((byte)'G');
+                    bcpgOut.WriteByte((byte)'N');
+                    bcpgOut.WriteByte((byte)'U');
+                    bcpgOut.WriteByte((byte)protectionMode);
+                    break;
+
+                default:
+                    throw new InvalidOperationException($"Unknown S2K type {type}");
             }
         }
     }
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 5e33823467..e89e82e362 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -257,6 +257,45 @@ public void Version6UnlockedSecretKeyParsingTest()
             }
         }
 
+        [Test]
+        public void Version6LockedSecretKeyParsingTest()
+        {
+            /*
+             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-locked-v6-secret-key
+             * The same secret key as in Version6UnlockedSecretKeyParsingTest, but the secret key
+             * material is locked with a passphrase using AEAD and Argon2.
+             * 
+             * AEAD/Argon passphrase decryption is not implemented yet, so we just test
+             * parsing and encoding
+             */
+
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6LockedSecretKey);
+            PgpSecretKey[] secretKeys = secretKeyRing.GetSecretKeys().ToArray();
+            IsEquals("wrong number of secret keys", secretKeys.Length, 2);
+
+            // signing key
+            PgpSecretKey signingKey = secretKeys[0];
+            IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
+
+            // encryption key
+            PgpSecretKey encryptionKey = secretKeys[1];
+            IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+            IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
+
+            // Encode test
+            using (MemoryStream ms = new MemoryStream())
+            {
+                using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
+                {
+                    secretKeyRing.Encode(bs);
+                }
+
+                byte[] encoded = ms.ToArray();
+                IsTrue(AreEqual(encoded, v6LockedSecretKey));
+            }
+        }
+
         [Test]
         public void Version6SampleCleartextSignedMessageVerifySignatureTest()
         {
@@ -324,9 +363,12 @@ public override void PerformTest()
         {
             Version4Ed25519LegacyPubkeySampleTest();
             Version4Ed25519LegacySignatureSampleTest();
+            Version4Ed25519LegacyCreateTest();
+
             Version6CertificateParsingTest();
             Version6PublicKeyCreationTest();
             Version6UnlockedSecretKeyParsingTest();
+            Version6LockedSecretKeyParsingTest();
             Version6SampleCleartextSignedMessageVerifySignatureTest();
         }
     }

From 244eaa02aa556e82345b87fca3913fbece601e14 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 22 Feb 2024 17:55:23 +0100
Subject: [PATCH 09/37] Packet criticality

---
 crypto/src/bcpg/AeadEncDataPacket.cs          |  4 +-
 crypto/src/bcpg/BcpgInputStream.cs            | 10 ++--
 crypto/src/bcpg/CompressedDataPacket.cs       |  2 +-
 crypto/src/bcpg/ContainedPacket.cs            |  6 +-
 crypto/src/bcpg/ExperimentalPacket.cs         |  7 +--
 crypto/src/bcpg/InputStreamPacket.cs          | 11 +++-
 crypto/src/bcpg/LiteralDataPacket.cs          |  8 +--
 crypto/src/bcpg/MarkerPacket.cs               |  1 +
 crypto/src/bcpg/ModDetectionCodePacket.cs     |  6 +-
 crypto/src/bcpg/OnePassSignaturePacket.cs     | 16 ++---
 crypto/src/bcpg/Packet.cs                     | 30 +++++++++-
 crypto/src/bcpg/PaddingPacket.cs              |  3 +
 crypto/src/bcpg/PublicKeyEncSessionPacket.cs  | 12 ++--
 crypto/src/bcpg/PublicKeyPacket.cs            | 21 ++++++-
 crypto/src/bcpg/PublicSubkeyPacket.cs         |  4 +-
 crypto/src/bcpg/SecretKeyPacket.cs            | 59 ++++++++++++++++---
 crypto/src/bcpg/SecretSubkeyPacket.cs         |  8 +--
 crypto/src/bcpg/SignaturePacket.cs            |  2 +
 crypto/src/bcpg/SymmetricEncDataPacket.cs     |  2 +-
 .../src/bcpg/SymmetricEncIntegrityPacket.cs   |  2 +-
 .../src/bcpg/SymmetricKeyEncSessionPacket.cs  |  8 ++-
 crypto/src/bcpg/TrustPacket.cs                | 10 ++--
 crypto/src/bcpg/UserAttributePacket.cs        |  2 +
 crypto/src/bcpg/UserIdPacket.cs               |  3 +
 .../src/openpgp/PgpEncryptedDataGenerator.cs  | 15 +++--
 25 files changed, 191 insertions(+), 61 deletions(-)

diff --git a/crypto/src/bcpg/AeadEncDataPacket.cs b/crypto/src/bcpg/AeadEncDataPacket.cs
index 2c7ec97f6f..61368904b8 100644
--- a/crypto/src/bcpg/AeadEncDataPacket.cs
+++ b/crypto/src/bcpg/AeadEncDataPacket.cs
@@ -21,7 +21,7 @@ public class AeadEncDataPacket
         private readonly byte[] m_iv;
 
         public AeadEncDataPacket(BcpgInputStream bcpgIn)
-            : base(bcpgIn)
+            : base(bcpgIn, PacketTag.ReservedAeadEncryptedData)
         {
             m_version = (byte)bcpgIn.ReadByte();
             if (m_version != 1)
@@ -37,7 +37,7 @@ public AeadEncDataPacket(BcpgInputStream bcpgIn)
 
         public AeadEncDataPacket(SymmetricKeyAlgorithmTag algorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize,
             byte[] iv)
-            : base(null)
+            : base(null, PacketTag.ReservedAeadEncryptedData)
         {
             m_version = 1;
             m_algorithm = algorithm;
diff --git a/crypto/src/bcpg/BcpgInputStream.cs b/crypto/src/bcpg/BcpgInputStream.cs
index efd258c9d7..c118a26e1b 100644
--- a/crypto/src/bcpg/BcpgInputStream.cs
+++ b/crypto/src/bcpg/BcpgInputStream.cs
@@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Bcpg
     public class BcpgInputStream
         : BaseInputStream
     {
-        private Stream m_in;
+        private readonly Stream m_in;
         private bool next = false;
         private int nextB;
 
@@ -135,7 +135,7 @@ public Packet ReadPacket()
             }
 
             bool newPacket = (hdr & 0x40) != 0;
-            PacketTag tag = 0;
+            PacketTag tag = PacketTag.Reserved;
             int bodyLen = 0;
             bool partial = false;
 
@@ -206,7 +206,7 @@ public Packet ReadPacket()
             switch (tag)
             {
                 case PacketTag.Reserved:
-                    return new InputStreamPacket(objStream);
+                    return new InputStreamPacket(objStream, PacketTag.Reserved);
                 case PacketTag.PublicKeyEncryptedSession:
                     return new PublicKeyEncSessionPacket(objStream);
                 case PacketTag.Signature:
@@ -241,6 +241,8 @@ public Packet ReadPacket()
                     return new SymmetricEncIntegrityPacket(objStream);
                 case PacketTag.ModificationDetectionCode:
                     return new ModDetectionCodePacket(objStream);
+                case PacketTag.ReservedAeadEncryptedData:
+                    return new AeadEncDataPacket(objStream);
                 case PacketTag.Padding:
                     return new PaddingPacket(objStream);
                 case PacketTag.Experimental1:
@@ -285,7 +287,7 @@ protected override void Dispose(bool disposing)
 		private class PartialInputStream
             : BaseInputStream
         {
-            private BcpgInputStream m_in;
+            private readonly BcpgInputStream m_in;
             private bool partial;
             private int dataLength;
 
diff --git a/crypto/src/bcpg/CompressedDataPacket.cs b/crypto/src/bcpg/CompressedDataPacket.cs
index 2432825eba..dbebed125e 100644
--- a/crypto/src/bcpg/CompressedDataPacket.cs
+++ b/crypto/src/bcpg/CompressedDataPacket.cs
@@ -10,7 +10,7 @@ public class CompressedDataPacket
 
 		internal CompressedDataPacket(
             BcpgInputStream bcpgIn)
-			: base(bcpgIn)
+			: base(bcpgIn, PacketTag.CompressedData)
         {
             this.algorithm = (CompressionAlgorithmTag) bcpgIn.ReadByte();
         }
diff --git a/crypto/src/bcpg/ContainedPacket.cs b/crypto/src/bcpg/ContainedPacket.cs
index ca39fd985c..812e36aa66 100644
--- a/crypto/src/bcpg/ContainedPacket.cs
+++ b/crypto/src/bcpg/ContainedPacket.cs
@@ -1,4 +1,3 @@
-using System;
 using System.IO;
 
 namespace Org.BouncyCastle.Bcpg
@@ -7,6 +6,11 @@ namespace Org.BouncyCastle.Bcpg
     public abstract class ContainedPacket
         : Packet
     {
+        protected ContainedPacket(PacketTag packetTag)
+            : base(packetTag)
+        {
+        }
+
         public byte[] GetEncoded()
         {
             MemoryStream bOut = new MemoryStream();
diff --git a/crypto/src/bcpg/ExperimentalPacket.cs b/crypto/src/bcpg/ExperimentalPacket.cs
index 3d413db099..dea370b599 100644
--- a/crypto/src/bcpg/ExperimentalPacket.cs
+++ b/crypto/src/bcpg/ExperimentalPacket.cs
@@ -7,22 +7,19 @@ namespace Org.BouncyCastle.Bcpg
     public class ExperimentalPacket
         : ContainedPacket
     {
-        private readonly PacketTag m_tag;
         private readonly byte[] m_contents;
 
 		internal ExperimentalPacket(PacketTag tag, BcpgInputStream bcpgIn)
+            :base(tag)
         {
-            m_tag = tag;
 			m_contents = bcpgIn.ReadAll();
         }
 
-		public PacketTag Tag => m_tag;
-
 		public byte[] GetContents() => (byte[])m_contents.Clone();
 
 		public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(m_tag, m_contents);
+            bcpgOut.WritePacket(Tag, m_contents);
         }
     }
 }
diff --git a/crypto/src/bcpg/InputStreamPacket.cs b/crypto/src/bcpg/InputStreamPacket.cs
index d4946ebf26..97d61e5282 100644
--- a/crypto/src/bcpg/InputStreamPacket.cs
+++ b/crypto/src/bcpg/InputStreamPacket.cs
@@ -5,7 +5,16 @@ public class InputStreamPacket
     {
         private readonly BcpgInputStream bcpgIn;
 
-		public InputStreamPacket(BcpgInputStream bcpgIn)
+        // for API backward compatibility
+        // it's unlikely this is being used, but just in case we'll mark
+        // unknown inputs as reserved.
+        public InputStreamPacket(BcpgInputStream bcpgIn)
+            : this(bcpgIn, PacketTag.Reserved)
+        {
+        }
+
+        public InputStreamPacket(BcpgInputStream bcpgIn, PacketTag packetTag)
+            :base(packetTag)
         {
             this.bcpgIn = bcpgIn;
         }
diff --git a/crypto/src/bcpg/LiteralDataPacket.cs b/crypto/src/bcpg/LiteralDataPacket.cs
index b4d28a2019..aac423a3f1 100644
--- a/crypto/src/bcpg/LiteralDataPacket.cs
+++ b/crypto/src/bcpg/LiteralDataPacket.cs
@@ -9,13 +9,13 @@ namespace Org.BouncyCastle.Bcpg
     public class LiteralDataPacket
         : InputStreamPacket
 	{
-		private int		format;
-        private byte[]	fileName;
-        private long	modDate;
+		private readonly int	format;
+        private readonly byte[]	fileName;
+        private readonly long	modDate;
 
 		internal LiteralDataPacket(
             BcpgInputStream bcpgIn)
-			: base(bcpgIn)
+			: base(bcpgIn, PacketTag.LiteralData)
         {
             format = bcpgIn.ReadByte();
             int len = bcpgIn.ReadByte();
diff --git a/crypto/src/bcpg/MarkerPacket.cs b/crypto/src/bcpg/MarkerPacket.cs
index f2c4507935..b5a8e9639f 100644
--- a/crypto/src/bcpg/MarkerPacket.cs
+++ b/crypto/src/bcpg/MarkerPacket.cs
@@ -10,6 +10,7 @@ public class MarkerPacket
         private readonly byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 };
 
         public MarkerPacket(BcpgInputStream bcpgIn)
+            :base(PacketTag.Marker)
         {
             bcpgIn.ReadFully(marker);
         }
diff --git a/crypto/src/bcpg/ModDetectionCodePacket.cs b/crypto/src/bcpg/ModDetectionCodePacket.cs
index ae8283aef5..56cd8d7cad 100644
--- a/crypto/src/bcpg/ModDetectionCodePacket.cs
+++ b/crypto/src/bcpg/ModDetectionCodePacket.cs
@@ -11,9 +11,10 @@ public class ModDetectionCodePacket
 
 		internal ModDetectionCodePacket(
             BcpgInputStream bcpgIn)
+            :base(PacketTag.ModificationDetectionCode)
         {
 			if (bcpgIn == null)
-				throw new ArgumentNullException("bcpgIn");
+				throw new ArgumentNullException(nameof(bcpgIn));
 
 			this.digest = new byte[20];
             bcpgIn.ReadFully(this.digest);
@@ -21,9 +22,10 @@ internal ModDetectionCodePacket(
 
 		public ModDetectionCodePacket(
             byte[] digest)
+            : base(PacketTag.ModificationDetectionCode)
         {
 			if (digest == null)
-				throw new ArgumentNullException("digest");
+				throw new ArgumentNullException(nameof(digest));
 
 			this.digest = (byte[]) digest.Clone();
         }
diff --git a/crypto/src/bcpg/OnePassSignaturePacket.cs b/crypto/src/bcpg/OnePassSignaturePacket.cs
index 4efae1d63e..161e84357f 100644
--- a/crypto/src/bcpg/OnePassSignaturePacket.cs
+++ b/crypto/src/bcpg/OnePassSignaturePacket.cs
@@ -7,15 +7,16 @@ namespace Org.BouncyCastle.Bcpg
 	public class OnePassSignaturePacket
 		: ContainedPacket
 	{
-		private int version;
-		private int sigType;
-		private HashAlgorithmTag hashAlgorithm;
-		private PublicKeyAlgorithmTag keyAlgorithm;
-		private long keyId;
-		private int nested;
+		private readonly int version;
+		private readonly int sigType;
+		private readonly HashAlgorithmTag hashAlgorithm;
+		private readonly PublicKeyAlgorithmTag keyAlgorithm;
+		private readonly long keyId;
+		private readonly int nested;
 
 		internal OnePassSignaturePacket(
 			BcpgInputStream	bcpgIn)
+			:base(PacketTag.OnePassSignature)
 		{
 			version = bcpgIn.ReadByte();
 			sigType = bcpgIn.ReadByte();
@@ -40,7 +41,8 @@ public OnePassSignaturePacket(
 			PublicKeyAlgorithmTag	keyAlgorithm,
 			long					keyId,
 			bool					isNested)
-		{
+			: base(PacketTag.OnePassSignature)
+        {
 			this.version = 3;
 			this.sigType = sigType;
 			this.hashAlgorithm = hashAlgorithm;
diff --git a/crypto/src/bcpg/Packet.cs b/crypto/src/bcpg/Packet.cs
index 83f6d1f74f..a3f84fbf72 100644
--- a/crypto/src/bcpg/Packet.cs
+++ b/crypto/src/bcpg/Packet.cs
@@ -1,7 +1,35 @@
+using System;
+
 namespace Org.BouncyCastle.Bcpg
 {
     public class Packet
         //: PacketTag
     {
+        private readonly PacketTag packetTag;
+
+        // for API backward compatibility
+        // it's unlikely this is being used, but just in case we'll mark
+        // unknown inputs as reserved.
+        public Packet()
+            : this(PacketTag.Reserved)
+        {
+        }
+
+        public Packet(PacketTag packetTag)
+        {
+            this.packetTag = packetTag;
+        }
+
+        public PacketTag Tag => packetTag;
+
+        /// <summary>
+        /// Returns whether the packet is to be considered critical for v6 implementations.
+        ///    * Packets with tags less or equal to 39 are critical.
+        ///    * Tags 40 to 59 are reserved for unassigned, non-critical packets.
+        ///    * Tags 60 to 63 are non-critical private or experimental packets.
+        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-packet-criticality"/>
+        /// </summary>
+        public bool IsCritical => (int)Tag <= 39;
     }
-}
+
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/PaddingPacket.cs b/crypto/src/bcpg/PaddingPacket.cs
index 4063c8a165..d91ed9b287 100644
--- a/crypto/src/bcpg/PaddingPacket.cs
+++ b/crypto/src/bcpg/PaddingPacket.cs
@@ -9,17 +9,20 @@ public class PaddingPacket
         private readonly byte[] padding;
 
         public PaddingPacket(BcpgInputStream bcpgIn)
+            :base(PacketTag.Padding)
         {
             padding = bcpgIn.ReadAll();
         }
 
         public PaddingPacket(int length, BcpgInputStream bcpgIn)
+            : base(PacketTag.Padding)
         {
             padding = new byte[length];
             bcpgIn.ReadFully(padding);
         }
 
         public PaddingPacket(byte[] padding)
+            : base(PacketTag.Padding)
         {
             this.padding = Arrays.Clone(padding);
         }
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index 25d2978905..b38b19bd55 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -11,13 +11,14 @@ namespace Org.BouncyCastle.Bcpg
 	public class PublicKeyEncSessionPacket
 		: ContainedPacket //, PublicKeyAlgorithmTag
 	{
-		private int version;
-		private long keyId;
-		private PublicKeyAlgorithmTag algorithm;
-        private byte[][] data;
+		private readonly int version;
+		private readonly long keyId;
+		private readonly PublicKeyAlgorithmTag algorithm;
+        private readonly byte[][] data;
 
 		internal PublicKeyEncSessionPacket(
 			BcpgInputStream bcpgIn)
+			:base(PacketTag.PublicKeyEncryptedSession)
 		{
 			version = bcpgIn.ReadByte();
 
@@ -59,7 +60,8 @@ public PublicKeyEncSessionPacket(
 			long                    keyId,
 			PublicKeyAlgorithmTag   algorithm,
 			byte[][]                data)
-		{
+            : base(PacketTag.PublicKeyEncryptedSession)
+        {
 			this.version = 3;
 			this.keyId = keyId;
 			this.algorithm = algorithm;
diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index 6449c03f4b..9d6d168393 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -25,8 +25,15 @@ public class PublicKeyPacket
 
         private readonly long v6KeyLen;
 
-        internal PublicKeyPacket(
-            BcpgInputStream bcpgIn)
+        internal PublicKeyPacket(BcpgInputStream bcpgIn)
+            : this(bcpgIn, PacketTag.PublicKey)
+        {
+        }
+
+        protected PublicKeyPacket(
+            BcpgInputStream bcpgIn,
+            PacketTag tag)
+            : base(tag)
         {
             version = bcpgIn.ReadByte();
 
@@ -93,6 +100,16 @@ public PublicKeyPacket(
             PublicKeyAlgorithmTag algorithm,
             DateTime time,
             IBcpgKey key)
+            : this(version, algorithm, time, key, PacketTag.PublicKey)
+        { }
+
+        protected PublicKeyPacket(
+            int version,
+            PublicKeyAlgorithmTag algorithm,
+            DateTime time,
+            IBcpgKey key,
+            PacketTag tag)
+            : base(tag)
         {
             this.version = version;
             this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
diff --git a/crypto/src/bcpg/PublicSubkeyPacket.cs b/crypto/src/bcpg/PublicSubkeyPacket.cs
index b9c7d73f54..0e70790a17 100644
--- a/crypto/src/bcpg/PublicSubkeyPacket.cs
+++ b/crypto/src/bcpg/PublicSubkeyPacket.cs
@@ -19,7 +19,7 @@ public PublicSubkeyPacket(
             PublicKeyAlgorithmTag algorithm,
             DateTime time,
             IBcpgKey key)
-            : base(version, algorithm, time, key)
+            : base(version, algorithm, time, key, PacketTag.PublicSubkey)
         {
         }
 
@@ -28,7 +28,7 @@ public PublicSubkeyPacket(
             PublicKeyAlgorithmTag	algorithm,
             DateTime				time,
             IBcpgKey				key)
-            : base(DefaultVersion, algorithm, time, key)
+            : base(DefaultVersion, algorithm, time, key, PacketTag.PublicSubkey)
         {
         }
 
diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index 7e825c9456..d237ef4694 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -71,8 +71,14 @@ public class SecretKeyPacket
         private bool HasS2KSpecifier
             => (s2kUsage == UsageChecksum || s2kUsage == UsageSha1 || s2kUsage == UsageAead);
 
-        internal SecretKeyPacket(
-            BcpgInputStream bcpgIn)
+        internal SecretKeyPacket(BcpgInputStream bcpgIn)
+            : this(bcpgIn, PacketTag.SecretKey)
+        {
+        }
+
+        protected SecretKeyPacket(
+            BcpgInputStream bcpgIn, PacketTag tag)
+            :base(tag)
         {
 			if (this is SecretSubkeyPacket)
 			{
@@ -144,12 +150,25 @@ internal SecretKeyPacket(
             secKeyData = bcpgIn.ReadAll();
         }
 
-		public SecretKeyPacket(
+
+        public SecretKeyPacket(
+            PublicKeyPacket pubKeyPacket,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            S2k s2k,
+            byte[] iv,
+            byte[] secKeyData)
+            : this(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData, PacketTag.SecretKey)
+        {
+        }
+
+        protected SecretKeyPacket(
             PublicKeyPacket				pubKeyPacket,
             SymmetricKeyAlgorithmTag	encAlgorithm,
             S2k							s2k,
             byte[]						iv,
-            byte[]						secKeyData)
+            byte[]						secKeyData,
+            PacketTag                   tag)
+            :base(tag)
         {
             this.pubKeyPacket = pubKeyPacket;
             this.encAlgorithm = encAlgorithm;
@@ -168,13 +187,26 @@ public SecretKeyPacket(
 			this.secKeyData = secKeyData;
         }
 
-		public SecretKeyPacket(
+        public SecretKeyPacket(
+            PublicKeyPacket pubKeyPacket,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            int s2kUsage,
+            S2k s2k,
+            byte[] iv,
+            byte[] secKeyData)
+            : this(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretKey)
+        {
+        }
+
+        protected SecretKeyPacket(
 			PublicKeyPacket				pubKeyPacket,
 			SymmetricKeyAlgorithmTag	encAlgorithm,
 			int							s2kUsage,
 			S2k							s2k,
 			byte[]						iv,
-			byte[]						secKeyData)
+			byte[]						secKeyData,
+            PacketTag                   tag)
+            :base(tag)
 		{
 			this.pubKeyPacket = pubKeyPacket;
 			this.encAlgorithm = encAlgorithm;
@@ -184,7 +216,6 @@ public SecretKeyPacket(
 			this.secKeyData = secKeyData;
 		}
 
-
         public SecretKeyPacket(
             PublicKeyPacket pubKeyPacket,
             SymmetricKeyAlgorithmTag encAlgorithm,
@@ -193,6 +224,20 @@ public SecretKeyPacket(
             S2k s2k,
             byte[] iv,
             byte[] secKeyData)
+            : this(pubKeyPacket, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretKey)
+        {
+        }
+
+        protected SecretKeyPacket(
+            PublicKeyPacket pubKeyPacket,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            int s2kUsage,
+            S2k s2k,
+            byte[] iv,
+            byte[] secKeyData,
+            PacketTag tag)
+            :base(tag)
         {
             this.pubKeyPacket = pubKeyPacket;
             this.encAlgorithm = encAlgorithm;
diff --git a/crypto/src/bcpg/SecretSubkeyPacket.cs b/crypto/src/bcpg/SecretSubkeyPacket.cs
index fd05f81b8b..5fb249a254 100644
--- a/crypto/src/bcpg/SecretSubkeyPacket.cs
+++ b/crypto/src/bcpg/SecretSubkeyPacket.cs
@@ -9,7 +9,7 @@ public class SecretSubkeyPacket
     {
         internal SecretSubkeyPacket(
 			BcpgInputStream bcpgIn)
-            : base(bcpgIn)
+            : base(bcpgIn, PacketTag.SecretSubkey)
         {
         }
 
@@ -19,7 +19,7 @@ public SecretSubkeyPacket(
             S2k							s2k,
             byte[]						iv,
             byte[]						secKeyData)
-            : base(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData)
+            : base(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData, PacketTag.SecretSubkey)
         {
         }
 
@@ -30,7 +30,7 @@ public SecretSubkeyPacket(
 			S2k							s2k,
 			byte[]						iv,
 			byte[]						secKeyData)
-			: base(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData)
+			: base(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretSubkey)
 		{
 		}
 
@@ -42,7 +42,7 @@ public SecretSubkeyPacket(
             S2k s2k,
             byte[] iv,
             byte[] secKeyData)
-            :base(pubKeyPacket, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, secKeyData)
+            :base(pubKeyPacket, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretSubkey)
         {
         }
 
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 4fcd27be19..45f9749a24 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -36,6 +36,7 @@ public class SignaturePacket
         private readonly byte[] salt;
 
         internal SignaturePacket(BcpgInputStream bcpgIn)
+            :base(PacketTag.Signature)
         {
             version = bcpgIn.ReadByte();
 
@@ -282,6 +283,7 @@ public SignaturePacket(
             SignatureSubpacket[]	unhashedData,
             byte[]					fingerprint,
             MPInteger[]				signature)
+            : base(PacketTag.Signature)
         {
             this.version = version;
             this.signatureType = signatureType;
diff --git a/crypto/src/bcpg/SymmetricEncDataPacket.cs b/crypto/src/bcpg/SymmetricEncDataPacket.cs
index 17ee55bb73..013a7fd39e 100644
--- a/crypto/src/bcpg/SymmetricEncDataPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncDataPacket.cs
@@ -8,7 +8,7 @@ public class SymmetricEncDataPacket
     {
         public SymmetricEncDataPacket(
             BcpgInputStream bcpgIn)
-            : base(bcpgIn)
+            : base(bcpgIn, PacketTag.SymmetricKeyEncrypted)
         {
         }
     }
diff --git a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
index a9b6d06782..1de72a4a02 100644
--- a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
@@ -10,7 +10,7 @@ public class SymmetricEncIntegrityPacket
 
 		internal SymmetricEncIntegrityPacket(
 			BcpgInputStream bcpgIn)
-			: base(bcpgIn)
+			: base(bcpgIn, PacketTag.SymmetricEncryptedIntegrityProtected)
         {
 			version = bcpgIn.ReadByte();
         }
diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
index 901088e33a..f053b431aa 100644
--- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
@@ -9,13 +9,14 @@ namespace Org.BouncyCastle.Bcpg
     public class SymmetricKeyEncSessionPacket
         : ContainedPacket
     {
-        private int version;
-        private SymmetricKeyAlgorithmTag encAlgorithm;
-        private S2k s2k;
+        private readonly int version;
+        private readonly SymmetricKeyAlgorithmTag encAlgorithm;
+        private readonly S2k s2k;
         private readonly byte[] secKeyData;
 
         public SymmetricKeyEncSessionPacket(
             BcpgInputStream bcpgIn)
+            :base(PacketTag.SymmetricKeyEncryptedSessionKey)
         {
             version = bcpgIn.ReadByte();
             encAlgorithm = (SymmetricKeyAlgorithmTag) bcpgIn.ReadByte();
@@ -29,6 +30,7 @@ public SymmetricKeyEncSessionPacket(
             SymmetricKeyAlgorithmTag	encAlgorithm,
             S2k							s2k,
             byte[]						secKeyData)
+            : base(PacketTag.SymmetricKeyEncryptedSessionKey)
         {
             this.version = 4;
             this.encAlgorithm = encAlgorithm;
diff --git a/crypto/src/bcpg/TrustPacket.cs b/crypto/src/bcpg/TrustPacket.cs
index a2a30177fb..1dd02f7f38 100644
--- a/crypto/src/bcpg/TrustPacket.cs
+++ b/crypto/src/bcpg/TrustPacket.cs
@@ -10,19 +10,21 @@ public class TrustPacket
         private readonly byte[] levelAndTrustAmount;
 
 		public TrustPacket(BcpgInputStream bcpgIn)
+            :base(PacketTag.Trust)
         {
             MemoryStream bOut = new MemoryStream();
-
-			int ch;
+            
+            int ch;
             while ((ch = bcpgIn.ReadByte()) >= 0)
             {
                 bOut.WriteByte((byte) ch);
             }
-
-			levelAndTrustAmount = bOut.ToArray();
+            
+            levelAndTrustAmount = bOut.ToArray();
         }
 
 		public TrustPacket(int trustCode)
+            : base(PacketTag.Trust)
         {
 			this.levelAndTrustAmount = new byte[]{ (byte)trustCode };
         }
diff --git a/crypto/src/bcpg/UserAttributePacket.cs b/crypto/src/bcpg/UserAttributePacket.cs
index e976f1215f..8d1261f700 100644
--- a/crypto/src/bcpg/UserAttributePacket.cs
+++ b/crypto/src/bcpg/UserAttributePacket.cs
@@ -13,6 +13,7 @@ public class UserAttributePacket
 
         public UserAttributePacket(
             BcpgInputStream bcpgIn)
+            :base(PacketTag.UserAttribute)
         {
             UserAttributeSubpacketsParser sIn = new UserAttributeSubpacketsParser(bcpgIn);
             UserAttributeSubpacket sub;
@@ -28,6 +29,7 @@ public UserAttributePacket(
 
         public UserAttributePacket(
             UserAttributeSubpacket[] subpackets)
+            : base(PacketTag.UserAttribute)
         {
             this.subpackets = subpackets;
         }
diff --git a/crypto/src/bcpg/UserIdPacket.cs b/crypto/src/bcpg/UserIdPacket.cs
index 7d3d3a846f..86ae6d7b35 100644
--- a/crypto/src/bcpg/UserIdPacket.cs
+++ b/crypto/src/bcpg/UserIdPacket.cs
@@ -14,16 +14,19 @@ public class UserIdPacket
         private readonly byte[] idData;
 
         public UserIdPacket(BcpgInputStream bcpgIn)
+            :base(PacketTag.UserId)
         {
             this.idData = bcpgIn.ReadAll();
         }
 
 		public UserIdPacket(string id)
+            : base(PacketTag.UserId)
         {
             this.idData = Encoding.UTF8.GetBytes(id);
         }
 
         public UserIdPacket(byte[] rawId)
+            : base(PacketTag.UserId)
         {
             this.idData = Arrays.Clone(rawId);
         }
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index ac847ddb6b..13648d1e8f 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -23,8 +23,8 @@ public class PgpEncryptedDataGenerator
 		private BcpgOutputStream	pOut;
         private CipherStream		cOut;
         private IBufferedCipher		c;
-        private bool				withIntegrityPacket;
-        private bool				oldFormat;
+        private readonly bool		withIntegrityPacket;
+        private readonly bool		oldFormat;
         private DigestStream		digestOut;
 
 		private abstract class EncMethod
@@ -34,18 +34,24 @@ private abstract class EncMethod
             protected SymmetricKeyAlgorithmTag  encAlgorithm;
             protected KeyParameter              key;
 
-			public abstract void AddSessionInfo(byte[] si, SecureRandom random);
+            protected EncMethod(PacketTag packetTag)
+                : base(packetTag)
+            {
+            }
+
+            public abstract void AddSessionInfo(byte[] si, SecureRandom random);
         }
 
         private class PbeMethod
             : EncMethod
         {
-            private S2k s2k;
+            private readonly S2k s2k;
 
             internal PbeMethod(
                 SymmetricKeyAlgorithmTag  encAlgorithm,
                 S2k                       s2k,
                 KeyParameter              key)
+                : base(PacketTag.SymmetricKeyEncryptedSessionKey)
             {
                 this.encAlgorithm = encAlgorithm;
                 this.s2k = s2k;
@@ -87,6 +93,7 @@ private class PubMethod
             internal byte[][] data;
 
             internal PubMethod(PgpPublicKey pubKey, bool sessionKeyObfuscation)
+                : base(PacketTag.PublicKeyEncryptedSession)
             {
                 this.pubKey = pubKey;
                 this.sessionKeyObfuscation = sessionKeyObfuscation;

From 683e76f3efe7e34f9b7a182c8b276f415221ebde Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 22 Feb 2024 19:04:42 +0100
Subject: [PATCH 10/37] verify v6 key binding signatures

---
 crypto/src/openpgp/PgpPublicKey.cs            | 47 ++++++++++++++-----
 crypto/src/openpgp/PgpSignature.cs            | 26 ++++++++--
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 14 ++++--
 3 files changed, 68 insertions(+), 19 deletions(-)

diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index a2689d4e5f..43fe698e4d 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -30,6 +30,37 @@ public class PgpPublicKey
         private const byte v5FingerprintPreamble = 0x9A;
         private const byte v6FingerprintPreamble = 0x9B;
 
+        internal static byte FingerprintPreamble(int version)
+        {
+            switch (version)
+            {
+                case PublicKeyPacket.Version4:
+                    return v4FingerprintPreamble;
+                case PublicKeyPacket.Version5:
+                    return v5FingerprintPreamble;
+                case PublicKeyPacket.Version6:
+                    return v6FingerprintPreamble;
+                default:
+                    throw new PgpException($"unsupported OpenPGP key packet version: {version}");
+            }
+        }
+        private static IDigest CreateDigestForFingerprint(int version)
+        {
+            switch (version)
+            {
+                case PublicKeyPacket.Version2:
+                case PublicKeyPacket.Version3:
+                    return PgpUtilities.CreateDigest(HashAlgorithmTag.MD5);
+                case PublicKeyPacket.Version4:
+                    return PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
+                case PublicKeyPacket.Version5:
+                case PublicKeyPacket.Version6:
+                    return PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+                default:
+                    throw new PgpException($"unsupported OpenPGP key packet version: {version}");
+            }
+        }
+
         // We default to these as they are specified as mandatory in RFC 6631.
         private static readonly PgpKdfParameters DefaultKdfParameters = new PgpKdfParameters(HashAlgorithmTag.Sha256,
             SymmetricKeyAlgorithmTag.Aes128);
@@ -37,7 +68,7 @@ public class PgpPublicKey
         public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
         {
             IBcpgKey key = publicPk.Key;
-            IDigest digest;
+            IDigest digest = CreateDigestForFingerprint(publicPk.Version);
 
             if (publicPk.Version <= PublicKeyPacket.Version3)
             {
@@ -45,8 +76,6 @@ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
 
                 try
                 {
-                    digest = PgpUtilities.CreateDigest(HashAlgorithmTag.MD5);
-
                     UpdateDigest(digest, rK.Modulus);
                     UpdateDigest(digest, rK.PublicExponent);
                 }
@@ -59,21 +88,17 @@ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
             {
                 try
                 {
+                    digest.Update(FingerprintPreamble(publicPk.Version));
+
                     byte[] kBytes = publicPk.GetEncodedContents();
 
                     if (publicPk.Version == PublicKeyPacket.Version4)
                     {
-                        digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
-
-                        digest.Update(v4FingerprintPreamble);
                         digest.Update((byte)(kBytes.Length >> 8));
                         digest.Update((byte)kBytes.Length);
                     }
                     else if (publicPk.Version == PublicKeyPacket.Version5 || publicPk.Version == PublicKeyPacket.Version6)
                     {
-                        digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
-
-                        digest.Update(publicPk.Version == PublicKeyPacket.Version5 ? v5FingerprintPreamble : v6FingerprintPreamble);
                         digest.Update((byte)(kBytes.Length >> 24));
                         digest.Update((byte)(kBytes.Length >> 16));
                         digest.Update((byte)(kBytes.Length >> 8));
@@ -81,7 +106,7 @@ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
                     }
                     else
                     {
-                        throw new PgpException("unsupported OpenPGP key packet version: " + publicPk.Version);
+                        throw new PgpException($"unsupported OpenPGP key packet version: {publicPk.Version}");
                     }
 
                     digest.BlockUpdate(kBytes, 0, kBytes.Length);
@@ -1390,4 +1415,4 @@ public static PgpPublicKey Join(PgpPublicKey key, PgpPublicKey copy, bool joinTr
             return merged;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index 4505c72c85..01d2a142e0 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -228,10 +228,25 @@ private void UpdateWithPublicKey(PgpPublicKey key)
 		{
 			byte[] keyBytes = GetEncodedPublicKey(key);
 
-			this.Update(
-				(byte) 0x99,
-				(byte)(keyBytes.Length >> 8),
-				(byte)(keyBytes.Length));
+            this.Update(PgpPublicKey.FingerprintPreamble(key.Version));
+
+            switch (key.Version)
+            {
+                case PublicKeyPacket.Version4:
+                    this.Update(
+                        (byte)(keyBytes.Length >> 8),
+                        (byte)(keyBytes.Length));
+                    break;
+                case PublicKeyPacket.Version5:
+                case PublicKeyPacket.Version6:
+                    this.Update(
+                        (byte)(keyBytes.Length >> 24),
+                        (byte)(keyBytes.Length >> 16),
+                        (byte)(keyBytes.Length >> 8),
+                        (byte)(keyBytes.Length));
+                    break;
+            }
+
 			this.Update(keyBytes);
 		}
 
@@ -306,7 +321,8 @@ public bool VerifyCertification(
             PgpPublicKey pubKey)
         {
             if (SignatureType != KeyRevocation
-                && SignatureType != SubkeyRevocation)
+                && SignatureType != SubkeyRevocation
+                && SignatureType != DirectKey)
             {
                 throw new InvalidOperationException("signature is not a key signature");
             }
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index e89e82e362..3ce1eccb1f 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -157,7 +157,11 @@ public void Version6CertificateParsingTest()
             IsEquals((ulong)masterKey.KeyId, 0xCB186C4F0609A697);
             IsTrue("wrong master key fingerprint", AreEqual(masterKey.GetFingerprint(), expectedFingerprint));
 
-            // TODO Verify self signatures
+            // Verify direct key self-signature
+            PgpSignature selfSig = masterKey.GetSignatures().First();
+            IsTrue(selfSig.SignatureType == PgpSignature.DirectKey);
+            selfSig.InitVerify(masterKey);
+            FailIf("self signature verification failed", !selfSig.VerifyCertification(masterKey));
 
             // subkey
             PgpPublicKey subKey = publicKeys[1];
@@ -167,7 +171,11 @@ public void Version6CertificateParsingTest()
             IsEquals(subKey.KeyId, 0x12C83F1E706F6308);
             IsTrue("wrong sub key fingerprint", AreEqual(subKey.GetFingerprint(), expectedFingerprint));
 
-            // TODO Verify subkey binding signature
+            // Verify subkey binding signature
+            PgpSignature bindingSig = subKey.GetSignatures().First();
+            IsTrue(bindingSig.SignatureType == PgpSignature.SubkeyBinding);
+            bindingSig.InitVerify(masterKey);
+            FailIf("subkey binding signature verification failed", !bindingSig.VerifyCertification(masterKey, subKey));
 
             // Encode test
             using (MemoryStream ms = new MemoryStream())
@@ -188,7 +196,7 @@ public void Version6PublicKeyCreationTest()
             /* 
              * Create a v6 Ed25519 pubkey with the same key material and creation datetime as the test vector
              * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
-             * the check the fingerprint and verify a signature
+             * then check the fingerprint and verify a signature
             */
             byte[] keyMaterial = Hex.Decode("f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3");
             var key = new Ed25519PublicKeyParameters(keyMaterial);

From 31050849ac786370d557d57cae8e032d8629ee74 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Fri, 23 Feb 2024 18:46:21 +0100
Subject: [PATCH 11/37] parse and verify v6 One-Pass Signatures

---
 crypto/src/bcpg/OnePassSignaturePacket.cs | 164 ++++++++++++++++++----
 crypto/src/openpgp/PgpOnePassSignature.cs |  17 ++-
 2 files changed, 153 insertions(+), 28 deletions(-)

diff --git a/crypto/src/bcpg/OnePassSignaturePacket.cs b/crypto/src/bcpg/OnePassSignaturePacket.cs
index 161e84357f..cde5501b4c 100644
--- a/crypto/src/bcpg/OnePassSignaturePacket.cs
+++ b/crypto/src/bcpg/OnePassSignaturePacket.cs
@@ -1,40 +1,91 @@
-using System;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
 using System.IO;
 
 namespace Org.BouncyCastle.Bcpg
 {
-	/// <remarks>Generic signature object</remarks>
-	public class OnePassSignaturePacket
+    /// <remarks>Generic signature object</remarks>
+    public class OnePassSignaturePacket
 		: ContainedPacket
 	{
-		private readonly int version;
+        public const int Version3 = 3;
+        public const int Version6 = 6;
+
+        private readonly int version;
 		private readonly int sigType;
 		private readonly HashAlgorithmTag hashAlgorithm;
-		private readonly PublicKeyAlgorithmTag keyAlgorithm;
-		private readonly long keyId;
+        private readonly PublicKeyAlgorithmTag keyAlgorithm;
+        private readonly long keyId;
 		private readonly int nested;
 
-		internal OnePassSignaturePacket(
+		// fields for V6
+		private readonly byte[] salt;
+		private readonly byte[] fingerprint;
+
+		private void EnforceConstraints()
+		{
+			int expectedSaltSize = PgpUtilities.GetSaltSize(hashAlgorithm);
+
+            if (version == Version6 && salt.Length != expectedSaltSize)
+            {
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-one-pass-signature-packet-t
+                // The salt size MUST match the value defined for the hash algorithm as specified in Table 23
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#hash-algorithms-registry
+                throw new IOException($"invalid salt size for v6 signature: expected {expectedSaltSize} got {salt.Length}");
+            }
+        }
+
+        internal OnePassSignaturePacket(
 			BcpgInputStream	bcpgIn)
 			:base(PacketTag.OnePassSignature)
 		{
 			version = bcpgIn.ReadByte();
+			if (version != Version3 && version != Version6)
+			{
+                throw new UnsupportedPacketVersionException($"unsupported OpenPGP One Pass Signature packet version: {version}");
+            }
+
 			sigType = bcpgIn.ReadByte();
 			hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte();
 			keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte();
 
-			keyId |= (long)bcpgIn.ReadByte() << 56;
-			keyId |= (long)bcpgIn.ReadByte() << 48;
-			keyId |= (long)bcpgIn.ReadByte() << 40;
-			keyId |= (long)bcpgIn.ReadByte() << 32;
-			keyId |= (long)bcpgIn.ReadByte() << 24;
-			keyId |= (long)bcpgIn.ReadByte() << 16;
-			keyId |= (long)bcpgIn.ReadByte() << 8;
-			keyId |= (uint)bcpgIn.ReadByte();
+            if (version == Version3)
+			{
+				keyId |= (long)bcpgIn.ReadByte() << 56;
+				keyId |= (long)bcpgIn.ReadByte() << 48;
+				keyId |= (long)bcpgIn.ReadByte() << 40;
+				keyId |= (long)bcpgIn.ReadByte() << 32;
+				keyId |= (long)bcpgIn.ReadByte() << 24;
+				keyId |= (long)bcpgIn.ReadByte() << 16;
+				keyId |= (long)bcpgIn.ReadByte() << 8;
+				keyId |= (uint)bcpgIn.ReadByte();
+			}
+			else
+			{
+                //Version 6
+                int saltSize = bcpgIn.ReadByte();
+                salt = new byte[saltSize];
+                bcpgIn.ReadFully(salt);
+
+				fingerprint = new byte[32];
+                bcpgIn.ReadFully(fingerprint);
+				keyId = (long)Pack.BE_To_UInt64(fingerprint);
+            }
 
 			nested = bcpgIn.ReadByte();
-		}
 
+			EnforceConstraints();
+        }
+
+		/// <summary>
+		/// Create a Version 3 OPS Packet
+		/// </summary>
+		/// <param name="sigType"></param>
+		/// <param name="hashAlgorithm"></param>
+		/// <param name="keyAlgorithm"></param>
+		/// <param name="keyId"></param>
+		/// <param name="isNested"></param>
 		public OnePassSignaturePacket(
 			int						sigType,
 			HashAlgorithmTag		hashAlgorithm,
@@ -43,15 +94,51 @@ public OnePassSignaturePacket(
 			bool					isNested)
 			: base(PacketTag.OnePassSignature)
         {
-			this.version = 3;
+			this.version = Version3;
 			this.sigType = sigType;
 			this.hashAlgorithm = hashAlgorithm;
 			this.keyAlgorithm = keyAlgorithm;
 			this.keyId = keyId;
 			this.nested = (isNested) ? 0 : 1;
+
+            EnforceConstraints();
+        }
+
+        /// <summary>
+		/// Create a Version 6 OPS Packet
+		/// </summary>
+		/// <param name="sigType"></param>
+		/// <param name="hashAlgorithm"></param>
+		/// <param name="keyAlgorithm"></param>
+		/// <param name="salt"></param>
+		/// <param name="fingerprint"></param>
+		/// <param name="isNested"></param>
+		public OnePassSignaturePacket(
+			int sigType,
+			HashAlgorithmTag hashAlgorithm,
+			PublicKeyAlgorithmTag keyAlgorithm,
+			byte[] salt,
+			byte[] fingerprint,
+			bool isNested)
+			: base(PacketTag.OnePassSignature)
+        {
+            this.version = Version6;
+            this.sigType = sigType;
+            this.hashAlgorithm = hashAlgorithm;
+            this.keyAlgorithm = keyAlgorithm;
+            this.salt = Arrays.Clone(salt);
+            this.fingerprint = Arrays.Clone(fingerprint);
+            this.keyId = (long)Pack.BE_To_UInt64(fingerprint);
+            this.nested = (isNested) ? 0 : 1;
+
+            EnforceConstraints();
+        }
+
+		public int Version {
+			get { return version; }
 		}
 
-		public int SignatureType
+        public int SignatureType
 		{
 			get { return sigType; }
 		}
@@ -73,17 +160,40 @@ public long KeyId
 			get { return keyId; }
 		}
 
-		public override void Encode(BcpgOutputStream bcpgOut)
+        public byte[] GetFingerprint()
+        {
+			return Arrays.Clone(fingerprint);
+        }
+
+        public byte[] GetSignatureSalt()
+        {
+            return Arrays.Clone(salt);
+        }
+
+        public override void Encode(BcpgOutputStream bcpgOut)
 		{
-			MemoryStream bOut = new MemoryStream();
-			using (var pOut = new BcpgOutputStream(bOut))
-            {
-				pOut.Write((byte)version, (byte)sigType, (byte)hashAlgorithm, (byte)keyAlgorithm);
-				pOut.WriteLong(keyId);
-				pOut.WriteByte((byte)nested);
-			}
+			using (MemoryStream bOut = new MemoryStream())
+			{
+				using (var pOut = new BcpgOutputStream(bOut))
+				{
+					pOut.Write((byte)version, (byte)sigType, (byte)hashAlgorithm, (byte)keyAlgorithm);
+
+					if (version == Version3)
+					{
+						pOut.WriteLong(keyId);
+					}
+					else
+					{
+						// V6
+						pOut.WriteByte((byte)salt.Length);
+						pOut.Write(salt);
+                        pOut.Write(fingerprint);
+                    }
+					pOut.WriteByte((byte)nested);
+				}
 
-			bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray());
+				bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray());
+			}
 		}
 	}
 }
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index c14e72bf77..8349438534 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -55,6 +55,11 @@ public void InitVerify(PgpPublicKey pubKey)
 			try
             {
                 sig.Init(false, key);
+                if (sigPack.Version == OnePassSignaturePacket.Version6)
+                {
+                    byte[] salt = sigPack.GetSignatureSalt();
+                    sig.BlockUpdate(salt, 0, salt.Length);
+                }
             }
 			catch (InvalidKeyException e)
             {
@@ -174,7 +179,17 @@ public PublicKeyAlgorithmTag KeyAlgorithm
 			get { return sigPack.KeyAlgorithm; }
 		}
 
-		public byte[] GetEncoded()
+        public byte[] GetSignatureSalt()
+        {
+            return sigPack.GetSignatureSalt();
+        }
+
+        public byte[] GetFingerprint()
+        {
+            return sigPack.GetFingerprint();
+        }
+
+        public byte[] GetEncoded()
         {
             var bOut = new MemoryStream();
 

From a613784591f9ee07e0d3fd8f5ca729e108f92667 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Wed, 28 Feb 2024 21:40:11 +0100
Subject: [PATCH 12/37] generate v6 signatures

---
 crypto/src/bcpg/SignaturePacket.cs            | 128 +++++++--
 crypto/src/openpgp/PgpKeyPair.cs              |   2 +-
 crypto/src/openpgp/PgpOnePassSignature.cs     |  11 +
 crypto/src/openpgp/PgpPrivateKey.cs           |  51 +++-
 crypto/src/openpgp/PgpPublicKey.cs            |   2 +-
 crypto/src/openpgp/PgpSecretKey.cs            |   2 +-
 crypto/src/openpgp/PgpSignature.cs            |  51 +++-
 crypto/src/openpgp/PgpSignatureGenerator.cs   | 249 +++++++++++++-----
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 129 ++++++---
 9 files changed, 487 insertions(+), 138 deletions(-)

diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 45f9749a24..2c56b05bff 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -20,10 +20,13 @@ public class SignaturePacket
         public const int Version5 = 5;
         public const int Version6 = 6;
 
+        public const int DefaultVersion = Version4;
+
         private readonly int                    version;
         private readonly int                    signatureType;
         private long                            creationTime;
-        private readonly long                   keyId;
+        private long                            keyId;
+        private bool                            keyIdAlreadySet = false;
         private readonly PublicKeyAlgorithmTag  keyAlgorithm;
         private readonly HashAlgorithmTag       hashAlgorithm;
         private readonly MPInteger[]            signature;
@@ -34,6 +37,39 @@ public class SignaturePacket
 
         // fields for v6 signatures
         private readonly byte[] salt;
+        private byte[] issuerFingerprint = null;
+
+        private void CheckIssuerSubpacket(SignatureSubpacket p)
+        {
+            if (p is IssuerFingerprint issuerFingerprintPkt && !(issuerFingerprint is null))
+            {
+                issuerFingerprint = issuerFingerprintPkt.GetFingerprint();
+
+                if (issuerFingerprintPkt.KeyVersion == PublicKeyPacket.Version4)
+                {
+                    keyId = (long)Pack.BE_To_UInt64(issuerFingerprint, issuerFingerprint.Length - 8);
+                }
+                else
+                {
+                    // v5 or v6
+                    keyId = (long)Pack.BE_To_UInt64(issuerFingerprint);
+                }
+                keyIdAlreadySet = true;
+            }
+
+            else if (p is IssuerKeyId issuerKeyId && !keyIdAlreadySet)
+            {
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-issuer-key-id
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#issuer-fingerprint-subpacket
+                // V6 signatures MUST NOT include an IssuerKeyId subpacket and SHOULD include an IssuerFingerprint subpacket
+                if (version == Version6)
+                {
+                    throw new IOException("V6 signatures MUST NOT include an IssuerKeyId subpacket");
+                }
+                keyId = issuerKeyId.KeyId;
+                keyIdAlreadySet = true;
+            }
+        }
 
         internal SignaturePacket(BcpgInputStream bcpgIn)
             :base(PacketTag.Signature)
@@ -102,11 +138,9 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
 
 				foreach (var p in hashedData)
                 {
-                    if (p is IssuerKeyId issuerKeyId)
-                    {
-                        keyId = issuerKeyId.KeyId;
-                    }
-                    else if (p is SignatureCreationTime sigCreationTime)
+                    CheckIssuerSubpacket(p);
+                    
+                    if (p is SignatureCreationTime sigCreationTime)
                     {
                         creationTime = DateTimeUtilities.DateTimeToUnixMs(sigCreationTime.GetTime());
                     }
@@ -144,10 +178,7 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
 
 				foreach (var p in unhashedData)
                 {
-                    if (p is IssuerKeyId issuerKeyId)
-                    {
-                        keyId = issuerKeyId.KeyId;
-                    }
+                    CheckIssuerSubpacket(p);
                 }
             }
             else
@@ -226,7 +257,7 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
             }
         }
 
-		/**
+        /**
         * Generate a version 4 signature packet.
         *
         * @param signatureType
@@ -246,7 +277,7 @@ public SignaturePacket(
             SignatureSubpacket[]	unhashedData,
             byte[]					fingerprint,
             MPInteger[]				signature)
-            : this(Version4, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, signature)
+            : this(Version4, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, null, null, signature)
         {
         }
 
@@ -268,7 +299,7 @@ public SignaturePacket(
             long					creationTime,
             byte[]					fingerprint,
             MPInteger[]				signature)
-            : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, null, null, fingerprint, signature)
+            : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, null, null, fingerprint, null, null, signature)
         {
 			this.creationTime = creationTime;
         }
@@ -283,6 +314,55 @@ public SignaturePacket(
             SignatureSubpacket[]	unhashedData,
             byte[]					fingerprint,
             MPInteger[]				signature)
+            :this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, null, null, signature)
+        {
+		}
+
+        public SignaturePacket(
+            int version,
+            int signatureType,
+            long keyId,
+            PublicKeyAlgorithmTag keyAlgorithm,
+            HashAlgorithmTag hashAlgorithm,
+            SignatureSubpacket[] hashedData,
+            SignatureSubpacket[] unhashedData,
+            byte[] fingerprint,
+            byte[] salt,
+            byte[] issuerFingerprint,
+            byte[] signatureEncoding)
+            : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, salt, issuerFingerprint)
+        {
+            this.signatureEncoding = Arrays.Clone(signatureEncoding);
+        }
+
+        public SignaturePacket(
+            int version,
+            int signatureType,
+            long keyId,
+            PublicKeyAlgorithmTag keyAlgorithm,
+            HashAlgorithmTag hashAlgorithm,
+            SignatureSubpacket[] hashedData,
+            SignatureSubpacket[] unhashedData,
+            byte[] fingerprint,
+            byte[] salt,
+            byte[] issuerFingerprint,
+            MPInteger[] signature)
+            : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, salt, issuerFingerprint)
+        {
+            this.signature = signature;
+        }
+
+        private SignaturePacket(
+            int version,
+            int signatureType,
+            long keyId,
+            PublicKeyAlgorithmTag keyAlgorithm,
+            HashAlgorithmTag hashAlgorithm,
+            SignatureSubpacket[] hashedData,
+            SignatureSubpacket[] unhashedData,
+            byte[] fingerprint,
+            byte[] salt,
+            byte[] issuerFingerprint)
             : base(PacketTag.Signature)
         {
             this.version = version;
@@ -292,16 +372,17 @@ public SignaturePacket(
             this.hashAlgorithm = hashAlgorithm;
             this.hashedData = hashedData;
             this.unhashedData = unhashedData;
-            this.fingerprint = fingerprint;
-            this.signature = signature;
+            this.fingerprint = Arrays.Clone(fingerprint);
+            this.salt = Arrays.Clone(salt);
+            this.issuerFingerprint = Arrays.Clone(issuerFingerprint);
 
-			if (hashedData != null)
-			{
-				SetCreationTime();
-			}
-		}
+            if (hashedData != null)
+            {
+                SetCreationTime();
+            }
+        }
 
-		public int Version => version;
+        public int Version => version;
 
 		public int SignatureType => signatureType;
 
@@ -311,6 +392,11 @@ public SignaturePacket(
         */
         public long KeyId => keyId;
 
+        public byte[] GetIssuerFingerprint()
+        {
+            return Arrays.Clone(issuerFingerprint);
+        }
+
         /**
          * Return the signatures fingerprint.
          * @return fingerprint (digest prefix) of the signature
diff --git a/crypto/src/openpgp/PgpKeyPair.cs b/crypto/src/openpgp/PgpKeyPair.cs
index 9cf78fa6fb..65ef5e829b 100644
--- a/crypto/src/openpgp/PgpKeyPair.cs
+++ b/crypto/src/openpgp/PgpKeyPair.cs
@@ -34,7 +34,7 @@ public PgpKeyPair(
             DateTime				time)
         {
             this.pub = new PgpPublicKey(algorithm, pubKey, time);
-			this.priv = new PgpPrivateKey(pub.KeyId, pub.PublicKeyPacket, privKey);
+			this.priv = new PgpPrivateKey(pub, privKey);
         }
 
 		/// <summary>Create a key pair from a PgpPrivateKey and a PgpPublicKey.</summary>
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index 8349438534..cbc889ca03 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -152,6 +152,17 @@ public void Update(ReadOnlySpan<byte> input)
         /// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
         public bool Verify(PgpSignature pgpSig)
         {
+            // the versions of the Signature and the One-Pass Signature must be aligned as specified in
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#signed-message-versions
+            if (pgpSig.Version == SignaturePacket.Version6 && sigPack.Version != OnePassSignaturePacket.Version6)
+            {
+                return false;
+            }
+            if (pgpSig.Version < SignaturePacket.Version6 && sigPack.Version != OnePassSignaturePacket.Version3)
+            {
+                return false;
+            }
+
             byte[] trailer = pgpSig.GetSignatureTrailer();
 
 			sig.BlockUpdate(trailer, 0, trailer.Length);
diff --git a/crypto/src/openpgp/PgpPrivateKey.cs b/crypto/src/openpgp/PgpPrivateKey.cs
index 61487a5b25..57b3bdca61 100644
--- a/crypto/src/openpgp/PgpPrivateKey.cs
+++ b/crypto/src/openpgp/PgpPrivateKey.cs
@@ -1,16 +1,45 @@
 using System;
 
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
 
 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 int version;
         private readonly long keyID;
+        private readonly byte[] fingerprint;
         private readonly PublicKeyPacket publicKeyPacket;
         private readonly AsymmetricKeyParameter privateKey;
 
+        /// <summary>
+        /// Create a PgpPrivateKey from  associated public key, and a regular private key.
+        /// </summary>
+        /// <param name="pubKey">the corresponding public key</param>
+        /// <param name="privateKey">the private key data packet to be associated with this private key.</param>
+        public PgpPrivateKey(PgpPublicKey pubKey, AsymmetricKeyParameter privateKey)
+            :this(pubKey.KeyId, pubKey.GetFingerprint(), pubKey.PublicKeyPacket, privateKey)
+        {
+        }
+
+        private PgpPrivateKey(
+            long keyID,
+            byte[] fingerprint,
+            PublicKeyPacket publicKeyPacket,
+            AsymmetricKeyParameter privateKey)
+        {
+            if (!privateKey.IsPrivate)
+                throw new ArgumentException("Expected a private key", nameof(privateKey));
+
+            this.version = publicKeyPacket.Version;
+            this.keyID = keyID;
+            this.fingerprint = fingerprint;
+            this.publicKeyPacket = publicKeyPacket;
+            this.privateKey = privateKey;
+        }
+
         /// <summary>
 		/// Create a PgpPrivateKey from a keyID, the associated public data packet, and a regular private key.
 		/// </summary>
@@ -21,13 +50,8 @@ public PgpPrivateKey(
             long                    keyID,
             PublicKeyPacket         publicKeyPacket,
             AsymmetricKeyParameter	privateKey)
+            :this(keyID, null, publicKeyPacket, privateKey)
         {
-			if (!privateKey.IsPrivate)
-				throw new ArgumentException("Expected a private key", "privateKey");
-
-            this.keyID = keyID;
-            this.publicKeyPacket = publicKeyPacket;
-            this.privateKey = privateKey;
         }
 
         /// <summary>The keyId associated with the contained private key.</summary>
@@ -36,6 +60,21 @@ public long KeyId
 			get { return keyID; }
         }
 
+        /// <summary>
+        /// The version of the contained private key.
+        /// </summary>
+        public int Version {
+            get { return version; }
+        }
+
+        /// <summary>
+        /// The Fingerprint associated with the contained private key.
+        /// </summary>
+        public byte[] GetFingerprint()
+        {
+            return Arrays.Clone(fingerprint);
+        }
+
         /// <summary>The public key packet associated with this private key, if available.</summary>
         public PublicKeyPacket PublicKeyPacket
         {
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 43fe698e4d..61be4b6a4f 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -569,7 +569,7 @@ public long KeyId
         /// <summary>The fingerprint of the public key</summary>
         public byte[] GetFingerprint()
         {
-            return (byte[]) fingerprint.Clone();
+            return Arrays.Clone(fingerprint);
         }
 
         /// <summary>
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 320c2956d8..e93023f336 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -805,7 +805,7 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP
                     throw new PgpException("unknown public key algorithm encountered");
                 }
 
-                return new PgpPrivateKey(KeyId, pubPk, privateKey);
+                return new PgpPrivateKey(pub, privateKey);
             }
             catch (PgpException)
             {
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index 01d2a142e0..32126a46a8 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -228,17 +228,17 @@ private void UpdateWithPublicKey(PgpPublicKey key)
 		{
 			byte[] keyBytes = GetEncodedPublicKey(key);
 
-            this.Update(PgpPublicKey.FingerprintPreamble(key.Version));
+            this.Update(PgpPublicKey.FingerprintPreamble(Version));
 
-            switch (key.Version)
+            switch (Version)
             {
-                case PublicKeyPacket.Version4:
+                case SignaturePacket.Version4:
                     this.Update(
                         (byte)(keyBytes.Length >> 8),
                         (byte)(keyBytes.Length));
                     break;
-                case PublicKeyPacket.Version5:
-                case PublicKeyPacket.Version6:
+                case SignaturePacket.Version5:
+                case SignaturePacket.Version6:
                     this.Update(
                         (byte)(keyBytes.Length >> 24),
                         (byte)(keyBytes.Length >> 16),
@@ -343,7 +343,12 @@ public long KeyId
             get { return sigPck.KeyId; }
         }
 
-		/// <summary>The creation time of this signature.</summary>
+        public byte[] GetIssuerFingerprint()
+        {
+            return sigPck.GetIssuerFingerprint();
+        }
+
+        /// <summary>The creation time of this signature.</summary>
         public DateTime CreationTime
         {
 			get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
@@ -541,8 +546,13 @@ public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2)
             }
 
             SignatureSubpacket[] unhashed = merged.ToArray();
-            return new PgpSignature(
-                new SignaturePacket(
+
+            SignaturePacket sigpkt;
+
+            if (sig1.KeyAlgorithm == PublicKeyAlgorithmTag.Ed25519 || sig1.KeyAlgorithm == PublicKeyAlgorithmTag.Ed448)
+            {
+                sigpkt = new SignaturePacket(
+                    sig1.Version,
                     sig1.SignatureType,
                     sig1.KeyId,
                     sig1.KeyAlgorithm,
@@ -550,9 +560,28 @@ public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2)
                     sig1.GetHashedSubPackets().ToSubpacketArray(),
                     unhashed,
                     sig1.GetDigestPrefix(),
-                    sig1.sigPck.GetSignature()
-                )
-            );
+                    sig1.GetSignatureSalt(),
+                    sig1.GetIssuerFingerprint(),
+                    sig1.sigPck.GetSignatureBytes());
+            }
+            else
+            {
+                sigpkt = new SignaturePacket(
+                    sig1.Version,
+                    sig1.SignatureType,
+                    sig1.KeyId,
+                    sig1.KeyAlgorithm,
+                    sig1.HashAlgorithm,
+                    sig1.GetHashedSubPackets().ToSubpacketArray(),
+                    unhashed,
+                    sig1.GetDigestPrefix(),
+                    sig1.GetSignatureSalt(),
+                    sig1.GetIssuerFingerprint(),
+                    sig1.sigPck.GetSignature());
+            }
+
+
+            return new PgpSignature(sigpkt);
         }
     }
 }
diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs
index 7ff771997a..dbfe93e6ff 100644
--- a/crypto/src/openpgp/PgpSignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -14,17 +14,21 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 	/// <remarks>Generator for PGP signatures.</remarks>
     public class PgpSignatureGenerator
     {
-		private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
+		private static readonly SignatureSubpacket[] EmptySignatureSubpackets = Array.Empty<SignatureSubpacket>();
 
 		private readonly PublicKeyAlgorithmTag keyAlgorithm;
         private readonly HashAlgorithmTag hashAlgorithm;
 
+		private int version;
+
         private PgpPrivateKey			privKey;
         private ISigner					sig;
-        private IDigest					dig;
+        private readonly IDigest		dig;
         private int						signatureType;
         private byte					lastb;
 
+		private byte[] salt;
+
 		private SignatureSubpacket[] unhashed = EmptySignatureSubpackets;
         private SignatureSubpacket[] hashed = EmptySignatureSubpackets;
 
@@ -45,9 +49,13 @@ public void InitSign(int sigType, PgpPrivateKey privKey)
 			InitSign(sigType, privKey, null);
         }
 
-		/// <summary>Initialise the generator for signing.</summary>
-		public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
+        /// <summary>Initialise the generator for signing.</summary>
+        public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
 		{
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-signature-packet-type-id-2
+            // An implementation MUST generate a version 6 signature when signing with a version 6 key.
+			// An implementation MUST generate a version 4 signature when signing with a version 4 key. 
+            this.version = privKey.Version;
 			this.privKey = privKey;
 			this.signatureType = sigType;
 
@@ -60,7 +68,9 @@ public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
 				ICipherParameters cp = key;
 
 				// TODO Ask SignerUtilities whether random is permitted?
-				if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
+				if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy
+					|| keyAlgorithm == PublicKeyAlgorithmTag.Ed25519
+					|| keyAlgorithm == PublicKeyAlgorithmTag.Ed448)
 				{
 					// EdDSA signers don't expect a SecureRandom
 				}
@@ -70,6 +80,19 @@ public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
                 }
 
 				sig.Init(true, cp);
+
+				// salt for v6 signatures
+				if(version == SignaturePacket.Version6)
+				{
+                    if (random is null)
+					{
+						throw new ArgumentNullException(nameof(random), "v6 signatures requires a SecureRandom() for salt generation");
+					}
+					int saltSize = PgpUtilities.GetSaltSize(hashAlgorithm);
+					salt = new byte[saltSize];
+					random.NextBytes(salt);
+                    sig.BlockUpdate(salt, 0, salt.Length);
+                }
 			}
 			catch (InvalidKeyException e)
 			{
@@ -190,9 +213,23 @@ public void SetUnhashedSubpackets(
         public PgpOnePassSignature GenerateOnePassVersion(
             bool isNested)
         {
-            return new PgpOnePassSignature(
-				new OnePassSignaturePacket(
-					signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested));
+			OnePassSignaturePacket opsPkt;
+
+            // the versions of the Signature and the One-Pass Signature must be aligned as specified in
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#signed-message-versions
+            if (version == SignaturePacket.Version6)
+			{
+				opsPkt = new OnePassSignaturePacket(
+					signatureType, hashAlgorithm, keyAlgorithm, salt, privKey.GetFingerprint(), isNested);
+
+            }
+			else
+			{
+                opsPkt = new OnePassSignaturePacket(
+					signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested);
+            }
+
+            return new PgpOnePassSignature(opsPkt);
         }
 
 		/// <summary>Return a signature object containing the current signature state.</summary>
@@ -205,36 +242,65 @@ public PgpSignature Generate()
 				hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
 			}
 
-			if (!IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
-				&& !IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
-			{
-				unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
-			}
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-issuer-key-id
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#issuer-fingerprint-subpacket
+            bool containsIssuerKeyId = IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId) || IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId);
+            bool containsIssuerKeyFpr = IsPacketPresent(hashed, SignatureSubpacketTag.IssuerFingerprint) || IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerFingerprint);
+            switch (version)
+            {
+                case SignaturePacket.Version4:
+                    // TODO enforce constraint: if a v4 signature contains both IssuerKeyId and IssuerFingerprint
+                    // subpackets, then the KeyId MUST match the low 64 bits of the fingerprint.
+                    if (!containsIssuerKeyId)
+					{
+                        unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
+                    }
+                    break;
+                case SignaturePacket.Version6:
+					// V6 signatures MUST NOT include an IssuerKeyId subpacket and SHOULD include an IssuerFingerprint subpacket
+					if (containsIssuerKeyId)
+					{
+                        // TODO enforce constraint: v6 signatures MUST NOT include an IssuerKeyId subpacket
+                    }
+                    if (!containsIssuerKeyFpr)
+                    {
+                        unhPkts = InsertSubpacket(unhPkts, new IssuerFingerprint(false, privKey.Version, privKey.GetFingerprint()));
+                    }
+                    break;
+            }
 
-			int version = 4;
 			byte[] hData;
 
 			try
             {
-				MemoryStream hOut = new MemoryStream();
-
-				for (int i = 0; i != hPkts.Length; i++)
+				using (MemoryStream hOut = new MemoryStream())
 				{
-					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();
+					for (int i = 0; i != hPkts.Length; i++)
+					{
+						hPkts[i].Encode(hOut);
+					}
+
+					byte[] data = hOut.ToArray();
+
+					using (MemoryStream sOut = new MemoryStream(data.Length + 6))
+					{
+						sOut.WriteByte((byte)version);
+						sOut.WriteByte((byte)signatureType);
+						sOut.WriteByte((byte)keyAlgorithm);
+						sOut.WriteByte((byte)hashAlgorithm);
+						if (version == SignaturePacket.Version6)
+						{
+                            sOut.WriteByte((byte)(data.Length >> 24));
+                            sOut.WriteByte((byte)(data.Length >> 16));
+                        }
+						sOut.WriteByte((byte)(data.Length >> 8));
+						sOut.WriteByte((byte)data.Length);
+						sOut.Write(data, 0, data.Length);
+
+						hData = sOut.ToArray();
+					}
+				}
 			}
             catch (IOException e)
             {
@@ -261,41 +327,75 @@ public PgpSignature Generate()
 			byte[] digest = DigestUtilities.DoFinal(dig);
 			byte[] fingerPrint = new byte[2]{ digest[0], digest[1] };
 
-			MPInteger[] sigValues;
-            if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
-            {
-                int sigLen = sigBytes.Length;
-                if (sigLen == Ed25519.SignatureSize)
+			SignaturePacket sigPkt;
+
+			if (keyAlgorithm == PublicKeyAlgorithmTag.Ed25519 || keyAlgorithm == PublicKeyAlgorithmTag.Ed448)
+			{
+				sigPkt = new SignaturePacket(
+					version,
+					signatureType,
+					privKey.KeyId,
+					keyAlgorithm,
+					hashAlgorithm,
+					hPkts,
+					unhPkts,
+					fingerPrint,
+					version == SignaturePacket.Version6 ? salt : null,
+					privKey.GetFingerprint(),
+					sigBytes);
+			}
+			else
+			{
+				MPInteger[] sigValues;
+				if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
 				{
-					sigValues = new MPInteger[2]{
-						new MPInteger(new BigInteger(1, sigBytes,  0, 32)),
-						new MPInteger(new BigInteger(1, sigBytes, 32, 32))
-					};
+					int sigLen = sigBytes.Length;
+
+					if (sigLen == Ed25519.SignatureSize)
+					{
+						sigValues = new MPInteger[2]
+						{
+							new MPInteger(new BigInteger(1, sigBytes,  0, 32)),
+							new MPInteger(new BigInteger(1, sigBytes, 32, 32))
+						};
+					}
+					else if (sigLen == Ed448.SignatureSize)
+					{
+						sigValues = new MPInteger[2]
+						{
+							new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
+							new MPInteger(BigInteger.Zero)
+						};
+					}
+					else
+					{
+						throw new InvalidOperationException();
+					}
 				}
-                else if (sigLen == Ed448.SignatureSize)
-                {
-                    sigValues = new MPInteger[2]{
-                        new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
-                        new MPInteger(BigInteger.Zero)
-                    };
-                }
-                else
+				else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
 				{
-					throw new InvalidOperationException();
+					sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
 				}
-            }
-			else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
-			{
-                sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
-            }
-			else
-			{
-                sigValues = PgpUtilities.DsaSigToMpi(sigBytes);
+				else
+				{
+					sigValues = PgpUtilities.DsaSigToMpi(sigBytes);
+				}
+
+				sigPkt = new SignaturePacket(
+					version,
+					signatureType,
+					privKey.KeyId,
+					keyAlgorithm,
+					hashAlgorithm,
+					hPkts,
+					unhPkts,
+					fingerPrint,
+					version == SignaturePacket.Version6 ? salt : null,
+					privKey.GetFingerprint(),
+					sigValues);
             }
 
-            return new PgpSignature(
-				new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm,
-					hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
+			return new PgpSignature(sigPkt);
         }
 
 		/// <summary>Generate a certification for the passed in ID and key.</summary>
@@ -387,7 +487,7 @@ private static bool IsPacketPresent(SignatureSubpacket[] packets, SignatureSubpa
 			return false;
 		}
 
-		private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
+        private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
 		{
 			return Arrays.Prepend(packets, subpacket);
 		}
@@ -408,11 +508,26 @@ private void UpdateWithPublicKey(
 		{
 			byte[] keyBytes = GetEncodedPublicKey(key);
 
-			Update(
-				0x99,
-				(byte)(keyBytes.Length >> 8),
-				(byte)(keyBytes.Length));
-			Update(keyBytes);
-		}
+            this.Update(PgpPublicKey.FingerprintPreamble(version));
+
+            switch (version)
+            {
+                case SignaturePacket.Version4:
+                    this.Update(
+                        (byte)(keyBytes.Length >> 8),
+                        (byte)(keyBytes.Length));
+                    break;
+                case SignaturePacket.Version5:
+                case SignaturePacket.Version6:
+                    this.Update(
+                        (byte)(keyBytes.Length >> 24),
+                        (byte)(keyBytes.Length >> 16),
+                        (byte)(keyBytes.Length >> 8),
+                        (byte)(keyBytes.Length));
+                    break;
+            }
+
+            this.Update(keyBytes);
+        }
 	}
 }
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 3ce1eccb1f..ce70c015f9 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -3,7 +3,6 @@
 using Org.BouncyCastle.Crypto.Agreement;
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Crypto.Signers;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
@@ -100,6 +99,9 @@ public void Version4Ed25519LegacyPubkeySampleTest()
         [Test]
         public void Version4Ed25519LegacyCreateTest()
         {
+            // create a v4 EdDsa_Legacy Pubkey with the same key material and creation datetime as the test vector
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
+            // then check KeyId/Fingerprint
             var key = new Ed25519PublicKeyParameters(Hex.Decode("3f098994bdd916ed4053197934e4a87c80733a1280d62f8010992e43ee3b2406"));
             var pubKey = new PgpPublicKey(PublicKeyAlgorithmTag.EdDsa_Legacy, key, DateTime.Parse("2014-08-19 14:28:27Z"));
             IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
@@ -126,11 +128,12 @@ public void Version4Ed25519LegacySignatureSampleTest()
             IsEquals(signature.HashAlgorithm, HashAlgorithmTag.Sha256);
             IsEquals(signature.CreationTime.ToString("yyyyMMddHHmmss"), "20150916122453");
 
-            byte[] original = Encoding.UTF8.GetBytes("OpenPGP");
-            signature.InitVerify(pubKey);
-            signature.Update(original);
-            
-            IsTrue("Failed generated signature check against original data", signature.Verify());
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+            VerifySignature(signature, data, pubKey);
+
+            // test with wrong data, verification should fail
+            data = Encoding.UTF8.GetBytes("OpePGP");
+            VerifySignature(signature, data, pubKey, shouldFail: true);
         }
 
         [Test]
@@ -208,12 +211,16 @@ public void Version6PublicKeyCreationTest()
             IsEquals((ulong)pubKey.KeyId, 0xCB186C4F0609A697);
             IsTrue("wrong master key fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
 
-            bool signatureOk = VerifySignature(
+            VerifyEncodedSignature(
                 v6SampleCleartextSignedMessageSignature,
                 Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
                 pubKey);
 
-            IsTrue("Failed generated signature check against original data", signatureOk);
+            VerifyEncodedSignature(
+                v6SampleCleartextSignedMessageSignature,
+                Encoding.UTF8.GetBytes("wrongdata"),
+                pubKey,
+                shouldFail: true);
         }
 
         [Test]
@@ -237,8 +244,24 @@ public void Version6UnlockedSecretKeyParsingTest()
             IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
             IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
 
-            AsymmetricCipherKeyPair signingKeyPair = GetKeyPair(signingKey);
-            IsTrue("signature test failed", SignThenVerifyEd25519Test(signingKeyPair));
+            // generate and verify a v6 signature
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
+            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
+            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey("".ToCharArray());
+            spkGen.SetIssuerFingerprint(false, signingKey);
+            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+            sigGen.Update(data);
+            sigGen.SetHashedSubpackets(spkGen.Generate());
+            PgpSignature signature = sigGen.Generate();
+
+            VerifySignature(signature, data, signingKey.PublicKey);
+            VerifySignature(signature, wrongData, signingKey.PublicKey, shouldFail: true);
+
+            byte[] encodedSignature = signature.GetEncoded();
+            VerifyEncodedSignature(encodedSignature, data, signingKey.PublicKey);
+            VerifyEncodedSignature(encodedSignature, wrongData, signingKey.PublicKey, shouldFail: true);
 
             // encryption key
             PgpSecretKey encryptionKey = secretKeys[1];
@@ -263,6 +286,41 @@ public void Version6UnlockedSecretKeyParsingTest()
                 byte[] encoded = ms.ToArray();
                 IsTrue(AreEqual(encoded, v6UnlockedSecretKey));
             }
+
+            // generate and verify a v6 userid self-cert
+            string userId = "Alice <alice@example.com>";
+            string wrongUserId = "Bob <bob@example.com>";
+            sigGen.InitSign(PgpSignature.PositiveCertification, privKey, new SecureRandom());
+            signature = sigGen.GenerateCertification(userId, signingKey.PublicKey);
+            signature.InitVerify(signingKey.PublicKey);
+            if (!signature.VerifyCertification(userId, signingKey.PublicKey))
+            {
+                Fail("self-cert verification failed.");
+            }
+            signature.InitVerify(signingKey.PublicKey);
+            if (signature.VerifyCertification(wrongUserId, signingKey.PublicKey))
+            {
+                Fail("self-cert verification failed.");
+            }
+            PgpPublicKey key = PgpPublicKey.AddCertification(signingKey.PublicKey, userId, signature);
+            byte[] keyEnc = key.GetEncoded();
+            PgpPublicKeyRing tmpRing = new PgpPublicKeyRing(keyEnc);
+            key = tmpRing.GetPublicKey();
+            IsTrue(key.GetUserIds().Contains(userId));
+
+            // generate and verify a v6 cert revocation
+            sigGen.InitSign(PgpSignature.KeyRevocation, privKey, new SecureRandom());
+            signature = sigGen.GenerateCertification(signingKey.PublicKey);
+            signature.InitVerify(signingKey.PublicKey);
+            if (!signature.VerifyCertification(signingKey.PublicKey))
+            {
+                Fail("revocation verification failed.");
+            }
+            key = PgpPublicKey.AddCertification(signingKey.PublicKey, signature);
+            keyEnc = key.GetEncoded();
+            tmpRing = new PgpPublicKeyRing(keyEnc);
+            key = tmpRing.GetPublicKey();
+            IsTrue(key.IsRevoked());
         }
 
         [Test]
@@ -312,23 +370,48 @@ public void Version6SampleCleartextSignedMessageVerifySignatureTest()
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
-            bool signatureOk = VerifySignature(
+            VerifyEncodedSignature(
                 v6SampleCleartextSignedMessageSignature,
                 Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
                 pubKey);
 
-            IsTrue("Failed generated signature check against original data", signatureOk);
+            VerifyEncodedSignature(
+                v6SampleCleartextSignedMessageSignature,
+                Encoding.UTF8.GetBytes("wrongdata"),
+                pubKey,
+                shouldFail: true);
         }
 
-        private static bool VerifySignature(byte[] sigPacket, byte[] data, PgpPublicKey signer)
+        private void VerifySignature(PgpSignature signature, byte[] data, PgpPublicKey signer, bool shouldFail = false)
+        {
+            IsEquals(signature.KeyAlgorithm, signer.Algorithm);
+            // the version of the signature is bound to the version of the signing key
+            IsEquals(signature.Version, signer.Version);
+
+            if (signature.KeyId != 0)
+            {
+                IsEquals(signature.KeyId, signer.KeyId);
+            }
+            byte[] issuerFpt = signature.GetIssuerFingerprint();
+            if (issuerFpt != null)
+            {
+                IsTrue(AreEqual(issuerFpt, signer.GetFingerprint()));
+            }
+
+            signature.InitVerify(signer);
+            signature.Update(data);
+
+            bool result = signature.Verify() != shouldFail;
+            IsTrue("signature test failed", result);
+        }
+
+        private void VerifyEncodedSignature(byte[] sigPacket, byte[] data, PgpPublicKey signer, bool shouldFail = false)
         {
             PgpObjectFactory factory = new PgpObjectFactory(sigPacket);
             PgpSignatureList sigList = (PgpSignatureList)factory.NextPgpObject();
             PgpSignature signature = sigList[0];
 
-            signature.InitVerify(signer);
-            signature.Update(data);
-            return signature.Verify();
+            VerifySignature(signature, data, signer, shouldFail);
         }
 
         private static AsymmetricCipherKeyPair GetKeyPair(PgpSecretKey secretKey, string password = "")
@@ -338,20 +421,6 @@ private static AsymmetricCipherKeyPair GetKeyPair(PgpSecretKey secretKey, string
                 secretKey.ExtractPrivateKey(password.ToCharArray()).Key);
         }
 
-        private static bool SignThenVerifyEd25519Test(AsymmetricCipherKeyPair signingKeyPair)
-        {
-            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
-
-            ISigner signer = new Ed25519Signer();
-            signer.Init(true, signingKeyPair.Private);
-            signer.BlockUpdate(data, 0, data.Length);
-            byte[] signature = signer.GenerateSignature();
-
-            signer.Init(false, signingKeyPair.Public);
-            signer.BlockUpdate(data, 0, data.Length);
-            return signer.VerifySignature(signature);
-        }
-
         private static bool EncryptThenDecryptX25519Test(AsymmetricCipherKeyPair alice, AsymmetricCipherKeyPair bob)
         {
             X25519Agreement agreeA = new X25519Agreement();

From ceaa293ba2590916948a8600cc2b80d2472dc17b Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 29 Feb 2024 17:48:02 +0100
Subject: [PATCH 13/37] test v6 inline signature generate and verify

---
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 106 +++++++++++++++++-
 1 file changed, 103 insertions(+), 3 deletions(-)

diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index ce70c015f9..5633d9a637 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -81,6 +81,18 @@ public class PgpCryptoRefreshTest
             "/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr" +
             "NK2ay45cX1IVAQ==");
 
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag
+        private readonly byte[] v6SampleInlineSignedMessage = Base64.Decode(
+            "xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk" +
+            "1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv" +
+            "bSB0aGUgZ3JvY2VyeSBzdG9yZToKCi0gdG9mdQotIHZlZ2V0YWJsZXMKLSBub29k" +
+            "bGVzCsKYBgEbCgAAACkFgmOYo2MiIQbLGGxPBgmml+TVLfpscisMHx4nwYpWcI9l" +
+            "JewnutmsyQAAAABpNiB2SV9QIYiQ9/Xi7jwYIlFPcFAPVR2G5ckh5ATjSlP7rCfQ" +
+            "b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj" +
+            "FtCgazStmsuOXF9SFQE=");
+
+        private readonly char[] emptyPassphrase = Array.Empty<char>();
+
         [Test]
         public void Version4Ed25519LegacyPubkeySampleTest()
         {
@@ -120,7 +132,7 @@ public void Version4Ed25519LegacySignatureSampleTest()
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
             PgpObjectFactory factory = new PgpObjectFactory(v4Ed25519LegacySignatureSample);
-            PgpSignatureList sigList = (PgpSignatureList)factory.NextPgpObject();
+            PgpSignatureList sigList = factory.NextPgpObject() as PgpSignatureList;
             PgpSignature signature = sigList[0];
 
             IsEquals(signature.KeyId, pubKey.KeyId);
@@ -249,7 +261,7 @@ public void Version6UnlockedSecretKeyParsingTest()
             byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
             PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
             PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
-            PgpPrivateKey privKey = signingKey.ExtractPrivateKey("".ToCharArray());
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(emptyPassphrase);
             spkGen.SetIssuerFingerprint(false, signingKey);
             sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
             sigGen.Update(data);
@@ -382,6 +394,92 @@ public void Version6SampleCleartextSignedMessageVerifySignatureTest()
                 shouldFail: true);
         }
 
+        [Test]
+        public void Version6SampleInlineSignedMessageVerifySignatureTest()
+        {
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
+            PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+            VerifyInlineSignature(v6SampleInlineSignedMessage, pubKey);
+        }
+
+        [Test]
+        public void Version6GenerateAndVerifyInlineSignatureTest()
+        {
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6UnlockedSecretKey);
+            PgpSecretKey signingKey = secretKeyRing.GetSecretKey();
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(emptyPassphrase);
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP\nOpenPGP");
+            byte[] inlineSignatureMessage;
+
+            using (MemoryStream ms = new MemoryStream())
+            {
+                using (BcpgOutputStream bcOut = new BcpgOutputStream(ms, newFormatOnly: true))
+                {
+                    PgpSignatureGenerator sGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha384);
+                    sGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+                    sGen.GenerateOnePassVersion(false).Encode(bcOut);
+
+                    PgpLiteralDataGenerator lGen = new PgpLiteralDataGenerator();
+                    DateTime modificationTime = DateTime.UtcNow;
+                    using (var lOut = lGen.Open(
+                        new UncloseableStream(bcOut),
+                        PgpLiteralData.Utf8,
+                        "_CONSOLE",
+                        data.Length,
+                        modificationTime))
+                    {
+                        lOut.Write(data, 0, data.Length);
+                        sGen.Update(data);
+                    }
+
+                    sGen.Generate().Encode(bcOut);
+                }
+
+                inlineSignatureMessage = ms.ToArray();
+            }
+
+            VerifyInlineSignature(inlineSignatureMessage, signingKey.PublicKey);
+            // corrupt data
+            inlineSignatureMessage[88] = 80;
+            VerifyInlineSignature(inlineSignatureMessage, signingKey.PublicKey, shouldFail: true);
+        }
+
+        private void VerifyInlineSignature(byte[] message, PgpPublicKey signer, bool shouldFail = false)
+        {
+            byte[] data;
+            PgpObjectFactory factory = new PgpObjectFactory(message);
+
+            PgpOnePassSignatureList p1 = factory.NextPgpObject() as PgpOnePassSignatureList;
+            PgpOnePassSignature ops = p1[0];
+
+            PgpLiteralData p2 = factory.NextPgpObject() as PgpLiteralData;
+            Stream dIn = p2.GetInputStream();
+
+            ops.InitVerify(signer);
+
+            using (MemoryStream ms = new MemoryStream())
+            {
+                byte[] buffer = new byte[30];
+                int bytesRead;
+                while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+                {
+                    ops.Update(buffer, 0, bytesRead);
+                    ms.Write(buffer, 0, bytesRead);
+                }
+
+                data = ms.ToArray();
+            }
+            PgpSignatureList p3 = factory.NextPgpObject() as PgpSignatureList;
+            PgpSignature sig = p3[0];
+
+            bool result = ops.Verify(sig) != shouldFail;
+            IsTrue("signature test failed", result);
+
+            VerifySignature(sig, data, signer, shouldFail);
+        }
+
         private void VerifySignature(PgpSignature signature, byte[] data, PgpPublicKey signer, bool shouldFail = false)
         {
             IsEquals(signature.KeyAlgorithm, signer.Algorithm);
@@ -408,7 +506,7 @@ private void VerifySignature(PgpSignature signature, byte[] data, PgpPublicKey s
         private void VerifyEncodedSignature(byte[] sigPacket, byte[] data, PgpPublicKey signer, bool shouldFail = false)
         {
             PgpObjectFactory factory = new PgpObjectFactory(sigPacket);
-            PgpSignatureList sigList = (PgpSignatureList)factory.NextPgpObject();
+            PgpSignatureList sigList = factory.NextPgpObject() as PgpSignatureList;
             PgpSignature signature = sigList[0];
 
             VerifySignature(signature, data, signer, shouldFail);
@@ -447,6 +545,8 @@ public override void PerformTest()
             Version6UnlockedSecretKeyParsingTest();
             Version6LockedSecretKeyParsingTest();
             Version6SampleCleartextSignedMessageVerifySignatureTest();
+            Version6SampleInlineSignedMessageVerifySignatureTest();
+            Version6GenerateAndVerifyInlineSignatureTest();
         }
     }
 }
\ No newline at end of file

From f4ac85cb30215d43b6996e999c1213dd8e77c2f3 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 29 Feb 2024 21:08:19 +0100
Subject: [PATCH 14/37] doc comments

---
 crypto/src/bcpg/AeadUtils.cs         | 47 ++++++++--------
 crypto/src/bcpg/OctetArrayBCPGKey.cs |  7 +--
 crypto/src/bcpg/SecretKeyPacket.cs   | 83 ++++++++++++++--------------
 3 files changed, 66 insertions(+), 71 deletions(-)

diff --git a/crypto/src/bcpg/AeadUtils.cs b/crypto/src/bcpg/AeadUtils.cs
index 922a4860ca..e7e63c74ee 100644
--- a/crypto/src/bcpg/AeadUtils.cs
+++ b/crypto/src/bcpg/AeadUtils.cs
@@ -7,12 +7,12 @@ namespace Org.BouncyCastle.Bcpg
 {
     public sealed class AeadUtils
     {
-        /**
-         * Return the length of the IV used by the given AEAD algorithm in octets.
-         * 
-         * @param aeadAlgorithmTag AEAD algorithm identifier
-         * @return length of the IV
-        */
+        /// <summary>
+        /// Return the length of the IV used by the given AEAD algorithm in octets.
+        /// </summary>
+        /// <param name="aeadAlgorithmTag">AEAD algorithm identifier</param>
+        /// <returns>length of the IV</returns>
+        /// <exception cref="ArgumentException">Thrown when aeadAlgorithmTag is unknown/invalid</exception>
         public static int GetIVLength(AeadAlgorithmTag aeadAlgorithmTag)
         {
             switch (aeadAlgorithmTag)
@@ -28,12 +28,12 @@ public static int GetIVLength(AeadAlgorithmTag aeadAlgorithmTag)
             }
         }
 
-        /**
-         * Return the length of the authentication tag used by the given AEAD algorithm in octets.
-         *
-         * @param aeadAlgorithmTag AEAD algorithm identifier
-         * @return length of the auth tag
-         */
+        /// <summary>
+        /// Return the length of the authentication tag used by the given AEAD algorithm in octets.
+        /// </summary>
+        /// <param name="aeadAlgorithmTag">AEAD algorithm identifier</param>
+        /// <returns>length of the auth tag</returns>
+        /// <exception cref="ArgumentException">Thrown when aeadAlgorithmTag is unknown/invalid</exception>
         public static int GetAuthTagLength(AeadAlgorithmTag aeadAlgorithmTag)
         {
             switch (aeadAlgorithmTag)
@@ -47,18 +47,17 @@ public static int GetAuthTagLength(AeadAlgorithmTag aeadAlgorithmTag)
             }
         }
 
-        /**
-         * Split a given byte array containing <pre>m</pre> bytes of key and <pre>n-8</pre> bytes of IV into
-         * two separate byte arrays.
-         * <pre>m</pre> is the key length of the cipher algorithm, while <pre>n</pre> is the IV length of the AEAD algorithm.
-         * Note, that the IV is filled with <pre>n-8</pre> bytes only, the remainder is left as 0s.
-         * Return an array of both arrays with the key and index 0 and the IV at index 1.
-         *
-         * @param messageKeyAndIv <pre>m+n-8</pre> bytes of concatenated message key and IV
-         * @param cipherAlgo      symmetric cipher algorithm
-         * @param aeadAlgo        AEAD algorithm
-         * @return array of arrays containing message key and IV
-         */
+        /// <summary>
+        /// Split a given byte array containing m bytes of key and n-8 bytes of IV into
+        /// two separate byte arrays.
+        /// m is the key length of the cipher algorithm, while n is the IV length of the AEAD algorithm.
+        /// Note, that the IV is filled with <pre>n-8</pre> bytes only, the remainder is left as 0s.
+        /// Return an array of both arrays with the key and index 0 and the IV at index 1.
+        /// </summary>
+        /// <param name="messageKeyAndIv">m+n-8 bytes of concatenated message key and IV</param>
+        /// <param name="cipherAlgo">symmetric cipher algorithm</param>
+        /// <param name="aeadAlgo">AEAD algorithm</param>
+        /// <returns>array of arrays containing message key and IV</returns>
         public static byte[][] SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgorithmTag cipherAlgo, AeadAlgorithmTag aeadAlgo)
         {
             int keyLen = PgpUtilities.GetKeySizeInOctets(cipherAlgo);
diff --git a/crypto/src/bcpg/OctetArrayBCPGKey.cs b/crypto/src/bcpg/OctetArrayBCPGKey.cs
index 1bf09958ec..73d0d34a4f 100644
--- a/crypto/src/bcpg/OctetArrayBCPGKey.cs
+++ b/crypto/src/bcpg/OctetArrayBCPGKey.cs
@@ -3,10 +3,9 @@
 
 namespace Org.BouncyCastle.Bcpg
 {
-    /**
-     * Public/Secret BcpgKey which is encoded as an array of octets rather than an MPI
-     * 
-     */
+    /// <summary>
+    /// Public/Secret BcpgKey which is encoded as an array of octets rather than an MPI
+    /// </summary>
     public abstract class OctetArrayBcpgKey
         : BcpgObject, IBcpgKey
     {
diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index d237ef4694..985fc28cf9 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -1,7 +1,6 @@
+using Org.BouncyCastle.Utilities;
 using System;
 using System.IO;
-using Org.BouncyCastle.Pqc.Crypto.SphincsPlus;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg
 {
@@ -10,53 +9,51 @@ public class SecretKeyPacket
         : ContainedPacket //, PublicKeyAlgorithmTag
     {
 
-        /**
-         * Unprotected.
-         */
+        /// <summary>
+        /// Unprotected secret key
+        /// </summary>
         public const int UsageNone = 0x00;
 
-        /**
-         * Malleable CFB.
-         * Malleable-CFB-encrypted keys are vulnerable to corruption attacks
-         * that can cause leakage of secret data when the secret key is used.
-         *
-         * @see <a href="https://eprint.iacr.org/2002/076">
-         * Kl�ma, V. and T. Rosa,
-         * "Attack on Private Signature Keys of the OpenPGP Format,
-         * PGP(TM) Programs and Other Applications Compatible with OpenPGP"</a>
-         * @see <a href="https://www.kopenpgp.com/">
-         * Bruseghini, L., Paterson, K. G., and D. Huigens,
-         * "Victory by KO: Attacking OpenPGP Using Key Overwriting"</a>
-         * @deprecated Use of MalleableCFB is deprecated.
-         * For v4 keys, use {@link #USAGE_SHA1} instead.
-         * For v6 keys use {@link #USAGE_AEAD} instead.
-         */
+        /// <summary>
+        /// Malleable CFB.
+        /// Malleable-CFB-encrypted keys are vulnerable to corruption attacks
+        /// that can cause leakage of secret data when the secret key is used.
+        /// 
+        /// <see href="https://eprint.iacr.org/2002/076">
+        /// Kl�ma, V.and T. Rosa,
+        /// "Attack on Private Signature Keys of the OpenPGP Format,
+        /// PGP(TM) Programs and Other Applications Compatible with OpenPGP"</see>
+        /// <see href="https://www.kopenpgp.com/">
+        /// Bruseghini, L., Paterson, K.G., and D. Huigens,
+        /// "Victory by KO: Attacking OpenPGP Using Key Overwriting"</see>
+        /// </summary>
         public const int UsageChecksum = 0xff;
 
-        /**
-         * CFB.
-         * CFB-encrypted keys are vulnerable to corruption attacks that can
-         * cause leakage of secret data when the secret key is use.
-         *
-         * @see <a href="https://eprint.iacr.org/2002/076">
-         * Kl�ma, V. and T. Rosa,
-         * "Attack on Private Signature Keys of the OpenPGP Format,
-         * PGP(TM) Programs and Other Applications Compatible with OpenPGP"</a>
-         * @see <a href="https://www.kopenpgp.com/">
-         * Bruseghini, L., Paterson, K. G., and D. Huigens,
-         * "Victory by KO: Attacking OpenPGP Using Key Overwriting"</a>
-         */
+
+        /// <summary>
+        /// CFB.
+        /// CFB-encrypted keys are vulnerable to corruption attacks that can
+        /// cause leakage of secret data when the secret key is use.
+        /// 
+        /// <see href = "https://eprint.iacr.org/2002/076" >
+        /// Kl�ma, V. and T.Rosa,
+        /// "Attack on Private Signature Keys of the OpenPGP Format,
+        /// PGP(TM) Programs and Other Applications Compatible with OpenPGP"</see>
+        /// <see href = "https://www.kopenpgp.com/" >
+        /// Bruseghini, L., Paterson, K.G., and D. Huigens,
+        /// "Victory by KO: Attacking OpenPGP Using Key Overwriting"</see>
+        /// </summary>
         public const int UsageSha1 = 0xfe;
 
-        /**
-         * AEAD.
-         * This usage protects against above-mentioned attacks.
-         * Passphrase-protected secret key material in a v6 Secret Key or
-         * v6 Secret Subkey packet SHOULD be protected with AEAD encryption
-         * unless it will be transferred to an implementation that is known
-         * to not support AEAD.
-         * Users should migrate to AEAD with all due speed.
-         */
+        /// <summary>
+        /// AEAD.
+        /// This usage protects against corruption attacks.
+        /// Passphrase-protected secret key material in a v6 Secret Key or
+        /// v6 Secret Subkey packet SHOULD be protected with AEAD encryption
+        /// unless it will be transferred to an implementation that is known
+        /// to not support AEAD.
+        /// Users should migrate to AEAD with all due speed.
+        /// </summary>
         public const int UsageAead = 0xfd;
 
 

From a8ce4b0b1f3276a6bf543d7b55593a3627359ee2 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sat, 2 Mar 2024 15:51:33 +0100
Subject: [PATCH 15/37] v5 keys and signature verification, tests on multiple
 v4+v6 signatures

---
 crypto/src/bcpg/SignaturePacket.cs            | 129 +++--
 crypto/src/openpgp/PgpLiteralData.cs          |  38 ++
 crypto/src/openpgp/PgpOnePassSignature.cs     |  25 +-
 crypto/src/openpgp/PgpSignature.cs            |  18 +-
 .../test/PgpInteroperabilityTestSuite.cs      | 448 ++++++++++++++++++
 5 files changed, 605 insertions(+), 53 deletions(-)
 create mode 100644 crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs

diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 2c56b05bff..5af8843876 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -41,7 +41,7 @@ public class SignaturePacket
 
         private void CheckIssuerSubpacket(SignatureSubpacket p)
         {
-            if (p is IssuerFingerprint issuerFingerprintPkt && !(issuerFingerprint is null))
+            if (p is IssuerFingerprint issuerFingerprintPkt && issuerFingerprint is null)
             {
                 issuerFingerprint = issuerFingerprintPkt.GetFingerprint();
 
@@ -406,13 +406,19 @@ public byte[] GetFingerprint()
             return Arrays.Clone(fingerprint);
         }
 
-		/**
+        /**
         * return the signature trailer that must be included with the data
         * to reconstruct the signature
         *
         * @return byte[]
         */
+
         public byte[] GetSignatureTrailer()
+        {
+            return GetSignatureTrailer(Array.Empty<byte>());
+        }
+
+        public byte[] GetSignatureTrailer(byte[] additionalMetadata)
         {
 			if (version == Version3)
             {
@@ -424,56 +430,93 @@ public byte[] GetSignatureTrailer()
                 return trailer;
             }
 
-            MemoryStream sOut = new MemoryStream();
-
-			sOut.WriteByte((byte)Version);
-            sOut.WriteByte((byte)SignatureType);
-            sOut.WriteByte((byte)KeyAlgorithm);
-            sOut.WriteByte((byte)HashAlgorithm);
-
-            // Mark position an reserve two bytes (version4) or four bytes (version6)
-            // for length
-            long lengthPosition = sOut.Position;
-            if (version == Version6)
+            using (MemoryStream sOut = new MemoryStream())
             {
+                sOut.WriteByte((byte)Version);
+                sOut.WriteByte((byte)SignatureType);
+                sOut.WriteByte((byte)KeyAlgorithm);
+                sOut.WriteByte((byte)HashAlgorithm);
+
+                // Mark position an reserve two bytes (version4) or four bytes (version6)
+                // for length
+                long lengthPosition = sOut.Position;
+                if (version == Version6)
+                {
+                    sOut.WriteByte(0x00);
+                    sOut.WriteByte(0x00);
+                }
                 sOut.WriteByte(0x00);
                 sOut.WriteByte(0x00);
-            }
-            sOut.WriteByte(0x00);
-            sOut.WriteByte(0x00);
 
-            SignatureSubpacket[] hashed = GetHashedSubPackets();
-			for (int i = 0; i != hashed.Length; i++)
-            {
-                hashed[i].Encode(sOut);
-            }
+                SignatureSubpacket[] hashed = GetHashedSubPackets();
+                for (int i = 0; i != hashed.Length; i++)
+                {
+                    hashed[i].Encode(sOut);
+                }
 
-            ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2);
-            if (version == Version6)
-            {
-                dataLength -= 2;
-            }
+                ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2);
+                if (version == Version6)
+                {
+                    dataLength -= 2;
+                }
 
-            uint hDataLength = Convert.ToUInt32(sOut.Position);
+                uint hDataLength = Convert.ToUInt32(sOut.Position);
 
-			sOut.WriteByte((byte)Version);
-            sOut.WriteByte(0xff);
-            sOut.WriteByte((byte)(hDataLength >> 24));
-            sOut.WriteByte((byte)(hDataLength >> 16));
-            sOut.WriteByte((byte)(hDataLength >>  8));
-            sOut.WriteByte((byte)(hDataLength      ));
+                // Additional metadata for v5 signatures
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#name-computing-signatures
+                // Only for document signatures (type 0x00 or 0x01) the following three data items are
+                // hashed here:
+                //   * the one-octet content format,
+                //   * the file name as a string (one octet length, followed by the file name)
+                //   * a four-octet number that indicates a date,
+                // The three data items hashed for document signatures need to mirror the values of the
+                // Literal Data packet.
+                // For detached and cleartext signatures 6 zero bytes are hashed instead.
 
-            // Reset position and fill in length
-            sOut.Position = lengthPosition;
-            if (version == Version6)
-            {
-                sOut.WriteByte((byte)(dataLength >> 24));
-                sOut.WriteByte((byte)(dataLength >> 16));
-            }
-            sOut.WriteByte((byte)(dataLength >> 8));
-            sOut.WriteByte((byte)(dataLength     ));
+                if (version == Version5 && (signatureType == 0x00 || signatureType == 0x01))
+                {
+                    if (additionalMetadata != null && additionalMetadata.Length > 0)
+                    {
+                        sOut.Write(additionalMetadata, 0, additionalMetadata.Length);
+                    }
+                    else
+                    {
+                        sOut.WriteByte(0x00);
+                        sOut.WriteByte(0x00);
+                        sOut.WriteByte(0x00);
+                        sOut.WriteByte(0x00);
+                        sOut.WriteByte(0x00);
+                        sOut.WriteByte(0x00);
+                    }
+                }
+
+                sOut.WriteByte((byte)Version);
+                sOut.WriteByte(0xff);
+
+                if (version == Version5)
+                {
+                    sOut.WriteByte((byte)((ulong)hDataLength >> 56));
+                    sOut.WriteByte((byte)((ulong)hDataLength >> 48));
+                    sOut.WriteByte((byte)((ulong)hDataLength >> 40));
+                    sOut.WriteByte((byte)((ulong)hDataLength >> 32));
+                }
+                sOut.WriteByte((byte)(hDataLength >> 24));
+                sOut.WriteByte((byte)(hDataLength >> 16));
+                sOut.WriteByte((byte)(hDataLength >>  8));
+                sOut.WriteByte((byte)(hDataLength      ));
 
-            return sOut.ToArray();
+                // Reset position and fill in length
+                sOut.Position = lengthPosition;
+                if (version == Version6)
+                {
+                    sOut.WriteByte((byte)(dataLength >> 24));
+                    sOut.WriteByte((byte)(dataLength >> 16));
+                }
+                sOut.WriteByte((byte)(dataLength >> 8));
+                sOut.WriteByte((byte)(dataLength     ));
+
+                return sOut.ToArray();
+            }
         }
 
 		public PublicKeyAlgorithmTag KeyAlgorithm => keyAlgorithm;
diff --git a/crypto/src/openpgp/PgpLiteralData.cs b/crypto/src/openpgp/PgpLiteralData.cs
index 92fafe657a..f9abb5ed48 100644
--- a/crypto/src/openpgp/PgpLiteralData.cs
+++ b/crypto/src/openpgp/PgpLiteralData.cs
@@ -62,5 +62,43 @@ public Stream GetDataStream()
         {
             return GetInputStream();
         }
+
+        /// <summary>
+        /// Additional metadata for v5 signatures
+        /// https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#name-computing-signatures
+        /// Only for document signatures (type 0x00 or 0x01) the following three data items are hashed:
+        ///   * the one-octet content format,
+        ///   * the file name as a string (one octet length, followed by the file name)
+        ///   * a four-octet number that indicates a date,
+        /// The three data items hashed for document signatures need to mirror the values of the
+        /// Literal Data packet.
+        /// For detached and cleartext signatures 6 zero bytes are hashed instead.
+        /// </summary>
+        /// <param name="sigVersion">Signature version</param>
+        /// <returns></returns>
+        public byte[] GetMetadata(int sigVersion)
+        {
+            // only v5 signatures requires additional metadata
+            if (sigVersion != SignaturePacket.Version5)
+            {
+                return Array.Empty<byte>();
+            }
+
+            using (var ms = new MemoryStream())
+            {
+                byte[] rawFileName = data.GetRawFileName();
+                long modTime = data.ModificationTime / 1000;
+                ms.WriteByte((byte)Format);
+                ms.WriteByte((byte)rawFileName.Length);
+                ms.Write(rawFileName, 0, rawFileName.Length);
+
+                ms.WriteByte((byte)(modTime >> 24));
+                ms.WriteByte((byte)(modTime >> 16));
+                ms.WriteByte((byte)(modTime >> 8));
+                ms.WriteByte((byte)modTime);
+
+                return ms.ToArray();
+            }
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index cbc889ca03..704cc7bd9e 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -1,14 +1,11 @@
-using System;
-using System.IO;
-
 using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities;
+using System;
+using System.IO;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>A one pass signature object.</remarks>
+    /// <remarks>A one pass signature object.</remarks>
     public class PgpOnePassSignature
     {
         private static OnePassSignaturePacket Cast(Packet packet)
@@ -151,6 +148,11 @@ public void Update(ReadOnlySpan<byte> input)
 
         /// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
         public bool Verify(PgpSignature pgpSig)
+        {
+            return Verify(pgpSig, Array.Empty<byte>());
+        }
+
+        public bool Verify(PgpSignature pgpSig, byte[] additionalMetadata)
         {
             // the versions of the Signature and the One-Pass Signature must be aligned as specified in
             // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#signed-message-versions
@@ -162,8 +164,8 @@ public bool Verify(PgpSignature pgpSig)
             {
                 return false;
             }
-
-            byte[] trailer = pgpSig.GetSignatureTrailer();
+            // Additional metadata for v5 signatures
+            byte[] trailer = pgpSig.GetSignatureTrailer(additionalMetadata);
 
 			sig.BlockUpdate(trailer, 0, trailer.Length);
 
@@ -175,7 +177,12 @@ public long KeyId
 			get { return sigPack.KeyId; }
         }
 
-		public int SignatureType
+        public int Version
+        {
+            get { return sigPack.Version; }
+        }
+
+        public int SignatureType
         {
             get { return sigPack.SignatureType; }
         }
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index 32126a46a8..b543bb9d2e 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -213,7 +213,17 @@ public bool Verify()
 			return sig.VerifySignature(GetSignature());
         }
 
-		private void UpdateWithIdData(int header, byte[] idBytes)
+        public bool Verify(byte[] additionalMetadata)
+        {
+            // Additional metadata for v5 signatures
+            byte[] trailer = sigPck.GetSignatureTrailer(additionalMetadata);
+
+            sig.BlockUpdate(trailer, 0, trailer.Length);
+
+            return sig.VerifySignature(GetSignature());
+        }
+
+        private void UpdateWithIdData(int header, byte[] idBytes)
 		{
 			this.Update(
 				(byte) header,
@@ -359,6 +369,12 @@ public byte[] GetSignatureTrailer()
             return sigPck.GetSignatureTrailer();
         }
 
+        public byte[] GetSignatureTrailer(byte[] additionalMetadata)
+        {
+            // Additional metadata for v5 signatures
+            return sigPck.GetSignatureTrailer(additionalMetadata);
+        }
+
         public byte[] GetSignatureSalt()
         {
             return sigPck.GetSignatureSalt();
diff --git a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
new file mode 100644
index 0000000000..3513047523
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
@@ -0,0 +1,448 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class PgpInteroperabilityTestSuite
+        : SimpleTest
+    {
+        // v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+        // https://www.ietf.org/archive/id/draft-bre-openpgp-samples-01.html#name-alices-ed25519-samples
+        private static readonly byte[] alicePubkey = Base64.Decode(
+            "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U" +
+            "b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE" +
+            "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy" +
+            "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO" +
+            "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4" +
+            "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s" +
+            "E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb" +
+            "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn" +
+            "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=");
+
+        private static readonly byte[] aliceSecretkey = Base64.Decode(
+            "lFgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U" +
+            "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RtCZBbGlj" +
+            "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPoiQBBMWCAA4AhsDBQsJ" +
+            "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l" +
+            "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf" +
+            "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICnF0EXEcE6RIKKwYB" +
+            "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA" +
+            "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK6IeAQYFggAIBYhBOuF" +
+            "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM" +
+            "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb" +
+            "Pnn+We1aTBhaGa86AQ==");
+
+        // v6 keys from crypto-refresh
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
+        private static readonly byte[] v6Certificate = Base64.Decode(
+            "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf" +
+            "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy" +
+            "KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw" +
+            "gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE" +
+            "QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn" +
+            "+eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh" +
+            "BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8" +
+            "j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805" +
+            "I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==");
+
+        private static readonly byte[] v6UnlockedSecretKey = Base64.Decode(
+            "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB" +
+            "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ" +
+            "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+            "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh" +
+            "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe" +
+            "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/" +
+            "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG" +
+            "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+            "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE" +
+            "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr" +
+            "k0mXubZvyl4GBg==");
+
+        // v5 keys from "OpenPGP interoperability test suite"
+        // https://tests.sequoia-pgp.org/#Inline_Sign-Verify_roundtrip_with_key__Emma_
+        private static readonly byte[] v5Certificate = Base64.Decode(
+            "mDcFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd" +
+            "fj75iux+my8QtBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0" +
+            "e8mHJGQCX5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyIC" +
+            "AQYVCgkICwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3w" +
+            "wJAXRJy9M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2Bbg8BVyR" +
+            "9OQSAAAAMgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0" +
+            "YvYWWAoDAQgHiHoFGBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACR" +
+            "WVabVAUCXJH05AIbDAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijho" +
+            "b2U5AQC+RtOHCHx7TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==");
+
+        private static readonly byte[] v5UnlockedSecretKey = Base64.Decode(
+            "lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd" +
+            "fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA" +
+            "Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC" +
+            "X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI" +
+            "CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9" +
+            "M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA" +
+            "MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD" +
+            "AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF" +
+            "GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb" +
+            "DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7" +
+            "TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==");
+
+        private static readonly char[] emptyPassphrase = Array.Empty<char>();
+
+        private static PgpSignatureGenerator CreateAndInitPgpSignatureGenerator(PgpSecretKey signingKey, HashAlgorithmTag hashAlgo, char[] passphrase)
+        {
+            PgpSignatureGenerator generator = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, hashAlgo);
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(passphrase);
+            generator.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+
+            return generator;
+        }
+
+        private static PgpPublicKeyRingBundle CreateBundle(params PgpPublicKeyRing[] keyrings)
+        {
+            using (MemoryStream ms = new MemoryStream())
+            {
+                foreach (var keyring in keyrings)
+                {
+                    keyring.Encode(ms);
+                }
+                return new PgpPublicKeyRingBundle(ms.ToArray());
+            }
+        }
+
+        private void VerifyMultipleInlineSignaturesTest(byte[] message, PgpPublicKeyRingBundle bundle, bool shouldFail = false)
+        {
+            PgpObjectFactory factory = new PgpObjectFactory(message);
+            PgpOnePassSignatureList opss = factory.NextPgpObject() as PgpOnePassSignatureList;
+            for (int i = 0; i < opss.Count; i++)
+            {
+                PgpOnePassSignature ops = opss[i];
+                ops.InitVerify(bundle.GetPublicKey(ops.KeyId));
+            }
+
+            PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+            using (Stream dIn = lit.GetInputStream())
+            {
+
+                byte[] buffer = new byte[30];
+                int bytesRead;
+                while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+                {
+                    for (int i = 0; i < opss.Count; i++)
+                    {
+                        opss[i].Update(buffer, 0, bytesRead);
+                    }
+                }
+            }
+
+            PgpSignatureList sigs = factory.NextPgpObject() as PgpSignatureList;
+            IsEquals(opss.Count, sigs.Count);
+            int sigCount = sigs.Count - 1;
+            for (int i = 0; i <= sigCount; i++)
+            {
+                IsTrue(shouldFail != opss[i].Verify(sigs[sigCount - i]));
+            }
+        }
+
+        [Test]
+        public void MultipleInlineSignatureTest()
+        {
+            // Verify Inline Signature with multiple keys:
+            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            // https://tests.sequoia-pgp.org/#Inline_Sign_with_minimal_key_from_RFC9760_and_key__Alice___verify_with_key_from_RFC9760
+
+            // inline signed message generated by GopenPGP 3.0.0-alpha
+            byte[] message = Base64.Decode(
+                "xEYGAAobIPdza3bN03j7U7LE/Q/46kHCmsfVx2UmTPsNpUk/V/UWyxhsTwYJppfk" +
+                "1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkAxA0DAAoW8jFVDE9H444ByxRiAAAAAABI" +
+                "ZWxsbyBXb3JsZCA6KcJ1BAAWCgAnBQJl4HbKCRDyMVUMT0fjjhYhBOuFu1+jOnXh" +
+                "XpROY/IxVQxPR+OOAACKGAEAsQpg3dNdO4C9eMGn1jvVTjP0r2welMFD68dFU5d8" +
+                "nq8A+gNFdJbX0PP0vNx/kIxpilbdssnF+a04CdVpAkwXmaYPwpgGABsKAAAAKQUC" +
+                "ZeB2yiKhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAACB3IPdz" +
+                "a3bN03j7U7LE/Q/46kHCmsfVx2UmTPsNpUk/V/UWJsjxFqBQXqDFAaOjiv8oabeX" +
+                "qvELkq1bKLb9fJ+ASfZW9FyI1ORHdCrI5zEnpfrFe4Id+xg9N39MTGq+OoPeDA==");
+
+            PgpPublicKeyRingBundle bundle = CreateBundle(
+                new PgpPublicKeyRing(alicePubkey),
+                new PgpPublicKeyRing(v6Certificate));
+
+            VerifyMultipleInlineSignaturesTest(message, bundle);
+
+            // inline signed message generated by PGPy 0.6.0+dkg-crypto-refresh
+            message = Base64.Decode(
+                "xA0DAAoW8jFVDE9H444AxEYGAAobIFWUOmg2wsfVON4qIM1sWUPd9223ANjaMnHT" +
+                "Mvad9EfVyxhsTwYJppfk1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkByxRiAGXgds5I" +
+                "ZWxsbyBXb3JsZCA6KcKYBgAbCgAAACkFgmXgds4iIQbLGGxPBgmml+TVLfpscisM" +
+                "Hx4nwYpWcI9lJewnutmsyQAAAACSWCBVlDpoNsLH1TjeKiDNbFlD3fdttwDY2jJx" +
+                "0zL2nfRH1aouTY4WN/3DFsfP8yFg/BE7Ssaikt7bbXtBSH/AldOtyM1myiFsP+yx" +
+                "8Img2A7eq9+wKTLjhPHl7zSh7y9KEATCdQQAFgoAHQWCZeB2zhYhBOuFu1+jOnXh" +
+                "XpROY/IxVQxPR+OOAAoJEPIxVQxPR+OOgDcBAOz0kSpV4/F9Exxdq6oYlHZdsX5U" +
+                "n9QpjmJVjo7bsMGDAQCd3PA5joXmfoKQhtQT5Qm1dhjfv/c89oPzdjQYmVLnCg==");
+
+            VerifyMultipleInlineSignaturesTest(message, bundle);
+        }
+
+        [Test]
+        public void GenerateAndVerifyMultipleInlineSignatureTest()
+        {
+            // Inline Sign-Verify roundtrip test with multiple keys:
+            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
+            byte[] message;
+
+            PgpSecretKey[] signingKeys = new PgpSecretKey[] {
+                new PgpSecretKeyRing(v6UnlockedSecretKey).GetSecretKey(),
+                new PgpSecretKeyRing(aliceSecretkey).GetSecretKey()
+            };
+
+            PgpSignatureGenerator[] generators = new PgpSignatureGenerator[] {
+                CreateAndInitPgpSignatureGenerator(signingKeys[0], HashAlgorithmTag.Sha384, emptyPassphrase),
+                CreateAndInitPgpSignatureGenerator(signingKeys[1], HashAlgorithmTag.Sha256, emptyPassphrase)
+            };
+
+            using (MemoryStream ms = new MemoryStream())
+            {
+                using (BcpgOutputStream bcOut = new BcpgOutputStream(ms, newFormatOnly: true))
+                {
+                    int sigCount  = generators.Length;
+                    int count = 1;
+                    foreach (PgpSignatureGenerator generator in generators)
+                    {
+                        generator.GenerateOnePassVersion(count != sigCount).Encode(bcOut);
+                        count++;
+                    }
+
+                    PgpLiteralDataGenerator lGen = new PgpLiteralDataGenerator();
+                    DateTime modificationTime = DateTime.UtcNow;
+                    using (var lOut = lGen.Open(
+                        new UncloseableStream(bcOut),
+                        PgpLiteralData.Utf8,
+                        "_CONSOLE",
+                        data.Length,
+                        modificationTime))
+                    {
+                        lOut.Write(data, 0, data.Length);
+
+                        foreach (PgpSignatureGenerator generator in generators)
+                        {
+                            generator.Update(data);
+                        }
+                    }
+
+                    foreach (PgpSignatureGenerator generator in generators.Reverse())
+                    {
+                        generator.Generate().Encode(bcOut);
+                    }
+                }
+
+                message = ms.ToArray();
+            }
+
+            PgpPublicKeyRingBundle bundle = CreateBundle(
+                new PgpPublicKeyRing(alicePubkey),
+                new PgpPublicKeyRing(v6Certificate));
+
+            VerifyMultipleInlineSignaturesTest(message, bundle);
+
+            //corrupt data;
+            message[95] = 0x50;
+            VerifyMultipleInlineSignaturesTest(message, bundle, shouldFail: true);
+        }
+
+        private void VerifyMultipleDetachedSignaturesTest(byte[] signaturePacket, byte[] data, PgpPublicKeyRingBundle bundle, bool shouldFail = false)
+        {
+            PgpObjectFactory factory = new PgpObjectFactory(signaturePacket);
+            PgpSignatureList sigs = factory.NextPgpObject() as PgpSignatureList;
+
+            IsEquals(sigs.Count, 2);
+            for (int i = 0; i < sigs.Count; i++)
+            {
+                PgpSignature sig = sigs[i];
+                sig.InitVerify(bundle.GetPublicKey(sig.KeyId));
+                sig.Update(data);
+
+                IsTrue(shouldFail != sig.Verify());
+            }
+        }
+
+        [Test]
+        public void MultipleDetachedSignatureTest()
+        {
+            // Verify Detached Signature with multiple keys:
+            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            // https://tests.sequoia-pgp.org/#Detached_Sign_with_minimal_key_from_RFC9760_and_key__Alice___verify_with_key_from_RFC9760
+
+            byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
+            byte[] corruptedData = Encoding.UTF8.GetBytes("Hello World :(");
+
+            PgpPublicKeyRingBundle bundle = CreateBundle(
+                new PgpPublicKeyRing(alicePubkey),
+                new PgpPublicKeyRing(v6Certificate));
+
+            // Detached Signature generated by GopenPGP 3.0.0-alpha
+            byte[] signaturePacket = Base64.Decode(
+                "wpgGABsKAAAAKQUCZeB2zCKhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+                "2azJAAAAAEPPIIh5xfXDp5Zmfa7KJ0S+3Z+RBO9j5AC33ZRAwGgWKVuBts2H+I0k" +
+                "GlIQXoyX+2LnurlGQGxZRqwk/z2d4Tk8oAA62CuJ318aZdo8Z4utdmHvsWlluAWl" +
+                "lh0XdZ5l/qBNC8J1BAAWCgAnBQJl4HbMCRDyMVUMT0fjjhYhBOuFu1+jOnXhXpRO" +
+                "Y/IxVQxPR+OOAABPnQEA881lXU6DUMYbXx3rmGa5qSQld9pHxzRYtBT/WCfkzVwA" +
+                "/0/PN5jncrytAiEjb6YwuZuTVjJdTy6xtzuH+XALdREG");
+
+            VerifyMultipleDetachedSignaturesTest(signaturePacket, data, bundle);
+            VerifyMultipleDetachedSignaturesTest(signaturePacket, corruptedData, bundle, shouldFail: true);
+
+            // Detached Signature generated by PGPy 0.6.0+dkg-crypto-refresh
+            signaturePacket = Base64.Decode(
+                "wpgGABsKAAAAKQWCZeB20SIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+                "2azJAAAAADUkIIqFiPBBvz4Uqsug38k/hVaFdHoHfy82ESRfutwk1ch+TaG8Kk2I" +
+                "7IMcrzKKSp60I7MEGb5CUCzeeM4v883yXlzZhwiBl+enR8kHxcVZzH+z7aS3OptN" +
+                "mrcay8CfwzHJD8J1BAAWCgAdBYJl4HbRFiEE64W7X6M6deFelE5j8jFVDE9H444A" +
+                "CgkQ8jFVDE9H447lbQEAx8hE9sbx1s8kMwuuEUtvoayJyz6R3PyQAIGH72g9XNcA" +
+                "/32a6SYBHAHl8HOrlkZWzUwaIyhOcI5jN6ppiKRZAL8O");
+
+            VerifyMultipleDetachedSignaturesTest(signaturePacket, data, bundle);
+            VerifyMultipleDetachedSignaturesTest(signaturePacket, corruptedData, bundle, shouldFail: true);
+        }
+
+
+        [Test]
+        public void GenerateAndVerifyMultipleDetachedSignatureTest()
+        {
+            // Inline Sign-Verify roundtrip test with multiple keys:
+            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+
+            byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
+            byte[] corruptedData = Encoding.UTF8.GetBytes("Hello World :(");
+            byte[] signaturePacket;
+
+            PgpSecretKey[] signingKeys = new PgpSecretKey[] {
+                new PgpSecretKeyRing(v6UnlockedSecretKey).GetSecretKey(),
+                new PgpSecretKeyRing(aliceSecretkey).GetSecretKey()
+            };
+
+            PgpSignatureGenerator[] generators = new PgpSignatureGenerator[] {
+                CreateAndInitPgpSignatureGenerator(signingKeys[0], HashAlgorithmTag.Sha3_512, emptyPassphrase),
+                CreateAndInitPgpSignatureGenerator(signingKeys[1], HashAlgorithmTag.Sha224, emptyPassphrase)
+            };
+
+            using (MemoryStream ms = new MemoryStream())
+            {
+                using (BcpgOutputStream bcOut = new BcpgOutputStream(ms, newFormatOnly: true))
+                {
+                    foreach (PgpSignatureGenerator generator in generators)
+                    {
+                        generator.Update(data);
+                        generator.Generate().Encode(bcOut);
+                    }
+                }
+
+                signaturePacket = ms.ToArray();
+            }
+            
+            PgpPublicKeyRingBundle bundle = CreateBundle(
+                new PgpPublicKeyRing(alicePubkey),
+                new PgpPublicKeyRing(v6Certificate));
+
+            VerifyMultipleDetachedSignaturesTest(signaturePacket, data, bundle);
+            VerifyMultipleDetachedSignaturesTest(signaturePacket, corruptedData, bundle, shouldFail: true);
+        }
+
+        [Test]
+        public void Version5KeyParsingTest()
+        {
+            string uid = "emma.goldman@example.net";
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v5Certificate);
+            PgpPublicKey[] pubKeys = pubRing.GetPublicKeys().ToArray();
+            IsEquals("wrong number of public keys", pubKeys.Length, 2);
+
+            PgpPublicKey masterKey = pubKeys[0];
+            PgpPublicKey subKey = pubKeys[1];
+
+            IsTrue(masterKey.IsMasterKey);
+            IsTrue(subKey.IsEncryptionKey);
+            IsEquals(masterKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+            IsEquals(subKey.Algorithm, PublicKeyAlgorithmTag.ECDH);
+
+            IsTrue(masterKey.GetUserIds().Contains(uid));
+            IsTrue(!masterKey.GetUserIds().Contains("emma.g@example.net"));
+
+            IsEquals(masterKey.KeyId, 0x19347BC987246402);
+            IsEquals((ulong)subKey.KeyId, 0xE4557C2B02FFBF4B);
+            IsTrue(AreEqual(masterKey.GetFingerprint(), Hex.Decode("19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54")));
+            IsTrue(AreEqual(subKey.GetFingerprint(), Hex.Decode("E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965")));
+
+            // verify v5 self sig
+            PgpSignature signature = masterKey.GetSignaturesForId(uid).ToArray()[0];
+            IsEquals(signature.Version, SignaturePacket.Version5);
+            IsEquals(signature.SignatureType, PgpSignature.PositiveCertification);
+            signature.InitVerify(masterKey);
+            IsTrue(signature.VerifyCertification(uid, masterKey));
+
+            // verify subkey binding sig
+            signature = subKey.GetSignatures().ToArray()[0];
+            IsEquals(signature.Version, SignaturePacket.Version5);
+            IsEquals(signature.SignatureType, PgpSignature.SubkeyBinding);
+            signature.InitVerify(masterKey);
+            IsTrue(signature.VerifyCertification(masterKey, subKey));
+        }
+
+        [Test]
+        public void Version5InlineSignatureTest()
+        {
+            // Verify v5 Inline Signature generated by OpenPGP.js 5.5.0
+            // https://tests.sequoia-pgp.org/#Inline_Sign-Verify_roundtrip_with_key__Emma_
+            byte[] message = Base64.Decode(
+                "xA0DAQoWGTR7yYckZAIByxR1AGXgdslIZWxsbyBXb3JsZCA6KcJ3BQEWCgAp" +
+                "BQJl4HbJIiEFGTR7yYckZAJfmd8+wuAADtmISJLh97PqTJQAkVlWm1QAADsI" +
+                "AQD7aH9a0GKcHdFThMsOQ88xAM5PiqPyDV1A/K23rPN28wD/QoPa1yEE3Y2R" +
+                "ZtqtH6jAymdyIwtsa5wLvzUjTmP5OQo=");
+
+            PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v5Certificate);
+            PgpPublicKey signer = pubRing.GetPublicKey();
+
+            PgpObjectFactory factory = new PgpObjectFactory(message);
+
+            PgpOnePassSignatureList opss = factory.NextPgpObject() as PgpOnePassSignatureList;
+            IsEquals(opss.Count, 1);
+            PgpOnePassSignature ops = opss[0];
+            IsEquals(ops.Version, OnePassSignaturePacket.Version3);
+
+            ops.InitVerify(signer);
+            PgpLiteralData literal = factory.NextPgpObject() as PgpLiteralData;
+            using (Stream dIn = literal.GetInputStream())
+            {
+                byte[] buffer = new byte[30];
+                int bytesRead;
+                while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+                {
+                    ops.Update(buffer, 0, bytesRead);
+                }
+            }
+
+            PgpSignatureList sigs = factory.NextPgpObject() as PgpSignatureList;
+            IsEquals(sigs.Count, 1);
+            byte[] metadata = literal.GetMetadata(sigs[0].Version);
+            IsTrue(ops.Verify(sigs[0], metadata));
+        }
+
+
+        public override string Name => "PgpInteroperabilityTestSuite";
+
+        public override void PerformTest()
+        {
+            MultipleInlineSignatureTest();
+            GenerateAndVerifyMultipleInlineSignatureTest();
+
+            MultipleDetachedSignatureTest();
+            GenerateAndVerifyMultipleDetachedSignatureTest();
+
+            Version5KeyParsingTest();
+            Version5InlineSignatureTest();
+        }
+    }
+}

From 6c933f243dc90b24501e962c1e065542c5dd7dee Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sun, 3 Mar 2024 14:57:43 +0100
Subject: [PATCH 16/37] generation of v6 OpenPGP keypairs

---
 crypto/src/openpgp/PgpKeyPair.cs              |  31 +++-
 crypto/src/openpgp/PgpKeyRingGenerator.cs     |  18 +--
 crypto/src/openpgp/PgpSecretKey.cs            |  30 +++-
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 149 +++++++++++++++++-
 4 files changed, 205 insertions(+), 23 deletions(-)

diff --git a/crypto/src/openpgp/PgpKeyPair.cs b/crypto/src/openpgp/PgpKeyPair.cs
index 65ef5e829b..bfa2e44c21 100644
--- a/crypto/src/openpgp/PgpKeyPair.cs
+++ b/crypto/src/openpgp/PgpKeyPair.cs
@@ -27,19 +27,38 @@ public PgpKeyPair(
         {
         }
 
-		public PgpKeyPair(
+        public PgpKeyPair(
+            int version,
+            PublicKeyAlgorithmTag algorithm,
+            AsymmetricCipherKeyPair keyPair,
+            DateTime time)
+            : this(version, algorithm, keyPair.Public, keyPair.Private, time)
+        {
+        }
+
+        public PgpKeyPair(
             PublicKeyAlgorithmTag	algorithm,
             AsymmetricKeyParameter	pubKey,
             AsymmetricKeyParameter	privKey,
             DateTime				time)
+            :this(PublicKeyPacket.DefaultVersion, algorithm, pubKey, privKey, time)
+        {
+        }
+
+        public PgpKeyPair(
+            int version,
+            PublicKeyAlgorithmTag algorithm,
+            AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter privKey,
+            DateTime time)
         {
-            this.pub = new PgpPublicKey(algorithm, pubKey, time);
-			this.priv = new PgpPrivateKey(pub, privKey);
+            this.pub = new PgpPublicKey(version, algorithm, pubKey, time);
+            this.priv = new PgpPrivateKey(pub, privKey);
         }
 
-		/// <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>
+        /// <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)
diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs
index a04ebc7dfe..9f9bbd6ff4 100644
--- a/crypto/src/openpgp/PgpKeyRingGenerator.cs
+++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs
@@ -1,15 +1,13 @@
+using Org.BouncyCastle.Security;
 using System;
 using System.Collections.Generic;
 
-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>
+    /// <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<PgpSecretKey>         keys = new List<PgpSecretKey>();
@@ -300,7 +298,7 @@ public void AddSubKey(
                 //
                 // Generate the certification
                 //
-                sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey);
+                sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey, rand);
 
                 sGen.SetHashedSubpackets(hashedPackets);
                 sGen.SetUnhashedSubpackets(unhashedPackets);
@@ -346,12 +344,12 @@ public void AddSubKey(
                 //
                 // Generate the certification
                 //
-                sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey);
+                sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey, rand);
 
                 // add primary key binding sub packet
                 PgpSignatureGenerator pGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, primaryKeyBindingHashAlgorithm);
 
-                pGen.InitSign(PgpSignature.PrimaryKeyBinding, keyPair.PrivateKey);
+                pGen.InitSign(PgpSignature.PrimaryKeyBinding, keyPair.PrivateKey, rand);
 
                 PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator(hashedPackets);
 
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index e93023f336..b2a3d1a939 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -110,6 +110,22 @@ internal PgpSecretKey(
                     ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
                     secKey = new ElGamalSecretBcpgKey(esK.X);
                     break;
+                case PublicKeyAlgorithmTag.Ed25519:
+                    Ed25519PrivateKeyParameters e25519pk = (Ed25519PrivateKeyParameters)privKey.Key;
+                    secKey = new Ed25519SecretBcpgKey(e25519pk.GetEncoded());
+                    break;
+                case PublicKeyAlgorithmTag.Ed448:
+                    Ed448PrivateKeyParameters e448pk = (Ed448PrivateKeyParameters)privKey.Key;
+                    secKey = new Ed448SecretBcpgKey(e448pk.GetEncoded());
+                    break;
+                case PublicKeyAlgorithmTag.X25519:
+                    X25519PrivateKeyParameters x25519pk = (X25519PrivateKeyParameters)privKey.Key;
+                    secKey = new X25519SecretBcpgKey(x25519pk.GetEncoded());
+                    break;
+                case PublicKeyAlgorithmTag.X448:
+                    X448PrivateKeyParameters x448pk = (X448PrivateKeyParameters)privKey.Key;
+                    secKey = new X448SecretBcpgKey(x448pk.GetEncoded());
+                    break;
                 default:
                     throw new PgpException("unknown key class");
             }
@@ -243,7 +259,7 @@ internal PgpSecretKey(
             PgpSignatureSubpacketVector	hashedPackets,
             PgpSignatureSubpacketVector	unhashedPackets,
             SecureRandom				rand)
-            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets),
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, rand),
                 encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
         {
         }
@@ -319,7 +335,7 @@ internal PgpSecretKey(
             PgpSignatureSubpacketVector hashedPackets,
             PgpSignatureSubpacketVector unhashedPackets,
             SecureRandom                rand)
-            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm),
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm, rand),
                 encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
         {
         }
@@ -329,7 +345,8 @@ private static PgpPublicKey CertifiedPublicKey(
             PgpKeyPair					keyPair,
             string						id,
             PgpSignatureSubpacketVector	hashedPackets,
-            PgpSignatureSubpacketVector	unhashedPackets)
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom rand)
         {
             PgpSignatureGenerator sGen;
             try
@@ -344,7 +361,7 @@ private static PgpPublicKey CertifiedPublicKey(
             //
             // Generate the certification
             //
-            sGen.InitSign(certificationLevel, keyPair.PrivateKey);
+            sGen.InitSign(certificationLevel, keyPair.PrivateKey, rand);
 
             sGen.SetHashedSubpackets(hashedPackets);
             sGen.SetUnhashedSubpackets(unhashedPackets);
@@ -367,7 +384,8 @@ private static PgpPublicKey CertifiedPublicKey(
             string id,
             PgpSignatureSubpacketVector hashedPackets,
             PgpSignatureSubpacketVector unhashedPackets,
-            HashAlgorithmTag hashAlgorithm)
+            HashAlgorithmTag hashAlgorithm,
+            SecureRandom rand)
         {
             PgpSignatureGenerator sGen;
             try
@@ -382,7 +400,7 @@ private static PgpPublicKey CertifiedPublicKey(
             //
             // Generate the certification
             //
-            sGen.InitSign(certificationLevel, keyPair.PrivateKey);
+            sGen.InitSign(certificationLevel, keyPair.PrivateKey, rand);
 
             sGen.SetHashedSubpackets(hashedPackets);
             sGen.SetUnhashedSubpackets(unhashedPackets);
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 5633d9a637..1d6d456843 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -335,6 +335,152 @@ public void Version6UnlockedSecretKeyParsingTest()
             IsTrue(key.IsRevoked());
         }
 
+        [Test]
+        public void Version6Ed25519KeyPairCreationTest()
+        {
+            /* 
+             * Create a v6 Ed25519 keypair with the same key material and creation datetime as the test vector
+             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+             * then check the fingerprint and verify a signature
+             */
+            byte[] keyMaterial = Hex.Decode("1972817b12be707e8d5f586ce61361201d344eb266a2c82fde6835762b65b0b7");
+            Ed25519PrivateKeyParameters seckey = new Ed25519PrivateKeyParameters(keyMaterial);
+            Ed25519PublicKeyParameters pubkey = seckey.GeneratePublicKey();
+            PgpKeyPair keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed25519, pubkey, seckey, DateTime.Parse("2022-11-30 16:08:03Z"));
+
+            IsEquals(keypair.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsEquals(keypair.PublicKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+            byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
+            IsEquals((ulong)keypair.KeyId, 0xCB186C4F0609A697);
+            IsTrue("wrong master key fingerprint", AreEqual(keypair.PublicKey.GetFingerprint(), expectedFingerprint));
+
+
+            VerifyEncodedSignature(
+                v6SampleCleartextSignedMessageSignature,
+                Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
+                keypair.PublicKey);
+
+            VerifyEncodedSignature(
+                v6SampleCleartextSignedMessageSignature,
+                Encoding.UTF8.GetBytes("wrongdata"),
+                keypair.PublicKey,
+                shouldFail: true);
+
+            // encode-decode roundtrip
+
+            PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
+                PgpSignature.PositiveCertification,
+                keypair,
+                "Alice <alice@example.com>",
+                SymmetricKeyAlgorithmTag.Null,
+                Array.Empty<char>(),
+                true,
+                null,
+                null,
+                new SecureRandom());
+
+            PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+            byte[] encodedsecring;
+            using (MemoryStream ms = new MemoryStream())
+            {
+                secring.Encode(ms);
+                encodedsecring = ms.ToArray();
+            }
+
+            PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
+
+            PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
+            PgpSecretKey pgpseckey = decodedsecring.GetSecretKey();
+            IsEquals(pgppubkey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsEquals(pgppubkey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+            IsEquals((ulong)pgppubkey.KeyId, 0xCB186C4F0609A697);
+            IsTrue("wrong master key fingerprint", AreEqual(pgppubkey.GetFingerprint(), expectedFingerprint));
+            IsTrue(pgppubkey.GetUserIds().Contains("Alice <alice@example.com>"));
+
+            // Sign-Verify roundtrip
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
+            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(pgppubkey.Algorithm, HashAlgorithmTag.Sha512);
+            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
+            PgpPrivateKey privKey = pgpseckey.ExtractPrivateKey(emptyPassphrase);
+            spkGen.SetIssuerFingerprint(false, pgpseckey);
+            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+            sigGen.Update(data);
+            sigGen.SetHashedSubpackets(spkGen.Generate());
+            PgpSignature signature = sigGen.Generate();
+
+            AreEqual(signature.GetIssuerFingerprint(), expectedFingerprint);
+            VerifySignature(signature, data, pgppubkey);
+            VerifySignature(signature, wrongData, pgppubkey, shouldFail: true);
+        }
+
+        [Test]
+        public void Version6Ed448KeyPairCreationTest()
+        {
+            /* 
+             * Create a v6 Ed448 keypair, then perform encode-decode and sign-verify roundtrips
+             */
+            SecureRandom rand = new SecureRandom();
+            DateTime now = DateTime.UtcNow;
+
+            Ed448KeyPairGenerator ed448gen = new Ed448KeyPairGenerator();
+            ed448gen.Init(new Ed448KeyGenerationParameters(rand));
+            AsymmetricCipherKeyPair kp = ed448gen.GenerateKeyPair();
+
+            PgpKeyPair keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed448, kp, now);
+            IsEquals(keypair.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed448);
+            IsEquals(keypair.PublicKey.CreationTime.ToString("yyyyMMddHHmmss"), now.ToString("yyyyMMddHHmmss"));
+            long keyId = keypair.PublicKey.KeyId;
+            byte[] fpr = keypair.PublicKey.GetFingerprint();
+            IsEquals(fpr.Length, 32);
+
+            // encode-decode roundtrip
+            PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
+                PgpSignature.PositiveCertification,
+                keypair,
+                "Alice <alice@example.com>",
+                SymmetricKeyAlgorithmTag.Null,
+                Array.Empty<char>(),
+                true,
+                null,
+                null,
+                rand);
+
+            PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+            byte[] encodedsecring;
+            using (MemoryStream ms = new MemoryStream())
+            {
+                secring.Encode(ms);
+                encodedsecring = ms.ToArray();
+            }
+
+            PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
+            PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
+            PgpSecretKey pgpseckey = decodedsecring.GetSecretKey();
+            IsEquals(pgppubkey.Algorithm, PublicKeyAlgorithmTag.Ed448);
+            IsEquals(pgppubkey.CreationTime.ToString("yyyyMMddHHmmss"), now.ToString("yyyyMMddHHmmss"));
+            IsEquals(pgppubkey.KeyId, keyId);
+            IsTrue("wrong master key fingerprint", AreEqual(pgppubkey.GetFingerprint(), fpr));
+            IsTrue(pgppubkey.GetUserIds().Contains("Alice <alice@example.com>"));
+
+            // Sign-Verify roundtrip
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
+            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(pgppubkey.Algorithm, HashAlgorithmTag.Sha512);
+            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
+            PgpPrivateKey privKey = pgpseckey.ExtractPrivateKey(emptyPassphrase);
+            spkGen.SetIssuerFingerprint(false, pgpseckey);
+            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, rand);
+            sigGen.Update(data);
+            sigGen.SetHashedSubpackets(spkGen.Generate());
+            PgpSignature signature = sigGen.Generate();
+
+            AreEqual(signature.GetIssuerFingerprint(), fpr);
+
+            VerifySignature(signature, data, pgppubkey);
+            VerifySignature(signature, wrongData, pgppubkey, shouldFail: true);
+        }
+
         [Test]
         public void Version6LockedSecretKeyParsingTest()
         {
@@ -539,9 +685,10 @@ public override void PerformTest()
             Version4Ed25519LegacyPubkeySampleTest();
             Version4Ed25519LegacySignatureSampleTest();
             Version4Ed25519LegacyCreateTest();
-
             Version6CertificateParsingTest();
             Version6PublicKeyCreationTest();
+            Version6Ed25519KeyPairCreationTest();
+            Version6Ed448KeyPairCreationTest();
             Version6UnlockedSecretKeyParsingTest();
             Version6LockedSecretKeyParsingTest();
             Version6SampleCleartextSignedMessageVerifySignatureTest();

From fd0da1397ca5ef110124ea45f17cfef328ef41e2 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sun, 3 Mar 2024 17:57:38 +0100
Subject: [PATCH 17/37] test adding encryption subkeys in v6 keyrings

---
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 122 ++++++++++++++----
 1 file changed, 96 insertions(+), 26 deletions(-)

diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 1d6d456843..ab1ddf0d0d 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -285,7 +285,7 @@ public void Version6UnlockedSecretKeyParsingTest()
             kpGen.Init(new X25519KeyGenerationParameters(new SecureRandom()));
             AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
 
-            IsTrue("X25519 agreement failed", EncryptThenDecryptX25519Test(alice, bob));
+            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, encryptionKey.PublicKey.Algorithm));
 
             // Encode test
             using (MemoryStream ms = new MemoryStream())
@@ -367,26 +367,28 @@ public void Version6Ed25519KeyPairCreationTest()
                 shouldFail: true);
 
             // encode-decode roundtrip
-
+            SecureRandom rand = new SecureRandom();
+            string uid = "Alice <alice@example.com>";
             PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
                 PgpSignature.PositiveCertification,
                 keypair,
-                "Alice <alice@example.com>",
+                uid,
                 SymmetricKeyAlgorithmTag.Null,
                 Array.Empty<char>(),
                 true,
                 null,
                 null,
-                new SecureRandom());
+                rand);
 
-            PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
-            byte[] encodedsecring;
-            using (MemoryStream ms = new MemoryStream())
-            {
-                secring.Encode(ms);
-                encodedsecring = ms.ToArray();
-            }
+            // add an encryption subkey
+            X25519KeyPairGenerator x25519gen = new X25519KeyPairGenerator();
+            x25519gen.Init(new X25519KeyGenerationParameters(rand));
+            AsymmetricCipherKeyPair x25519kp = x25519gen.GenerateKeyPair();
+            keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.X25519, x25519kp, DateTime.Parse("2022-11-30 16:08:03Z"));
+            keyRingGen.AddSubKey(keypair);
 
+            PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+            byte[] encodedsecring = secring.GetEncoded();
             PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
 
             PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
@@ -395,7 +397,33 @@ public void Version6Ed25519KeyPairCreationTest()
             IsEquals(pgppubkey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
             IsEquals((ulong)pgppubkey.KeyId, 0xCB186C4F0609A697);
             IsTrue("wrong master key fingerprint", AreEqual(pgppubkey.GetFingerprint(), expectedFingerprint));
-            IsTrue(pgppubkey.GetUserIds().Contains("Alice <alice@example.com>"));
+            IsTrue(pgppubkey.GetUserIds().Contains(uid));
+
+            // verify selfsig
+            PgpSignature signature = pgppubkey.GetSignaturesForId(uid).ToArray()[0];
+            IsEquals(signature.Version, SignaturePacket.Version6);
+            IsEquals(signature.SignatureType, PgpSignature.PositiveCertification);
+            signature.InitVerify(pgppubkey);
+            IsTrue(signature.VerifyCertification(uid, pgppubkey));
+            IsTrue(!signature.VerifyCertification("Bob <bob@example.com>", pgppubkey));
+
+            // verify subkey
+            PgpSecretKey subKey = decodedsecring.GetSecretKeys().ToArray()[1];
+            IsEquals(subKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+
+            // Verify subkey binding signature
+            PgpSignature bindingSig = subKey.PublicKey.GetSignatures().First();
+            IsTrue(bindingSig.SignatureType == PgpSignature.SubkeyBinding);
+            bindingSig.InitVerify(pgppubkey);
+            IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
+
+            // encrypt-decrypt test
+            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
+            IAsymmetricCipherKeyPairGenerator kpGen = new X25519KeyPairGenerator();
+            kpGen.Init(new X25519KeyGenerationParameters(rand));
+            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
+            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
+
 
             // Sign-Verify roundtrip
             byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
@@ -407,7 +435,7 @@ public void Version6Ed25519KeyPairCreationTest()
             sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
             sigGen.Update(data);
             sigGen.SetHashedSubpackets(spkGen.Generate());
-            PgpSignature signature = sigGen.Generate();
+            signature = sigGen.Generate();
 
             AreEqual(signature.GetIssuerFingerprint(), expectedFingerprint);
             VerifySignature(signature, data, pgppubkey);
@@ -435,10 +463,11 @@ public void Version6Ed448KeyPairCreationTest()
             IsEquals(fpr.Length, 32);
 
             // encode-decode roundtrip
+            string uid = "Alice <alice@example.com>";
             PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
                 PgpSignature.PositiveCertification,
                 keypair,
-                "Alice <alice@example.com>",
+                uid,
                 SymmetricKeyAlgorithmTag.Null,
                 Array.Empty<char>(),
                 true,
@@ -446,22 +475,49 @@ public void Version6Ed448KeyPairCreationTest()
                 null,
                 rand);
 
-            PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
-            byte[] encodedsecring;
-            using (MemoryStream ms = new MemoryStream())
-            {
-                secring.Encode(ms);
-                encodedsecring = ms.ToArray();
-            }
+            // add an encryption subkey
+            X448KeyPairGenerator x448gen = new X448KeyPairGenerator();
+            x448gen.Init(new X448KeyGenerationParameters(rand));
+            AsymmetricCipherKeyPair x448kp = x448gen.GenerateKeyPair();
+            keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.X448, x448kp, now);
+            keyRingGen.AddSubKey(keypair);
 
+            PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+            byte[] encodedsecring = secring.GetEncoded();
             PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
+
             PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
             PgpSecretKey pgpseckey = decodedsecring.GetSecretKey();
             IsEquals(pgppubkey.Algorithm, PublicKeyAlgorithmTag.Ed448);
             IsEquals(pgppubkey.CreationTime.ToString("yyyyMMddHHmmss"), now.ToString("yyyyMMddHHmmss"));
             IsEquals(pgppubkey.KeyId, keyId);
             IsTrue("wrong master key fingerprint", AreEqual(pgppubkey.GetFingerprint(), fpr));
-            IsTrue(pgppubkey.GetUserIds().Contains("Alice <alice@example.com>"));
+            IsTrue(pgppubkey.GetUserIds().Contains(uid));
+
+            // verify selfsig
+            PgpSignature signature = pgppubkey.GetSignaturesForId(uid).ToArray()[0];
+            IsEquals(signature.Version, SignaturePacket.Version6);
+            IsEquals(signature.SignatureType, PgpSignature.PositiveCertification);
+            signature.InitVerify(pgppubkey);
+            IsTrue(signature.VerifyCertification(uid, pgppubkey));
+            IsTrue(!signature.VerifyCertification("Bob <bob@example.com>", pgppubkey));
+
+            // verify subkey
+            PgpSecretKey subKey = decodedsecring.GetSecretKeys().ToArray()[1];
+            IsEquals(subKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X448);
+
+            // Verify subkey binding signature
+            PgpSignature bindingSig = subKey.PublicKey.GetSignatures().First();
+            IsTrue(bindingSig.SignatureType == PgpSignature.SubkeyBinding);
+            bindingSig.InitVerify(pgppubkey);
+            IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
+
+            // encrypt-decrypt test
+            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
+            IAsymmetricCipherKeyPairGenerator kpGen = new X448KeyPairGenerator();
+            kpGen.Init(new X448KeyGenerationParameters(rand));
+            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
+            IsTrue("X448 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
 
             // Sign-Verify roundtrip
             byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
@@ -473,7 +529,7 @@ public void Version6Ed448KeyPairCreationTest()
             sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, rand);
             sigGen.Update(data);
             sigGen.SetHashedSubpackets(spkGen.Generate());
-            PgpSignature signature = sigGen.Generate();
+            signature = sigGen.Generate();
 
             AreEqual(signature.GetIssuerFingerprint(), fpr);
 
@@ -665,14 +721,28 @@ private static AsymmetricCipherKeyPair GetKeyPair(PgpSecretKey secretKey, string
                 secretKey.ExtractPrivateKey(password.ToCharArray()).Key);
         }
 
-        private static bool EncryptThenDecryptX25519Test(AsymmetricCipherKeyPair alice, AsymmetricCipherKeyPair bob)
+        private static bool EncryptThenDecryptTest(AsymmetricCipherKeyPair alice, AsymmetricCipherKeyPair bob, PublicKeyAlgorithmTag algo)
         {
-            X25519Agreement agreeA = new X25519Agreement();
+            IRawAgreement agreeA, agreeB;
+
+            switch (algo)
+            {
+                case PublicKeyAlgorithmTag.X25519:
+                    agreeA = new X25519Agreement();
+                    agreeB = new X25519Agreement();
+                    break;
+                case PublicKeyAlgorithmTag.X448:
+                    agreeA = new X448Agreement();
+                    agreeB = new X448Agreement();
+                    break;
+                default:
+                    throw new ArgumentException($"Unsupported algo {algo}");
+            }
+
             agreeA.Init(alice.Private);
             byte[] secretA = new byte[agreeA.AgreementSize];
             agreeA.CalculateAgreement(bob.Public, secretA, 0);
 
-            X25519Agreement agreeB = new X25519Agreement();
             agreeB.Init(bob.Private);
             byte[] secretB = new byte[agreeB.AgreementSize];
             agreeB.CalculateAgreement(alice.Public, secretB, 0);

From cf27aea36fa9191e050fd22d8590290727d045fe Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Mon, 4 Mar 2024 19:29:06 +0100
Subject: [PATCH 18/37] small modifications in tests

---
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 51 ++++++++-----------
 1 file changed, 21 insertions(+), 30 deletions(-)

diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index ab1ddf0d0d..14ffc27217 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -18,9 +18,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
     public class PgpCryptoRefreshTest
         : SimpleTest
     {
-        public override string Name => "PgpCryptoRefreshTest";
-
-
         // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
         private readonly byte[] v4Ed25519LegacyPubkeySample = Base64.Decode(
             "xjMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" +
@@ -280,14 +277,7 @@ public void Version6UnlockedSecretKeyParsingTest()
             IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
             IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
 
-            AsymmetricCipherKeyPair alice = GetKeyPair(encryptionKey);
-            IAsymmetricCipherKeyPairGenerator kpGen = new X25519KeyPairGenerator();
-            kpGen.Init(new X25519KeyGenerationParameters(new SecureRandom()));
-            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
-
-            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, encryptionKey.PublicKey.Algorithm));
-
-            // Encode test
+            // Encode-Decode roundtrip
             using (MemoryStream ms = new MemoryStream())
             {
                 using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
@@ -366,7 +356,7 @@ public void Version6Ed25519KeyPairCreationTest()
                 keypair.PublicKey,
                 shouldFail: true);
 
-            // encode-decode roundtrip
+            // Encode-Decode roundtrip
             SecureRandom rand = new SecureRandom();
             string uid = "Alice <alice@example.com>";
             PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
@@ -417,14 +407,6 @@ public void Version6Ed25519KeyPairCreationTest()
             bindingSig.InitVerify(pgppubkey);
             IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
 
-            // encrypt-decrypt test
-            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
-            IAsymmetricCipherKeyPairGenerator kpGen = new X25519KeyPairGenerator();
-            kpGen.Init(new X25519KeyGenerationParameters(rand));
-            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
-            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
-
-
             // Sign-Verify roundtrip
             byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
             byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
@@ -440,6 +422,15 @@ public void Version6Ed25519KeyPairCreationTest()
             AreEqual(signature.GetIssuerFingerprint(), expectedFingerprint);
             VerifySignature(signature, data, pgppubkey);
             VerifySignature(signature, wrongData, pgppubkey, shouldFail: true);
+
+            // encrypt-decrypt test
+            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
+
+            PgpSecretKeyRing bobSecring = new PgpSecretKeyRing(v6UnlockedSecretKey);
+            PgpSecretKey bobEncryptionKey = bobSecring.GetSecretKeys().ToArray()[1];
+            AsymmetricCipherKeyPair bob = GetKeyPair(bobEncryptionKey);
+
+            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
         }
 
         [Test]
@@ -512,13 +503,6 @@ public void Version6Ed448KeyPairCreationTest()
             bindingSig.InitVerify(pgppubkey);
             IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
 
-            // encrypt-decrypt test
-            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
-            IAsymmetricCipherKeyPairGenerator kpGen = new X448KeyPairGenerator();
-            kpGen.Init(new X448KeyGenerationParameters(rand));
-            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
-            IsTrue("X448 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
-
             // Sign-Verify roundtrip
             byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
             byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
@@ -532,9 +516,15 @@ public void Version6Ed448KeyPairCreationTest()
             signature = sigGen.Generate();
 
             AreEqual(signature.GetIssuerFingerprint(), fpr);
-
             VerifySignature(signature, data, pgppubkey);
             VerifySignature(signature, wrongData, pgppubkey, shouldFail: true);
+
+            // Encrypt-Decrypt test
+            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
+            IAsymmetricCipherKeyPairGenerator kpGen = new X448KeyPairGenerator();
+            kpGen.Init(new X448KeyGenerationParameters(rand));
+            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
+            IsTrue("X448 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
         }
 
         [Test]
@@ -563,7 +553,7 @@ public void Version6LockedSecretKeyParsingTest()
             IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
             IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
 
-            // Encode test
+            // Encode-Decode roundtrip
             using (MemoryStream ms = new MemoryStream())
             {
                 using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
@@ -580,7 +570,6 @@ public void Version6LockedSecretKeyParsingTest()
         public void Version6SampleCleartextSignedMessageVerifySignatureTest()
         {
             // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-cleartext-signed-mes
-
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
@@ -750,6 +739,8 @@ private static bool EncryptThenDecryptTest(AsymmetricCipherKeyPair alice, Asymme
             return Arrays.AreEqual(secretA, secretB);
         }
 
+        public override string Name => "PgpCryptoRefreshTest";
+
         public override void PerformTest()
         {
             Version4Ed25519LegacyPubkeySampleTest();

From a9c2208b5ee6a6bfa098b60f33419292076034f7 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Tue, 5 Mar 2024 18:10:24 +0100
Subject: [PATCH 19/37] "Version" and "Hash" Armor Header only on explicit
 request

---
 crypto/src/bcpg/ArmoredOutputStream.cs        | 58 ++++++++++++++++---
 .../test/src/openpgp/test/PGPArmoredTest.cs   | 47 +++++++++++++++
 .../test/PGPClearSignedSignatureTest.cs       | 43 +++++++++-----
 3 files changed, 125 insertions(+), 23 deletions(-)

diff --git a/crypto/src/bcpg/ArmoredOutputStream.cs b/crypto/src/bcpg/ArmoredOutputStream.cs
index 03d4a1a918..f39e5095b8 100644
--- a/crypto/src/bcpg/ArmoredOutputStream.cs
+++ b/crypto/src/bcpg/ArmoredOutputStream.cs
@@ -18,6 +18,7 @@ public class ArmoredOutputStream
         : BaseOutputStream
     {
         public static readonly string HeaderVersion = "Version";
+        private readonly bool showVersion;
 
         private static readonly byte[] encodingTable =
         {
@@ -148,14 +149,29 @@ private static string CreateVersion()
         private readonly IDictionary<string, IList<string>> m_headers;
 
         public ArmoredOutputStream(Stream outStream)
+            : this(outStream, false)
         {
+        }
+
+        public ArmoredOutputStream(Stream outStream, bool showVersion)
+        {
+            this.showVersion = showVersion;
             this.outStream = outStream;
             this.m_headers = new Dictionary<string, IList<string>>(1);
-            SetHeader(HeaderVersion, Version);
+
+            if (showVersion)
+            {
+                SetHeader(HeaderVersion, Version);
+            }
         }
 
         public ArmoredOutputStream(Stream outStream, IDictionary<string, string> headers)
-            : this(outStream)
+            :this(outStream, headers, false)
+        {
+        }
+
+        public ArmoredOutputStream(Stream outStream, IDictionary<string, string> headers, bool showVersion)
+            : this(outStream, showVersion)
         {
             foreach (var header in headers)
             {
@@ -229,21 +245,42 @@ public void ResetHeaders()
             }
         }
 
-        /**
-         * Start a clear text signed message.
-         * @param hashAlgorithm
-         */
+        /// <summary>
+        /// Start a clear text signed message.
+        /// </summary>
+        public void BeginClearText() 
+        {
+            DoWrite("-----BEGIN PGP SIGNED MESSAGE-----" + NewLine + NewLine);
+
+            clearText = true;
+            newLine = true;
+            lastb = 0;
+
+        }
+
+        /// <summary>
+        /// Start a clear text signed message with a "Hash" Armor Header.
+        /// This header is deprecated and SHOULD NOT be emitted unless:
+        ///    * The cleartext signed message contains a v4 signature made using a SHA2-based digest(SHA224, SHA256, SHA384, or SHA512), and
+        ///    * The cleartext signed message might be verified by a legacy OpenPGP implementation that requires this header.
+        ///    <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-hash-armor-header"/>
+        /// </summary>
+        /// <param name="hashAlgorithm"></param>
+        /// <exception cref="IOException"></exception>
         public void BeginClearText(
             HashAlgorithmTag    hashAlgorithm)
         {
-            string    hash;
+            string hash;
 
             switch (hashAlgorithm)
             {
             case HashAlgorithmTag.Sha1:
                 hash = "SHA1";
                 break;
-            case HashAlgorithmTag.Sha256:
+            case HashAlgorithmTag.Sha224:
+                hash = "SHA224";
+                break;
+             case HashAlgorithmTag.Sha256:
                 hash = "SHA256";
                 break;
             case HashAlgorithmTag.Sha384:
@@ -336,7 +373,10 @@ public override void WriteByte(byte value)
 
                 DoWrite(headerStart + type + headerTail + NewLine);
 
-                if (m_headers.TryGetValue(HeaderVersion, out var versionHeaders))
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-armor-header
+                // To minimize metadata, implementations SHOULD NOT emit this key and its corresponding value except
+                // for debugging purposes with explicit user consent.
+                if (showVersion && m_headers.TryGetValue(HeaderVersion, out var versionHeaders))
                 {
                     WriteHeaderEntry(HeaderVersion, versionHeaders[0]);
                 }
diff --git a/crypto/test/src/openpgp/test/PGPArmoredTest.cs b/crypto/test/src/openpgp/test/PGPArmoredTest.cs
index 9fe5da6edd..195c1ca248 100644
--- a/crypto/test/src/openpgp/test/PGPArmoredTest.cs
+++ b/crypto/test/src/openpgp/test/PGPArmoredTest.cs
@@ -124,6 +124,52 @@ private void blankLineTest()
 			}
 		}
 
+		private void VersionIsOptionalTest()
+		{
+			using (MemoryStream bOut = new MemoryStream())
+			{
+				using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut))
+				{
+					aOut.Write(sample, 0, sample.Length);
+				}
+
+				bOut.Position = 0;
+				using (var reader = new StreamReader(bOut))
+				{
+					string line;
+                    while ((line = reader.ReadLine()) != null)
+                    {
+						FailIf("Unexpected version armor header", line.StartsWith("Version:"));
+                    }
+                }
+			}
+
+            using (MemoryStream bOut = new MemoryStream())
+            {
+                using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut, showVersion: true))
+                {
+                    aOut.Write(sample, 0, sample.Length);
+                }
+
+                bOut.Position = 0;
+                using (var reader = new StreamReader(bOut))
+                {
+					bool versionIsPresent = false;
+                    string line;
+                    while ((line = reader.ReadLine()) != null)
+                    {
+						if (line.StartsWith("Version:"))
+						{ 
+							versionIsPresent = true;
+							break;
+						}
+                    }
+
+                    IsTrue("Version armored header expected", versionIsPresent);
+                }
+            }
+        }
+
         private void repeatHeaderTest()
         {
             MemoryStream bOut = new MemoryStream();
@@ -288,6 +334,7 @@ public override void PerformTest()
 			blankLineTest();
             pgpUtilTest();
             repeatHeaderTest();
+			VersionIsOptionalTest();
         }
 
 		public override string Name
diff --git a/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs b/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs
index a6f03c06f4..e41c1f19cf 100644
--- a/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs
+++ b/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs
@@ -164,23 +164,26 @@ public override string Name
 
 		private void messageTest(
 			string message,
-			string type)
+			string type,
+			bool ignoreHeaders = false)
 		{
 			ArmoredInputStream aIn = new ArmoredInputStream(
 				new MemoryStream(Encoding.ASCII.GetBytes(message)));
 
-			string[] headers = aIn.GetArmorHeaders();
-
-			if (headers == null || headers.Length != 1)
+			if (!ignoreHeaders)
 			{
-				Fail("wrong number of headers found");
-			}
+				string[] headers = aIn.GetArmorHeaders();
 
-			if (!"Hash: SHA256".Equals(headers[0]))
-			{
-				Fail("header value wrong: " + headers[0]);
-			}
+				if (headers == null || headers.Length != 1)
+				{
+					Fail("wrong number of headers found");
+				}
 
+				if (!"Hash: SHA256".Equals(headers[0]))
+				{
+					Fail("header value wrong: " + headers[0]);
+				}
+			}
 			//
 			// read the input, making sure we ingore the last newline.
 			//
@@ -254,7 +257,8 @@ private PgpSecretKey ReadSecretKey(
 
 		private void generateTest(
 			string message,
-			string type)
+			string type,
+            bool includeHashHeader = true)
 		{
 			PgpSecretKey                    pgpSecKey = ReadSecretKey(new MemoryStream(secretKey));
 			PgpPrivateKey                   pgpPrivKey = pgpSecKey.ExtractPrivateKey("".ToCharArray());
@@ -273,7 +277,14 @@ private void generateTest(
 			ArmoredOutputStream aOut = new ArmoredOutputStream(bOut);
 			MemoryStream bIn = new MemoryStream(Encoding.ASCII.GetBytes(message), false);
 
-			aOut.BeginClearText(HashAlgorithmTag.Sha256);
+			if (includeHashHeader)
+			{
+				aOut.BeginClearText(HashAlgorithmTag.Sha256);
+			}
+			else
+			{
+                aOut.BeginClearText();
+            }
 
 			//
 			// note the last \n m_in the file is ignored
@@ -306,7 +317,7 @@ private void generateTest(
 			aOut.Close();
 
 			byte[] bs = bOut.ToArray();
-			messageTest(Encoding.ASCII.GetString(bs, 0, bs.Length), type);
+			messageTest(Encoding.ASCII.GetString(bs, 0, bs.Length), type, !includeHashHeader);
 		}
 
 		private static int ReadInputLine(
@@ -424,7 +435,11 @@ public override void PerformTest()
 			generateTest(nlOnlyMessage, "\\r");
 			generateTest(crOnlyMessage, "\\n");
 			generateTest(crNlMessage, "\\r\\n");
-		}
+
+            generateTest(nlOnlyMessage, "\\r", includeHashHeader: false);
+            generateTest(crOnlyMessage, "\\n", includeHashHeader: false);
+            generateTest(crNlMessage, "\\r\\n", includeHashHeader: false);
+        }
 
 		[Test]
 		public void TestFunction()

From f939a076cfd1918a949f67baf0be457707973858 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Tue, 12 Mar 2024 18:34:39 +0100
Subject: [PATCH 20/37] Review PgpSecretKey class

 * Unprotected v6 keys does not use the two-octets checksum https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#table-2
 * In DoCopyWithNewPassword rewrite Checksum using SHA-1 instead of the deprecated UsageChecksum (MalleableCFB)
 * Some code deduplication and reorganization
---
 crypto/src/bcpg/SecretKeyPacket.cs            |   4 +-
 crypto/src/openpgp/PgpSecretKey.cs            | 312 +++++++++++-------
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  |  24 +-
 .../test/src/openpgp/test/PgpKeyRingTest.cs   |   2 +-
 4 files changed, 209 insertions(+), 133 deletions(-)

diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index 985fc28cf9..001229647b 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -263,9 +263,9 @@ public SymmetricKeyAlgorithmTag EncAlgorithm
 			get { return encAlgorithm; }
         }
 
-        public AeadAlgorithmTag GetAeadAlgorithm()
+        public AeadAlgorithmTag AeadAlgorithm
         {
-            return aeadAlgorithm;
+            get { return aeadAlgorithm; }
         }
 
         public int S2kUsage
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index b2a3d1a939..24e7173853 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -25,6 +25,8 @@ public class PgpSecretKey
         private readonly SecretKeyPacket	secret;
         private readonly PgpPublicKey		pub;
 
+        #region "Internal constructors"
+
         internal PgpSecretKey(
             SecretKeyPacket	secret,
             PgpPublicKey	pub)
@@ -33,13 +35,29 @@ internal PgpSecretKey(
             this.pub = pub;
         }
 
+        internal PgpSecretKey(
+            PgpPrivateKey               privKey,
+            PgpPublicKey                pubKey,
+            SymmetricKeyAlgorithmTag    encAlgorithm,
+            byte[]                      rawPassPhrase,
+            bool                        clearPassPhrase,
+            bool                        useSha1,
+            SecureRandom                rand,
+            bool                        isMasterKey)
+            :this(privKey, pubKey, encAlgorithm, 0, rawPassPhrase, clearPassPhrase,
+                 useSha1 ? SecretKeyPacket.UsageSha1 : SecretKeyPacket.UsageChecksum,
+                 rand, isMasterKey)
+        {
+        }
+
         internal PgpSecretKey(
             PgpPrivateKey				privKey,
             PgpPublicKey				pubKey,
             SymmetricKeyAlgorithmTag	encAlgorithm,
+            AeadAlgorithmTag            aeadAlgorithm,
             byte[]						rawPassPhrase,
             bool                        clearPassPhrase,
-            bool						useSha1,
+            int						    s2kUsage,
             SecureRandom				rand,
             bool						isMasterKey)
         {
@@ -132,25 +150,53 @@ internal PgpSecretKey(
 
             try
             {
-                MemoryStream bOut = new MemoryStream();
-                BcpgOutputStream pOut = new BcpgOutputStream(bOut);
+                byte[] keyData = secKey.GetEncoded();
 
-                pOut.WriteObject(secKey);
+                if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
+                {
+                    s2kUsage = SecretKeyPacket.UsageNone;
+                }
 
-                byte[] keyData = bOut.ToArray();
-                byte[] checksumData = Checksum(useSha1, keyData, keyData.Length);
+                // RFC 4880 � 5.5.3 + "RFC 4880bis" � 5.5.3 + Crypto-refresh � 5.5.3. 
+                // v6       keys with UsageNone:    No checksum
+                // v5 v6    keys with MalleableCFB: Not allowed
+                // v3 v4 v5 keys with UsageNone:    two-octet checksum
+                // v3 v4    keys with MalleableCFB: two-octet checksum
+                // all      keys with UsageSha1:    Sha1 checksum
+                // all      keys with UsageAead:    No checksum (use the auth tag of the AEAD algo)
+                if (pub.Version > PublicKeyPacket.Version4 && s2kUsage == SecretKeyPacket.UsageChecksum)
+                {
+                    throw new PgpException($"A version {pub.Version} key MUST NOT use MalleableCFB S2K Usage");
+                }
+                else
+                {
+                    byte[] checksumData = Array.Empty<byte>();
+
+                    if (s2kUsage == SecretKeyPacket.UsageSha1)
+                    {
+                        checksumData = Checksum(true, keyData, keyData.Length);
+                    }
+                    else if (s2kUsage == SecretKeyPacket.UsageNone && pub.Version != PublicKeyPacket.Version6)
+                    {
+                        checksumData = Checksum(false, keyData, keyData.Length);
+                    }
+                    else if (s2kUsage == SecretKeyPacket.UsageChecksum)
+                    {
+                        checksumData = Checksum(false, keyData, keyData.Length);
+                    }
 
-                keyData = Arrays.Concatenate(keyData, checksumData);
+                    keyData = Arrays.Concatenate(keyData, checksumData);
+                }
 
                 if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
                 {
                     if (isMasterKey)
                     {
-                        this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, keyData);
+                        this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, null, null, keyData);
                     }
                     else
                     {
-                        this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, keyData);
+                        this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, null, null, keyData);
                     }
                 }
                 else
@@ -168,17 +214,13 @@ internal PgpSecretKey(
                         encData = EncryptKeyDataV3(keyData, encAlgorithm, rawPassPhrase, clearPassPhrase, 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);
+                        this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, encData);
                     }
                     else
                     {
-                        this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+                        this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, encData);
                     }
                 }
             }
@@ -192,6 +234,97 @@ internal PgpSecretKey(
             }
         }
 
+
+        internal PgpSecretKey(
+            int certificationLevel,
+            PgpKeyPair keyPair,
+            string id,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            byte[] rawPassPhrase,
+            bool clearPassPhrase,
+            bool useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom rand)
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, rand),
+                encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
+        {
+        }
+
+        internal PgpSecretKey(
+            int certificationLevel,
+            PgpKeyPair keyPair,
+            string id,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            HashAlgorithmTag hashAlgorithm,
+            byte[] rawPassPhrase,
+            bool clearPassPhrase,
+            bool useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom rand)
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm, rand),
+                encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
+        {
+        }
+
+        private static PgpPublicKey CertifiedPublicKey(
+            int certificationLevel,
+            PgpKeyPair keyPair,
+            string id,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom rand)
+        {
+            return CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, HashAlgorithmTag.Sha1, rand);
+        }
+
+
+        private static PgpPublicKey CertifiedPublicKey(
+            int certificationLevel,
+            PgpKeyPair keyPair,
+            string id,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            HashAlgorithmTag hashAlgorithm,
+            SecureRandom rand)
+        {
+            PgpSignatureGenerator sGen;
+            try
+            {
+                sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, hashAlgorithm);
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Creating signature generator: " + e.Message, e);
+            }
+
+            // TODO For v6 keys, the User Id should be optional and information about
+            // the primary Public-Key (key flags, key expiration, features, algorithm
+            // preferences, etc.) should be stored in a direct key signature over the
+            // Public-Key instead of in a User ID self-signature.
+
+            //
+            // Generate the certification
+            //
+            sGen.InitSign(certificationLevel, keyPair.PrivateKey, rand);
+
+            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);
+            }
+        }
+
+        #endregion
+
         /// <remarks>
         /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
         /// the historical behaviour of the library (1.7 and earlier).
@@ -248,22 +381,6 @@ public PgpSecretKey(
         {
         }
 
-        internal PgpSecretKey(
-            int							certificationLevel,
-            PgpKeyPair					keyPair,
-            string						id,
-            SymmetricKeyAlgorithmTag	encAlgorithm,
-            byte[]						rawPassPhrase,
-            bool                        clearPassPhrase,
-            bool						useSha1,
-            PgpSignatureSubpacketVector	hashedPackets,
-            PgpSignatureSubpacketVector	unhashedPackets,
-            SecureRandom				rand)
-            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, rand),
-                encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
-        {
-        }
-
         /// <remarks>
         /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
         /// the historical behaviour of the library (1.7 and earlier).
@@ -323,99 +440,6 @@ public PgpSecretKey(
         {
         }
 
-        internal PgpSecretKey(
-            int                         certificationLevel,
-            PgpKeyPair                  keyPair,
-            string                      id,
-            SymmetricKeyAlgorithmTag    encAlgorithm,
-            HashAlgorithmTag            hashAlgorithm,
-            byte[]                      rawPassPhrase,
-            bool                        clearPassPhrase,
-            bool                        useSha1,
-            PgpSignatureSubpacketVector hashedPackets,
-            PgpSignatureSubpacketVector unhashedPackets,
-            SecureRandom                rand)
-            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm, rand),
-                encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
-        {
-        }
-
-        private static PgpPublicKey CertifiedPublicKey(
-            int							certificationLevel,
-            PgpKeyPair					keyPair,
-            string						id,
-            PgpSignatureSubpacketVector	hashedPackets,
-            PgpSignatureSubpacketVector	unhashedPackets,
-            SecureRandom rand)
-        {
-            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, rand);
-
-            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);
-            }
-        }
-
-
-        private static PgpPublicKey CertifiedPublicKey(
-            int certificationLevel,
-            PgpKeyPair keyPair,
-            string id,
-            PgpSignatureSubpacketVector hashedPackets,
-            PgpSignatureSubpacketVector unhashedPackets,
-            HashAlgorithmTag hashAlgorithm,
-            SecureRandom rand)
-        {
-            PgpSignatureGenerator sGen;
-            try
-            {
-                sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, hashAlgorithm);
-            }
-            catch (Exception e)
-            {
-                throw new PgpException("Creating signature generator: " + e.Message, e);
-            }
-
-            //
-            // Generate the certification
-            //
-            sGen.InitSign(certificationLevel, keyPair.PrivateKey, rand);
-
-            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,
@@ -505,6 +529,12 @@ public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm
             get { return secret.EncAlgorithm; }
         }
 
+        /// <summary>The AEAD algorithm the key is encrypted with.</summary>
+        public AeadAlgorithmTag KeyEncryptionAeadAlgorithm
+        {
+            get { return secret.AeadAlgorithm; }
+        }
+
         /// <summary>The key ID of the public key associated with this key.</summary>
         public long KeyId
         {
@@ -1020,7 +1050,14 @@ internal static PgpSecretKey DoCopyWithNewPassword(
             if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null)
             {
                 s2kUsage = SecretKeyPacket.UsageNone;
-                if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1)   // SHA-1 hash, need to rewrite Checksum
+
+                // v6 keys with UsageNone don't use checksums
+                if (key.PublicKey.Version == PublicKeyPacket.Version6)
+                {
+                    keyData = new byte[rawKeyData.Length - 2];
+                    Array.Copy(rawKeyData, 0, keyData, 0, keyData.Length - 2);
+                }
+                else if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1)   // SHA-1 hash, need to rewrite Checksum
                 {
                     keyData = new byte[rawKeyData.Length - 18];
 
@@ -1040,13 +1077,34 @@ internal static PgpSecretKey DoCopyWithNewPassword(
             {
                 if (s2kUsage == SecretKeyPacket.UsageNone)
                 {
-                    s2kUsage = SecretKeyPacket.UsageChecksum;
+                    if (key.PublicKey.Version == PublicKeyPacket.Version6)
+                    {
+                        // v6 keys with UsageNone don't use checksums
+                    }
+                    else if (key.PublicKey.Version == PublicKeyPacket.Version3)
+                    {
+                        // for backward compatibility with legacy v3 keys use the deprecated UsageChecksum (MalleableCFB)
+                        s2kUsage = SecretKeyPacket.UsageChecksum;
+                    }
+                    else
+                    {
+                        // rewrite Checksum using SHA-1 instead of the deprecated UsageChecksum (MalleableCFB)
+                        s2kUsage = SecretKeyPacket.UsageSha1;
+                        byte[] check = Checksum(true, rawKeyData, rawKeyData.Length - 2);
+                        byte[] newKeyData = new byte[rawKeyData.Length - 2 + 20];
+                        Array.Copy(rawKeyData, 0, newKeyData, 0, rawKeyData.Length - 2);
+                        Array.Copy(check, 0, newKeyData, rawKeyData.Length - 2, check.Length);
+
+                        rawKeyData = newKeyData;
+                    }
+
                 }
 
                 try
                 {
-                    if (pubKeyPacket.Version >= 4)
+                    if (pubKeyPacket.Version >= PublicKeyPacket.Version4)
                     {
+                        // TODO for v6 use AEAD when implemented
                         keyData = EncryptKeyDataV4(rawKeyData, newEncAlgorithm, HashAlgorithmTag.Sha1, rawNewPassPhrase, clearPassPhrase, rand, out s2k, out iv);
                     }
                     else
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 14ffc27217..fa25de35b6 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -358,6 +358,12 @@ public void Version6Ed25519KeyPairCreationTest()
 
             // Encode-Decode roundtrip
             SecureRandom rand = new SecureRandom();
+
+            PgpSignatureSubpacketGenerator spgen = new PgpSignatureSubpacketGenerator();
+            spgen.SetPreferredHashAlgorithms(true, new int[] { (int)HashAlgorithmTag.Sha512, (int)HashAlgorithmTag.Sha256 });
+            spgen.SetPreferredSymmetricAlgorithms(true, new int[] { (int)SymmetricKeyAlgorithmTag.Aes256, (int)SymmetricKeyAlgorithmTag.Aes128 });
+            PgpSignatureSubpacketVector hashed = spgen.Generate();
+
             string uid = "Alice <alice@example.com>";
             PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
                 PgpSignature.PositiveCertification,
@@ -365,8 +371,8 @@ public void Version6Ed25519KeyPairCreationTest()
                 uid,
                 SymmetricKeyAlgorithmTag.Null,
                 Array.Empty<char>(),
-                true,
-                null,
+                false,
+                hashed,
                 null,
                 rand);
 
@@ -378,7 +384,11 @@ public void Version6Ed25519KeyPairCreationTest()
             keyRingGen.AddSubKey(keypair);
 
             PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+            
             byte[] encodedsecring = secring.GetEncoded();
+            // expected length of v6 unencrypted Ed25519 secret key packet: 75 octets
+            FailIf("unexpected packet length", encodedsecring[1] != 75);
+
             PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
 
             PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
@@ -461,7 +471,7 @@ public void Version6Ed448KeyPairCreationTest()
                 uid,
                 SymmetricKeyAlgorithmTag.Null,
                 Array.Empty<char>(),
-                true,
+                false,
                 null,
                 null,
                 rand);
@@ -474,7 +484,11 @@ public void Version6Ed448KeyPairCreationTest()
             keyRingGen.AddSubKey(keypair);
 
             PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+
             byte[] encodedsecring = secring.GetEncoded();
+            // expected length of v6 unencrypted Ed448 secret key packet: 125 octets
+            FailIf("unexpected packet length", encodedsecring[1] != 125);
+
             PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
 
             PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
@@ -545,11 +559,15 @@ public void Version6LockedSecretKeyParsingTest()
 
             // signing key
             PgpSecretKey signingKey = secretKeys[0];
+            IsEquals(signingKey.KeyEncryptionAlgorithm, SymmetricKeyAlgorithmTag.Aes256);
+            IsEquals(signingKey.KeyEncryptionAeadAlgorithm, AeadAlgorithmTag.Ocb);
             IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
             IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
 
             // encryption key
             PgpSecretKey encryptionKey = secretKeys[1];
+            IsEquals(encryptionKey.KeyEncryptionAlgorithm, SymmetricKeyAlgorithmTag.Aes256);
+            IsEquals(encryptionKey.KeyEncryptionAeadAlgorithm, AeadAlgorithmTag.Ocb);
             IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
             IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
 
diff --git a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
index 821a7f2952..6b2cc3e704 100644
--- a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
+++ b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
@@ -2440,7 +2440,7 @@ public void RewrapTest()
                     // this should succeed
                     PgpPrivateKey privTmp = pgpKey.ExtractPrivateKey(newPass);
 
-                    if (pgpKey.KeyId != oldKeyID || pgpKey.S2kUsage != SecretKeyPacket.UsageChecksum)
+                    if (pgpKey.KeyId != oldKeyID || pgpKey.S2kUsage != SecretKeyPacket.UsageSha1)
                     {
                         Fail("usage/key ID mismatch");
                     }

From e0ce83b74dca1e442863b731631e1c5c7d7cc5ea Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sat, 13 Apr 2024 15:44:05 +0200
Subject: [PATCH 21/37] SKESK v6 decryption

---
 crypto/src/bcpg/AeadInputStream.cs            | 182 ++++++++++++++++++
 crypto/src/bcpg/AeadUtils.cs                  |  35 +++-
 .../src/bcpg/SymmetricEncIntegrityPacket.cs   |  73 ++++++-
 .../src/bcpg/SymmetricKeyEncSessionPacket.cs  | 140 ++++++++++++--
 crypto/src/openpgp/PgpPbeEncryptedData.cs     | 167 ++++++++++++++--
 crypto/src/openpgp/PgpUtilities.cs            |  31 +++
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 134 +++++++++++++
 7 files changed, 725 insertions(+), 37 deletions(-)
 create mode 100644 crypto/src/bcpg/AeadInputStream.cs

diff --git a/crypto/src/bcpg/AeadInputStream.cs b/crypto/src/bcpg/AeadInputStream.cs
new file mode 100644
index 0000000000..39a5918341
--- /dev/null
+++ b/crypto/src/bcpg/AeadInputStream.cs
@@ -0,0 +1,182 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    internal class AeadInputStream
+        : BaseInputStream
+    {
+        // ported from bc-java
+
+        private readonly Stream inputStream;
+        private readonly byte[] buf;
+        private readonly BufferedAeadBlockCipher cipher;
+        private readonly KeyParameter secretKey;
+        private readonly byte[] aaData;
+        private readonly byte[] iv;
+        private readonly int chunkLength;
+        private readonly int tagLen;
+
+        private byte[] data;
+        private int dataOff;
+        private long chunkIndex = 0;
+        private long totalBytes = 0;
+
+        private static int GetChunkLength(int chunkSize)
+        {
+            return 1 << (chunkSize + 6);
+        }
+
+        public AeadInputStream(
+            Stream inputStream,
+            BufferedAeadBlockCipher cipher,
+            KeyParameter secretKey,
+            byte[] iv,
+            AeadAlgorithmTag aeadAlgorithm,
+            int chunkSize,
+            byte[] aaData)
+        {
+            this.inputStream = inputStream;
+            this.cipher = cipher;
+            this.secretKey = secretKey;
+            this.aaData = aaData;
+            this.iv = iv;
+
+            chunkLength = GetChunkLength(chunkSize);
+            tagLen = AeadUtils.GetAuthTagLength(aeadAlgorithm);
+
+            buf = new byte[chunkLength + tagLen + tagLen]; // allow room for chunk tag and message tag
+
+            Streams.ReadFully(inputStream, buf, 0, tagLen + tagLen);
+
+            // load the first block
+            data = ReadBlock();
+            dataOff = 0;
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            Streams.ValidateBufferArguments(buffer, offset, count);
+
+            if (count == 0)
+            {
+                return 0;
+            }
+
+            if (data != null && dataOff == data.Length)
+            {
+                data = ReadBlock();
+                dataOff = 0;
+            }
+
+            if (data == null)
+            {
+                return 0;
+            }
+
+            int available = data.Length - dataOff;
+            int supplyLen = count < available ? count : available;
+            Array.Copy(data, dataOff, buffer, offset, supplyLen);
+            dataOff += supplyLen;
+
+            return supplyLen;
+        }
+
+        private static byte[] GetNonce(byte[] iv, long chunkIndex)
+        {
+            byte[] nonce = Arrays.Clone(iv);
+            byte[] chunkid =  Pack.UInt64_To_BE((ulong)chunkIndex);
+            Array.Copy(chunkid, 0, nonce, nonce.Length - 8, 8);
+
+            return nonce;
+        }
+
+        private static byte[] GetAdata(bool isV5StyleAead, byte[] aaData, long chunkIndex, long totalBytes)
+        {
+            byte[] adata;
+            if (isV5StyleAead)
+            {
+                adata = new byte[13];
+                Array.Copy(aaData, 0, adata, 0, aaData.Length);
+                Array.Copy(Pack.UInt64_To_BE((ulong)chunkIndex), 0, adata, aaData.Length, 8);
+            }
+            else
+            {
+                adata = new byte[aaData.Length + 8];
+                Array.Copy(aaData, 0, adata, 0, aaData.Length);
+                Array.Copy(Pack.UInt64_To_BE((ulong)totalBytes), 0, adata, aaData.Length, 8);
+            }
+            return adata;
+        }
+
+        private byte[] ReadBlock()
+        {
+            int dataLen = Streams.ReadFully(inputStream, buf, tagLen + tagLen, chunkLength);
+            if (dataLen == 0)
+            {
+                return null;
+            }
+
+            byte[] adata = Arrays.Clone(aaData);
+            byte[] decData = new byte[dataLen];
+
+            try
+            {
+                AeadParameters aeadParams = new AeadParameters(
+                    secretKey,
+                    8 * tagLen,
+                    GetNonce(iv, chunkIndex),
+                    adata);
+
+                cipher.Init(false, aeadParams);
+
+                int len = cipher.ProcessBytes(buf, 0, dataLen + tagLen, decData, 0);
+
+                cipher.DoFinal(decData, len);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                throw new IOException($"exception processing chunk {chunkIndex}: {e.Message}");
+            }
+
+            totalBytes += decData.Length;
+            chunkIndex++;
+            Array.Copy(buf, dataLen + tagLen, buf, 0, tagLen); // copy back the "tag"
+
+            if (dataLen != chunkLength)
+            {
+                // last block
+                try
+                {
+                    adata = GetAdata(false, aaData, chunkIndex, totalBytes);
+                    AeadParameters aeadParams = new AeadParameters(
+                        secretKey,
+                        8 * tagLen,
+                        GetNonce(iv, chunkIndex),
+                        adata);
+
+                    cipher.Init(false, aeadParams);
+
+                    cipher.ProcessBytes(buf, 0, tagLen, buf, 0);
+
+                    cipher.DoFinal(buf, 0); // check final tag
+                }
+                catch (InvalidCipherTextException e)
+                {
+                    throw new IOException($"exception processing final tag: {e.Message}");
+                }
+            }
+            else
+            {
+                Streams.ReadFully(inputStream, buf, tagLen, tagLen);   // read the next tag bytes
+            }
+
+            return decData;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/AeadUtils.cs b/crypto/src/bcpg/AeadUtils.cs
index e7e63c74ee..d2bda51e8b 100644
--- a/crypto/src/bcpg/AeadUtils.cs
+++ b/crypto/src/bcpg/AeadUtils.cs
@@ -28,6 +28,28 @@ public static int GetIVLength(AeadAlgorithmTag aeadAlgorithmTag)
             }
         }
 
+        /// <summary>
+        /// Returns the name of the AEAD Algorithm
+        /// </summary>
+        /// <param name="aeadAlgorithmTag">AEAD algorithm identifier</param>
+        /// <returns>name of the AEAD Algorithm</returns>
+        /// <exception cref="ArgumentException"></exception>
+
+        public static string GetAeadAlgorithmName(AeadAlgorithmTag aeadAlgorithmTag)
+        {
+            switch (aeadAlgorithmTag)
+            {
+                case AeadAlgorithmTag.Eax:
+                    return "EAX";
+                case AeadAlgorithmTag.Ocb:
+                    return "OCB";
+                case AeadAlgorithmTag.Gcm:
+                    return "GCM";
+                default:
+                    throw new ArgumentException($"Invalid AEAD algorithm tag: {aeadAlgorithmTag}");
+            }
+        }
+
         /// <summary>
         /// Return the length of the authentication tag used by the given AEAD algorithm in octets.
         /// </summary>
@@ -52,23 +74,22 @@ public static int GetAuthTagLength(AeadAlgorithmTag aeadAlgorithmTag)
         /// two separate byte arrays.
         /// m is the key length of the cipher algorithm, while n is the IV length of the AEAD algorithm.
         /// Note, that the IV is filled with <pre>n-8</pre> bytes only, the remainder is left as 0s.
-        /// Return an array of both arrays with the key and index 0 and the IV at index 1.
         /// </summary>
         /// <param name="messageKeyAndIv">m+n-8 bytes of concatenated message key and IV</param>
         /// <param name="cipherAlgo">symmetric cipher algorithm</param>
         /// <param name="aeadAlgo">AEAD algorithm</param>
-        /// <returns>array of arrays containing message key and IV</returns>
-        public static byte[][] SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgorithmTag cipherAlgo, AeadAlgorithmTag aeadAlgo)
+        /// <param name="messageKey">Message key</param>
+        /// <param name="iv">IV</param>
+        public static void SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgorithmTag cipherAlgo, AeadAlgorithmTag aeadAlgo, out byte[] messageKey, out byte[] iv)
         {
             int keyLen = PgpUtilities.GetKeySizeInOctets(cipherAlgo);
             int ivLen = GetIVLength(aeadAlgo);
-            byte[] messageKey = new byte[keyLen];
-            byte[] iv = new byte[ivLen];
+
+            messageKey = new byte[keyLen];
+            iv = new byte[ivLen];
 
             Array.Copy(messageKeyAndIv, messageKey, messageKey.Length);
             Array.Copy(messageKeyAndIv, messageKey.Length, iv, 0, ivLen-8);
-
-            return new byte[][] { messageKey, iv };
         }
     }
 }
\ No newline at end of file
diff --git a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
index 1de72a4a02..e30da6f1e4 100644
--- a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
@@ -1,3 +1,4 @@
+using Org.BouncyCastle.Utilities;
 using System;
 using System.IO;
 
@@ -6,13 +7,81 @@ namespace Org.BouncyCastle.Bcpg
 	public class SymmetricEncIntegrityPacket
 		: InputStreamPacket
 	{
-		internal readonly int version;
+        /// <summary>
+        /// Version 3 SEIPD packet.
+        /// </summary>
+        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-symmetrically-encrypted-int"/>
+        public const int Version1 = 1;
+        /// <summary>
+        /// Version 2 SEIPD packet.
+        /// </summary>
+		/// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-symmetrically-encrypted-int"/>
+        public const int Version2 = 2;
 
-		internal SymmetricEncIntegrityPacket(
+        private readonly int version;                               // V1, V2
+        private readonly SymmetricKeyAlgorithmTag cipherAlgorithm;  // V2 Only
+        private readonly AeadAlgorithmTag aeadAlgorithm;            // V2 Only
+        private readonly int chunkSize;                             // V2 Only
+        private readonly byte[] salt;                               // V2 Only
+
+        internal SymmetricEncIntegrityPacket(
 			BcpgInputStream bcpgIn)
 			: base(bcpgIn, PacketTag.SymmetricEncryptedIntegrityProtected)
         {
 			version = bcpgIn.ReadByte();
+            if (version == Version2)
+            {
+                cipherAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
+                aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.ReadByte();
+                chunkSize = bcpgIn.ReadByte();
+
+                salt = new byte[32];
+                if (bcpgIn.Read(salt, 0, 32) != salt.Length)
+                {
+                    throw new IOException("Premature end of stream.");
+                }
+            }
+        }
+
+        public int Version
+        {
+            get { return version; }
+        }
+
+        public SymmetricKeyAlgorithmTag CipherAlgorithm
+        {
+            get { return cipherAlgorithm; }
+        }
+
+        public AeadAlgorithmTag AeadAlgorithm
+        {
+            get { return aeadAlgorithm; }
+        }
+
+        public int ChunkSize
+        {
+            get { return chunkSize; }
+        }
+
+        public byte[] GetSalt()
+        {
+            return Arrays.Clone(salt);
+        }
+
+        internal byte[] GetAAData()
+        {
+            return CreateAAData(Tag, Version, cipherAlgorithm, aeadAlgorithm, chunkSize);
+        }
+
+        internal static byte[] CreateAAData(PacketTag tag, int version, SymmetricKeyAlgorithmTag cipherAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+        {
+            return new byte[]{
+                (byte)(0xC0 | (byte)tag),
+                (byte)version,
+                (byte)cipherAlgorithm,
+                (byte)aeadAlgorithm,
+                (byte)chunkSize
+            };
         }
     }
 }
diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
index f053b431aa..215ff98d0a 100644
--- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
@@ -1,4 +1,6 @@
+using Org.BouncyCastle.Utilities;
 using System;
+using System.Data.SqlTypes;
 using System.IO;
 
 namespace Org.BouncyCastle.Bcpg
@@ -9,30 +11,85 @@ namespace Org.BouncyCastle.Bcpg
     public class SymmetricKeyEncSessionPacket
         : ContainedPacket
     {
+        /// <summary>
+        /// Version 4 SKESK packet.
+        /// Used only with V1 SEIPD or SED packets.
+        /// </summary>
+        public const int Version4 = 4;
+
+        /// <summary>
+        /// Version 5 SKESK packet.
+        /// Used only with AEADEncDataPacket AED packets.
+        /// Defined in retired "RFC4880-bis" draft
+        /// </summary>
+        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#name-symmetric-key-encrypted-ses"/>
+        public const int Version5 = 5;
+
+        /// <summary>
+        /// Version 6 SKESK packet.
+        /// Used only with V2 SEIPD packets.
+        /// </summary>
+        public const int Version6 = 6;
+
         private readonly int version;
         private readonly SymmetricKeyAlgorithmTag encAlgorithm;
         private readonly S2k s2k;
         private readonly byte[] secKeyData;
 
+        private readonly byte[] s2kBytes;
+        private readonly AeadAlgorithmTag aeadAlgorithm;
+        private readonly byte[] iv;
+
         public SymmetricKeyEncSessionPacket(
             BcpgInputStream bcpgIn)
             :base(PacketTag.SymmetricKeyEncryptedSessionKey)
         {
             version = bcpgIn.ReadByte();
-            encAlgorithm = (SymmetricKeyAlgorithmTag) bcpgIn.ReadByte();
 
-            s2k = new S2k(bcpgIn);
+            switch (version)
+            {
+                case Version4:
+                    encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
+                    s2k = new S2k(bcpgIn);
+                    secKeyData = bcpgIn.ReadAll();
+                    break;
+
+                case Version5:
+                case Version6:
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-symmetric-key-enc
+                    // SymmAlgo + AEADAlgo + S2KCount + S2K + IV
+                    int next5Fields5Count = bcpgIn.ReadByte();
+                    encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
+                    aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.ReadByte();
+
+                    int s2kOctetCount = bcpgIn.ReadByte();
+                    s2kBytes = new byte[s2kOctetCount];
+                    bcpgIn.ReadFully(s2kBytes);
+                    s2k = new S2k(new MemoryStream(s2kBytes));
+
+                    int ivsize = AeadUtils.GetIVLength(aeadAlgorithm);
+                    iv = new byte[ivsize];
+                    bcpgIn.ReadFully(iv);
 
-            secKeyData = bcpgIn.ReadAll();
+                    // contains both the encrypted session key and the AEAD authentication tag
+                    secKeyData = bcpgIn.ReadAll();
+                    break;
+            }
         }
 
-		public SymmetricKeyEncSessionPacket(
-            SymmetricKeyAlgorithmTag	encAlgorithm,
+        /// <summary>
+        /// Create a v4 SKESK packet.
+        /// </summary>
+        /// <param name="encAlgorithm">symmetric encryption algorithm</param>
+        /// <param name="s2k">s2k specifier</param>
+        /// <param name="secKeyData">encrypted session key</param>
+        public SymmetricKeyEncSessionPacket(
+            SymmetricKeyAlgorithmTag    encAlgorithm,
             S2k							s2k,
             byte[]						secKeyData)
             : base(PacketTag.SymmetricKeyEncryptedSessionKey)
         {
-            this.version = 4;
+            this.version = Version4;
             this.encAlgorithm = encAlgorithm;
             this.s2k = s2k;
             this.secKeyData = secKeyData;
@@ -46,6 +103,11 @@ public SymmetricKeyAlgorithmTag EncAlgorithm
 			get { return encAlgorithm; }
         }
 
+        public AeadAlgorithmTag AeadAlgorithm
+        {
+            get { return aeadAlgorithm; }
+        }
+
         /**
         * @return S2k
         */
@@ -70,21 +132,69 @@ public int Version
 			get { return version; }
         }
 
-        public override void Encode(BcpgOutputStream bcpgOut)
+        internal byte[] GetAAData()
         {
-            MemoryStream bOut = new MemoryStream();
-            using (var pOut = new BcpgOutputStream(bOut))
+            return CreateAAData(Tag, Version, EncAlgorithm, AeadAlgorithm);
+        }
+
+        internal static byte[] CreateAAData(PacketTag tag, int version, SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
+        {
+            return new byte[]
             {
-                pOut.Write((byte)version, (byte)encAlgorithm);
-                pOut.WriteObject(s2k);
+                (byte)(0xC0 | (byte)tag),
+                (byte)version,
+                (byte)encAlgorithm,
+                (byte)aeadAlgorithm
+            };
+        }
+
+        internal byte[] GetAeadIV()
+        {
+            return Arrays.Clone(iv);
+        }
 
-                if (secKeyData != null && secKeyData.Length > 0)
+        public override void Encode(BcpgOutputStream bcpgOut)
+        {
+            using (MemoryStream bOut = new MemoryStream())
+            {
+                using (var pOut = new BcpgOutputStream(bOut))
                 {
-                    pOut.Write(secKeyData);
+                    pOut.WriteByte((byte)version);
+
+                    if (version == Version4)
+                    {
+                        pOut.WriteByte((byte)encAlgorithm);
+                        pOut.WriteObject(s2k);
+
+                        if (secKeyData != null && secKeyData.Length > 0)
+                        {
+                            pOut.Write(secKeyData);
+                        }
+                    }
+                    else if (version == Version5 || version == Version6)
+                    {
+                        var s2kenc = s2k.GetEncoded();
+                        int s2kLen = s2kenc.Length;
+
+                        // len of 5 following fields
+                        int count = 1 + 1 + 1 + s2kLen + iv.Length;
+                        pOut.WriteByte((byte)count);
+
+                        pOut.WriteByte((byte)encAlgorithm);
+                        pOut.WriteByte((byte)aeadAlgorithm);
+                        pOut.WriteByte((byte)s2kLen);
+                        pOut.Write(s2kenc);
+                        pOut.Write(iv);
+
+                        if (secKeyData != null && secKeyData.Length > 0)
+                        {
+                            pOut.Write(secKeyData);
+                        }
+                    }
                 }
-            }
 
-			bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray());
+                bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray());
+            }
         }
     }
 }
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index 1c3432d892..91bec3fd0c 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -1,15 +1,15 @@
-using System;
-using System.IO;
-
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.IO;
+using System;
+using System.IO;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>A password based encryption object.</remarks>
+    /// <remarks>A password based encryption object.</remarks>
     public class PgpPbeEncryptedData
         : PgpEncryptedData
     {
@@ -21,10 +21,63 @@ internal PgpPbeEncryptedData(
 			: base(encData)
 		{
 			this.keyData = keyData;
-		}
+            EnforceConstraints();
+        }
 
-		/// <summary>Return the raw input stream for the data stream.</summary>
-		public override Stream GetInputStream()
+        private void EnforceConstraints()
+        {
+            switch (keyData.Version)
+            {
+                case SymmetricKeyEncSessionPacket.Version4:
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-4-symmetric-key-enc
+                    // A version 4 SKESK packet precedes a version 1 SEIPD packet. In historic data, it is sometimes found
+                    // preceding a deprecated SED packet. A v4 SKESK packet MUST NOT precede a v2 SEIPD packet.
+                    if (encData is SymmetricEncDataPacket)
+                    {
+                        return;
+                    }
+
+                    if (encData is SymmetricEncIntegrityPacket seipd1)
+                    {
+                        if (seipd1.Version == SymmetricEncIntegrityPacket.Version1)
+                        {
+                            return;
+                        }
+
+                        // V2 SEIPD cannot be preceded by V4 SKESK
+                        throw new PgpException($"Version 4 SKESK cannot precede SEIPD of version {seipd1.Version}");
+                    }
+                    break;
+
+                case SymmetricKeyEncSessionPacket.Version5:
+                    // https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-01.html does not state any constraints
+                    break;
+
+                case SymmetricKeyEncSessionPacket.Version6:
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-symmetric-key-enc
+                    // A version 6 SKESK packet precedes a version 2 SEIPD packet. A v6 SKESK packet MUST NOT precede a v1 SEIPD
+                    // packet or a deprecated Symmetrically Encrypted Data.
+                    if (encData is SymmetricEncDataPacket)
+                    {
+                        throw new PgpException("Version 6 SKESK MUST NOT precede a deprecated SED packet.");
+                    }
+
+                    if (encData is SymmetricEncIntegrityPacket seipd2)
+                    {
+                        if (seipd2.Version == SymmetricEncIntegrityPacket.Version2)
+                        {
+                            return;
+                        }
+                        throw new PgpException($"Version 6 PKESK cannot precede SEIPD of version {seipd2.Version}");
+                    }
+                    break;
+                default:
+                    throw new UnsupportedPacketVersionException($"Unsupported PGP secret key encrypted session key packet version encountered: {keyData.Version}");
+            }
+        }
+
+        /// <summary>Return the raw input stream for the data stream.</summary>
+        public override Stream GetInputStream()
 		{
 			return encData.GetInputStream();
 		}
@@ -57,7 +110,7 @@ public Stream GetDataStreamRaw(byte[] rawPassPhrase)
             return DoGetDataStream(rawPassPhrase, false);
         }
 
-        internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
+        private Stream DoGetDataStreamVersion1(byte[] rawPassPhrase, bool clearPassPhrase)
         {
 			try
 			{
@@ -130,7 +183,6 @@ internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
 					throw new PgpDataValidationException("quick check failed.");
 				}
 
-
 				return encStream;
 			}
 			catch (PgpException)
@@ -143,17 +195,106 @@ internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
 			}
 		}
 
-		private IBufferedCipher CreateStreamCipher(
+        private static KeyParameter DeriveVersion6SessionKey(SymmetricKeyEncSessionPacket keyData, byte[] rawPassPhrase, bool clearPassPhrase)
+        {
+            var keyAlgorithm = keyData.EncAlgorithm;
+            var aeadAlgo = keyData.AeadAlgorithm;
+            var aeadIV = keyData.GetAeadIV();
+            var hkdfInfo = keyData.GetAAData();
+            var secKeyData = keyData.GetSecKeyData();
+
+            var keyAlgoName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm);
+            var aeadAlgoName = AeadUtils.GetAeadAlgorithmName(aeadAlgo);
+            var keyCipher = CipherUtilities.GetCipher($"{keyAlgoName}/{aeadAlgoName}/NoPadding");
+
+            var key = PgpUtilities.DoMakeKeyFromPassPhrase(keyAlgorithm, keyData.S2k, rawPassPhrase, clearPassPhrase);
+
+            var hkdfParams = new HkdfParameters(key.GetKey(), Array.Empty<byte>(), hkdfInfo);
+            var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+            hkdfGen.Init(hkdfParams);
+            var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(keyAlgorithm)];
+            hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+            var aeadParams = new AeadParameters(
+                new KeyParameter(hkdfOutput),
+                8 * AeadUtils.GetAuthTagLength(aeadAlgo),
+                aeadIV,
+                hkdfInfo);
+
+            keyCipher.Init(false, aeadParams);
+            byte[] keyBytes = keyCipher.DoFinal(secKeyData);
+
+            return ParameterUtilities.CreateKeyParameter(
+                PgpUtilities.GetSymmetricCipherName(keyAlgorithm),
+                keyBytes, 0, keyBytes.Length);
+        }
+
+        private Stream DoGetDataStreamVersion2(SymmetricEncIntegrityPacket seipd, byte[] rawPassPhrase, bool clearPassPhrase)
+        {
+            try
+            {
+                KeyParameter sessionKey = DeriveVersion6SessionKey(keyData, rawPassPhrase, clearPassPhrase);
+
+                var aadata = seipd.GetAAData();
+                var salt = seipd.GetSalt();
+                PgpUtilities.DeriveAeadMessageKeyAndIv(sessionKey, seipd.CipherAlgorithm, seipd.AeadAlgorithm, salt, aadata, out var messageKey, out var iv);
+                var cipher = CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
+
+                var aeadStream = new AeadInputStream(
+                    encData.GetInputStream(),
+                    cipher,
+                    messageKey,
+                    iv,
+                    seipd.AeadAlgorithm,
+                    seipd.ChunkSize,
+                    aadata);
+
+                encStream = BcpgInputStream.Wrap(aeadStream);
+                return encStream;
+            }
+            catch (PgpException)
+            {
+                throw;
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception creating cipher", e);
+            }
+        }
+        
+        internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
+        {
+            if (encData is SymmetricEncIntegrityPacket seipd && seipd.Version == SymmetricEncIntegrityPacket.Version2)
+            {
+                return DoGetDataStreamVersion2(seipd, rawPassPhrase, clearPassPhrase);
+            }
+            else
+            {
+                return DoGetDataStreamVersion1(rawPassPhrase, clearPassPhrase);
+            }
+        }
+
+        private IBufferedCipher CreateStreamCipher(
 			SymmetricKeyAlgorithmTag keyAlgorithm)
 		{
 			string mode = (encData is SymmetricEncIntegrityPacket)
 				? "CFB"
 				: "OpenPGPCFB";
 
-			string cName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm)
-				+ "/" + mode + "/NoPadding";
+			string cName = $"{PgpUtilities.GetSymmetricCipherName(keyAlgorithm)}/{mode}/NoPadding";
 
 			return CipherUtilities.GetCipher(cName);
 		}
-	}
+
+        private static BufferedAeadBlockCipher CreateAeadCipher(
+            SymmetricKeyAlgorithmTag keyAlgorithm, AeadAlgorithmTag aeadAlgorithm)
+        {
+            string algo = PgpUtilities.GetSymmetricCipherName(keyAlgorithm);
+            string mode = AeadUtils.GetAeadAlgorithmName(aeadAlgorithm);
+            string cName = $"{algo}/{mode}/NoPadding";
+
+            return CipherUtilities.GetCipher(cName) as BufferedAeadBlockCipher;
+        }
+
+    }
 }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 08418d126a..dab25543e4 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -8,6 +8,7 @@
 using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Signers;
 using Org.BouncyCastle.Math;
@@ -277,6 +278,36 @@ public static int GetKeySizeInOctets(SymmetricKeyAlgorithmTag algorithm)
             return (GetKeySize(algorithm) + 7) / 8;
         }
 
+        /// <summary>
+        /// Derive a message key and IV from the given session key.
+        /// </summary>
+        /// <param name="sessionKey">session key</param>
+        /// <param name="encAlgorithm">symmetric cipher algorithm tag</param>
+        /// <param name="aeadAlgorithm">AEAD algorithm tag</param>
+        /// <param name="salt">salt</param>
+        /// <param name="hkdfInfo">HKDF info</param>
+        /// <param name="messageKey"></param>
+        /// <param name="iv"></param>
+        public static void DeriveAeadMessageKeyAndIv(
+            KeyParameter sessionKey,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            byte[] salt,
+            byte[] hkdfInfo,
+            out KeyParameter messageKey,
+            out byte[] iv)
+        {
+            var hkdfGen = new HkdfBytesGenerator(CreateDigest(HashAlgorithmTag.Sha256));
+            var hkdfParams = new HkdfParameters(sessionKey.GetKey(), salt, hkdfInfo);
+            hkdfGen.Init(hkdfParams);
+            var hkdfOutput = new byte[GetKeySizeInOctets(encAlgorithm) + AeadUtils.GetIVLength(aeadAlgorithm) - 8];
+            hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+            AeadUtils.SplitMessageKeyAndIv(hkdfOutput, encAlgorithm, aeadAlgorithm, out var messageKeyBytes, out iv);
+
+            messageKey = new KeyParameter(messageKeyBytes);
+        }
+
         public static KeyParameter MakeKey(
 			SymmetricKeyAlgorithmTag	algorithm,
 			byte[]						keyBytes)
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index fa25de35b6..88917149a0 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -88,6 +88,31 @@ public class PgpCryptoRefreshTest
             "b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj" +
             "FtCgazStmsuOXF9SFQE=");
 
+        // Sample AEAD encryption and decryption
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-eax-encryption-
+        // encrypts the cleartext string Hello, world! with the passphrase password, using AES-128 with AEAD-EAX encryption.
+        private readonly byte[] v6skesk_aes128_eax = Base64.Decode(
+            "w0AGHgcBCwMIpa5XnR/F2Cv/aSJPkZmTs1Bvo7WaanPP+MXvxfQcV/tU4cImgV14" +
+            "KPX5LEVOtl6+AKtZhsaObnxV0mkCBwEGn/kOOzIZZPOkKRPI3MZhkyUBUifvt+rq" +
+            "pJ8EwuZ0F11KPSJu1q/LnKmsEiwUcOEcY9TAqyQcapOK1Iv5mlqZuQu6gyXeYQR1" +
+            "QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o=");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-ocb-encryption-
+        // encrypts the cleartext string Hello, world! with the passphrase password, using AES-128 with AEAD-OCB encryption.
+        private readonly byte[] v6skesk_aes128_ocb = Base64.Decode(
+            "wz8GHQcCCwMIVqKY0vXjZFP/z8xcEWZO2520JZDX3EawckG2EsOBLP/76gDyNHsl" +
+            "ZBEj+IeuYNT9YU4IN9gZ02zSaQIHAgYgpmH3MfyaMDK1YjMmAn46XY21dI6+/wsM" +
+            "WRDQns3WQf+f04VidYA1vEl1TOG/P/+n2tCjuBBPUTPPQqQQCoPu9MobSAGohGv0" +
+            "K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw==");
+
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-gcm-encryption-
+        // encrypts the cleartext string Hello, world! with the passphrase password, using AES-128 with AEAD-GCM encryption.
+        private readonly byte[] v6skesk_aes128_gcm = Base64.Decode(
+            "wzwGGgcDCwMI6dOXhbIHAAj/tC58SD70iERXyzcmubPbn/d25fTZpAlS4kRymIUa" +
+            "v/91Jt8t1VRBdXmneZ/SaQIHAwb8uUSQvLmLvcnRBsYJAmaUD3LontwhtVlrFXax" +
+            "Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS" +
+            "+pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q==");
+
         private readonly char[] emptyPassphrase = Array.Empty<char>();
 
         [Test]
@@ -757,6 +782,114 @@ private static bool EncryptThenDecryptTest(AsymmetricCipherKeyPair alice, Asymme
             return Arrays.AreEqual(secretA, secretB);
         }
 
+        [Test]
+        public void Version6SkeskVersion2SeipdTest()
+        {
+            byte[][] messages = new byte[][]
+            {
+                v6skesk_aes128_eax,
+                v6skesk_aes128_ocb,
+                v6skesk_aes128_gcm
+            };
+
+            byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+            byte[] password = Encoding.UTF8.GetBytes("password");
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encData is null);
+
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                FailIf("invalid PgpPbeEncryptedData", encData0 is null);
+
+                using (var stream = encData0.GetDataStreamRaw(password))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (var ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        var decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                // corrupt AEAD nonce
+                var message = Arrays.Clone(messages[i]);
+                message[0x18]--;
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(password);
+                });
+            }
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                // corrupt encrypted session key
+                var message = Arrays.Clone(messages[i]);
+                message[0x28]--;
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(password);
+                });
+            }
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                // corrupt chunk #0 encrypted data
+                var message = Arrays.Clone(messages[i]);
+                message[message.Length - 35]--;
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(password);
+                });
+            }
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                // corrupt chunk #0 authtag
+                var message = Arrays.Clone(messages[i]);
+                message[message.Length - 20]--;
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(password);
+                });
+            }
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                // corrupt final authtag
+                var message = Arrays.Clone(messages[i]);
+                message[message.Length-2]--;
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(password);
+                });
+            }
+
+
+        }
+
         public override string Name => "PgpCryptoRefreshTest";
 
         public override void PerformTest()
@@ -773,6 +906,7 @@ public override void PerformTest()
             Version6SampleCleartextSignedMessageVerifySignatureTest();
             Version6SampleInlineSignedMessageVerifySignatureTest();
             Version6GenerateAndVerifyInlineSignatureTest();
+            Version6SkeskVersion2SeipdTest();
         }
     }
 }
\ No newline at end of file

From 16e73293f4b56dc22c0d018cf0631fce674d3e0c Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sun, 14 Apr 2024 17:41:20 +0200
Subject: [PATCH 22/37] Support for Argon2 s2k in SKESK and PgpSecretKey
 decryption

---
 crypto/src/bcpg/SecretKeyPacket.cs            |  12 +-
 crypto/src/openpgp/PgpSecretKey.cs            |  31 ++-
 crypto/src/openpgp/PgpUtilities.cs            |  19 +-
 crypto/test/data/openpgp/big-aead-msg.asc     |  57 ++++
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 246 ++++++++++++++----
 5 files changed, 305 insertions(+), 60 deletions(-)
 create mode 100644 crypto/test/data/openpgp/big-aead-msg.asc

diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index 001229647b..45f05d505e 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -293,7 +293,17 @@ public byte[] GetSecretKeyData()
             return secKeyData;
         }
 
-
+        internal byte[] GetAAData()
+        {
+            // HKDF Info used for key derivation in UsageAead
+            return new byte[]
+            {
+                (byte)(0xC0 | (byte)Tag),
+                (byte)pubKeyPacket.Version,
+                (byte)encAlgorithm,
+                (byte)aeadAlgorithm
+            };
+        }
 
         private byte[] EncodeConditionalParameters()
         {
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 24e7173853..7ecad0b99b 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -10,6 +10,7 @@
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC.Rfc8032;
@@ -595,7 +596,35 @@ private byte[] ExtractKeyData(byte[] rawPassPhrase, bool clearPassPhrase)
                 byte[] iv = secret.GetIV();
                 byte[] data;
 
-                if (secret.PublicKeyPacket.Version >= 4)
+                if (secret.S2kUsage == SecretKeyPacket.UsageAead)
+                {
+                    var aeadAlgorithm = secret.AeadAlgorithm;
+                    var hkdfInfo = secret.GetAAData();
+                    var hkdfParams = new HkdfParameters(key.GetKey(), Array.Empty<byte>(), hkdfInfo);
+                    var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+                    hkdfGen.Init(hkdfParams);
+                    var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(encAlgorithm)];
+                    hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+                    var encodedPubkeyContents = secret.PublicKeyPacket.GetEncodedContents();
+                    byte[] aadata = new byte[encodedPubkeyContents.Length + 1];
+                    aadata[0] = (byte)(0xC0 | (byte)secret.Tag);
+                    Array.Copy(encodedPubkeyContents, 0, aadata, 1, encodedPubkeyContents.Length);
+
+                    var encAlgoName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+                    var aeadAlgoName = AeadUtils.GetAeadAlgorithmName(aeadAlgorithm);
+                    var cipher = CipherUtilities.GetCipher($"{encAlgoName}/{aeadAlgoName}/NoPadding");
+
+                    var aeadParams = new AeadParameters(
+                        new KeyParameter(hkdfOutput),
+                        8 * AeadUtils.GetAuthTagLength(aeadAlgorithm),
+                        iv,
+                        aadata);
+
+                    cipher.Init(false, aeadParams);
+                    data = cipher.DoFinal(encData);
+                }
+                else if (secret.PublicKeyPacket.Version >= PublicKeyPacket.Version4)
                 {
                     data = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, 0, encData.Length);
 
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index dab25543e4..5210f5d1e3 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -370,7 +370,24 @@ internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag al
 			int generatedBytes = 0;
             int loopCount = 0;
 
-			while (generatedBytes < keyBytes.Length)
+            if (s2k != null && s2k.Type == S2k.Argon2)
+            {
+                Argon2Parameters.Builder builder =
+                    new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
+                        .WithVersion(Argon2Parameters.ARGON2_VERSION_13)
+                        .WithIterations(s2k.Passes)
+                        .WithMemoryPowOfTwo(s2k.MemorySizeExponent)
+                        .WithParallelism(s2k.Parallelism)
+                        .WithSalt(s2k.GetIV());
+                
+                Argon2BytesGenerator gen = new Argon2BytesGenerator();
+                
+                gen.Init(builder.Build());
+                gen.GenerateBytes(rawPassPhrase, keyBytes, 0, keyBytes.Length);
+                return MakeKey(algorithm, keyBytes);
+            }
+            
+            while (generatedBytes < keyBytes.Length)
             {
 				IDigest digest;
 				if (s2k != null)
diff --git a/crypto/test/data/openpgp/big-aead-msg.asc b/crypto/test/data/openpgp/big-aead-msg.asc
new file mode 100644
index 0000000000..5a00b879c9
--- /dev/null
+++ b/crypto/test/data/openpgp/big-aead-msg.asc
@@ -0,0 +1,57 @@
+-----BEGIN PGP MESSAGE-----
+Comment: V6 SKESK + V2 SEIPD AEAD encrypted message that spans
+Comment: over 4 chunks (chunk size 512 octets)
+Comment: 2000 octets of /dev/zero encrypted with password
+Comment: "password" using Argon2 and AES-256 in OCB mode
+Comment: generated with gosop 2.0.0-alpha
+Comment: Session key A96F671431CEB0F859CFC653976417CCC41...
+Comment: Session key ...26BC0F93C30C6E5F0073E0B91E65A
+
+w1gGJgkCFAS8v5Skp/SlGwgm963nzHEoAwQQ0jy/KQBcQi0ZlxKX7FQpCBWuNH9D
+ahN+JQDx/58tEnRr6CL5Guu36/OXRIxXIO+ni7IZYjL5rHobCk0NBO7b0ukCCQID
+gCNsalrnzMkJZTzVPg5XmGahfHmgHuLM3pSARdRdL5N9xhhBj6/Huj0xgtAi2KKR
+a+vzfnoFq0Zq+R053tiAxXGTPyxwdwb74UjEN58fd78jC4dSgV6OQeuA2E+waFTS
+VCOFr1wTmKTp99byPUHkWkEepm+iKAUX43MR1WHPseJakTX9r7AwOU3GZtfK9T15
+EzzOYS2USDws1uFylWai9giPnkN3cV3cQJWQwwCjt2l14p+cIkpJWHvG9372uB3P
+VQ0xovDyKjFBWAQ3OEzYieVuW1PQ5VVyWnFOxY2FIScAQkTZulJQ+E3XozAgowkV
+q0qOd8PLq6GPPFnsm4A6Cepvaq63EJAvoHsa/4ku524kmVe+nOrhlzRU2DrV66hT
+2bn3ywtu3B9Ji1CcGHFTBVvragFFbmBHjBdSwMNg3cT1oGOOGIiK8HqSjGeSfK+g
+/Vn5z8iIeR9QQTW3Q/SpnVrQUMt353fTo42bN5okEbOj3X6kUQfS5/3lgse/7xIR
+cRzBKknA0x7PtEHDb+KY/XSMUfnAWln6Huj7JdEiwG6DsxYlpSOckd1M7UyGLo/0
+sa/Rp7MhHPFUW6NwSM3YHAYv4jrLnPSYDUp6PGeaJl9fG7MXLqiWi9Jb45QvXs1B
+krI+Bnxg6uhdBlL7hJjsH4nOV0/34QYcAlPgJ+lPL5arxv+ouJpGCZC3Zm9hd5Cf
+Tlcp5IXa484/Qzrx4CKZGsTugV6tmQd15H6fGfyPlxK40Egvt7W4FO6p+OMX7peJ
+mTKWSm9bJIF/bCv4CBch29kyfW7yb1n1Y7mL+5Y60y4HHFKDl3ftrPbqGskp9zqe
+lDP21DWaCCfGZUXYX9TpnJZJl6o+YTghRiuobe+HM081Bk7PRQ82ZxqvSYDCCHMB
+n8xAmLQijunT4aVFQlh/rQNRy5bz+gvWb/zBjIQ0+sR1Fx8jlFxU0nYp8ft2elfv
+xXs7+87m5zPPkY4NBKPweV4vOslGSBdQASMnwLNb1Zp5IVQMuGSDrJwgiWQJcsuy
+vjhTwO1FUcnPcAO7kV8TmX07ig4X0MLxQGZVlLX5CnFNKC7QZZ0KVSx17ZpDrrOj
+VXXpbtHyuOoK1z+AOhvgPwvEvcJXpQLTjT3jbbLUL4wjMGhfKVMmOHdZD51gZGDl
+8ziObW4+4hYegHigPlWCOLnbIYj6RGyuoccdiEImrt9Syi/SKida2y5KduEwDQEw
+qAIFYPq/xE5+HZi6t7bsrSH94OpK+tzEGYoDDOBEKoOtwxqFV1Rb0ehwPPFafIfX
+GZRYDqE30Sh9L1AN9LNSsEY/F8RYaEb60aQ2tvYipSHpEgSrBDY0oQ5YJXf2Rd2W
+AH1eAEHkOP+gL66CIOlgLQsmp+U4/Kay5OVH34QwMaZUr2Wy4+dOCdl87sGarVCL
+v1ygk4v2kpjLpj+E6aqkA+OI1AAIindUKzbxIk4kHdV0YFloF/0Z450LXw/A0Pda
+hl9yiTJhEGvI5/jzFyhqP4YOFi0SZ+E9V66lFaqJSXBWLBZ1Xp8Hl/Yie8ArZh/i
+BRakHuL2FMcXRnYv8B6b7P62N5YG9WrTs4AxERJMZmFdiKNnirta5vHSblj2Mn+3
+dAWRJ/N2fZBJOhXNrVYSAQc7W3odQfbtncS24ZeAd4TGLJ90//zyF1deo3AVIAqQ
+BZEqTqDIORWzjy+2T2RF1ug6vyLHro5IQ66pHvEtXHXjU7YAE5+ZuFwHx5jp4est
+L0BxjZCYZApa90Lwaql1FeFUnUAQplpFhGiZfXRSgtkCxkdQ+kW1U2dlo1J+oJQ7
+4ZLjgrFCL7t7m5m6seM7bD4Sbh/OfFcSNe8hEvJF6QuLUmw3UL56V3UfBR50AKAv
+v5ajAAlCXA+KQp86/N4H0vZHkT6kZ8Hakmcbmz14nIp0QLkwI00XofX1oXipLrea
+yt7/uXxH36+/Pz3orVNHPnSn8n++5KKhlm+OMQfr+NbJHBgJ6kz2k51yA1vuYDF9
+fX0Kx9QlnSpq2YNs/eeUp+VHYQT3td3r1Xq3XrQEhbwBzr1IUJlyIRkNu2RziekH
+eCOmqS6QPHdx8S1MdA7XI9pIAT/oEaNxdMWa4u3QjP6F+TcaElAZhsQoDi/b4/uM
+l5RLW6STHJ5Acmg1+9QBM3QjR+N/HKmkE0TL3BP9mI6t/pXLfMAGT8EMNmqdHVV4
+55N8UvwqACGYQPz6Gv8PloO27p5V4ggNuCoNbwZ0l/Npi+AXDUfzrVCwhLZYnPeV
++pqSaK+6IKH8xNV+HBsZZVs8mDRCJg4rm4iAjtv2ylnzUgbcDm012WuuyjANi2II
+L+WbhSmMF+PM41K0DcHl6RFv2R83iPp7WELYIJnGHajPuXu+WV97WCIlnA1NdA2a
+vRbfLs4ztvh3+VtLQwISRXtOPq9XpIAZ34uc330SRzaWlnQXejHtos1eG26Xq6Kq
+/MiBEbz1RyhDDpTOKlSGsa8n5CveOWrufAjLjmLUQAekSy6uFxx9KDoh9SEiFh5o
+MP6Em9OR64px1Cpoaye79lLs/PEHKMDZUp8E4GGOL/pQKmKK71n3PRpSipzNrUrs
+rkf9GddnAJwf9WcpUEvwbPKAY392MyG0JGK8t+UpHnEn/q8/TlephIXl9BatamXh
+OtqEB4m/GedoZYKZzzcRyyLu16l0k6+fj8tgT0G4Cm13shw1MlvEvfgRIjrmtKyb
+38STUVxgHJzsSejMkGLXMPDx+InVcL/ESvmbbjBaSU31QphnD2xyPz6f0ZP/mCQP
+aqnkxOuGhK8VlBy4dTlW3sKvL1dFMSiz0HXJEIkMBwIDkFdqD5bj+PfR6M1FR3Aw
+sKZt+8/TOYGzbCH/2g==
+-----END PGP MESSAGE-----
\ No newline at end of file
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 88917149a0..09b4dbc5b1 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -88,9 +88,10 @@ public class PgpCryptoRefreshTest
             "b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj" +
             "FtCgazStmsuOXF9SFQE=");
 
-        // Sample AEAD encryption and decryption
+        // Sample AEAD encryption and decryption - V6 SKESK + V2 SEIPD
         // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-eax-encryption-
-        // encrypts the cleartext string Hello, world! with the passphrase password, using AES-128 with AEAD-EAX encryption.
+        // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
+        // using AES-128 with AEAD-EAX encryption.
         private readonly byte[] v6skesk_aes128_eax = Base64.Decode(
             "w0AGHgcBCwMIpa5XnR/F2Cv/aSJPkZmTs1Bvo7WaanPP+MXvxfQcV/tU4cImgV14" +
             "KPX5LEVOtl6+AKtZhsaObnxV0mkCBwEGn/kOOzIZZPOkKRPI3MZhkyUBUifvt+rq" +
@@ -98,7 +99,8 @@ public class PgpCryptoRefreshTest
             "QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o=");
 
         // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-ocb-encryption-
-        // encrypts the cleartext string Hello, world! with the passphrase password, using AES-128 with AEAD-OCB encryption.
+        // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
+        // using AES-128 with AEAD-OCB encryption.
         private readonly byte[] v6skesk_aes128_ocb = Base64.Decode(
             "wz8GHQcCCwMIVqKY0vXjZFP/z8xcEWZO2520JZDX3EawckG2EsOBLP/76gDyNHsl" +
             "ZBEj+IeuYNT9YU4IN9gZ02zSaQIHAgYgpmH3MfyaMDK1YjMmAn46XY21dI6+/wsM" +
@@ -106,13 +108,42 @@ public class PgpCryptoRefreshTest
             "K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw==");
 
         // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-gcm-encryption-
-        // encrypts the cleartext string Hello, world! with the passphrase password, using AES-128 with AEAD-GCM encryption.
+        // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
+        // using AES-128 with AEAD-GCM encryption.
         private readonly byte[] v6skesk_aes128_gcm = Base64.Decode(
             "wzwGGgcDCwMI6dOXhbIHAAj/tC58SD70iERXyzcmubPbn/d25fTZpAlS4kRymIUa" +
             "v/91Jt8t1VRBdXmneZ/SaQIHAwb8uUSQvLmLvcnRBsYJAmaUD3LontwhtVlrFXax" +
             "Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS" +
             "+pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q==");
 
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-messages-encrypted-u
+        // V4 SKESK + V1 SEIPD using Argon2 (t=1, p=4, m=21) with AES-128/192/256,
+        // cleartext string "Hello, world!", passphrase "password"
+        private readonly byte[] v4skesk_argon2_aes128 = Base64.Decode(
+            "wycEBwScUvg8J/leUNU1RA7N/zE2AQQVnlL8rSLPP5VlQsunlO+ECxHSPgGYGKY+" +
+            "YJz4u6F+DDlDBOr5NRQXt/KJIf4m4mOlKyC/uqLbpnLJZMnTq3o79GxBTdIdOzhH" +
+            "XfA3pqV4mTzF");
+
+        private readonly byte[] v4skesk_argon2_aes192 = Base64.Decode(
+            "wy8ECAThTKxHFTRZGKli3KNH4UP4AQQVhzLJ2va3FG8/pmpIPd/H/mdoVS5VBLLw" +
+            "F9I+AdJ1Sw56PRYiKZjCvHg+2bnq02s33AJJoyBexBI4QKATFRkyez2gldJldRys" +
+            "LVg77Mwwfgl2n/d572WciAM=");
+
+        private readonly byte[] v4skesk_argon2_aes256 = Base64.Decode(
+            "wzcECQS4eJUgIG/3mcaILEJFpmJ8AQQVnZ9l7KtagdClm9UaQ/Z6M/5roklSGpGu" +
+            "623YmaXezGj80j4B+Ku1sgTdJo87X1Wrup7l0wJypZls21Uwd67m9koF60eefH/K" +
+            "95D1usliXOEm8ayQJQmZrjf6K6v9PWwqMQ==");
+
+        // V6 SKESK + V2 SEIPD using Argon2 with AES-256 in OCB mode
+        // cleartext string "Hello, world!", passphrase "password"
+        // Session key 9DC22B5D8DFCED080C881885335E5A1A7E1215F17BBEC0B485655A308BE3D934
+        // generated with gosop 2.0.0-alpha
+        private readonly byte[] v6skesk_argon2_aes256_ocb = Base64.Decode(
+            "w1gGJgkCFARXue/MBMPDOPspqjeXOAwCAwQQdUzaSpJVUWXsrfYfYX6Bu+PWWSv5" +
+            "v6yNbe7XcntA8BuivOCuH6FU3Mt0UJPZRO9/fRjiEGTuwg6Q7ar/gZ/N0lkCCQIM" +
+            "Zjd4SG7Tv4RJHeycolKmqSHDoK5XlOsA7vlw50nKuRjDyRfsPOFDfHz8hR/z7D1i" +
+            "HST68tjRCRmwqeqVgusCmBlXrXzYTkPXGtmZl2+EYazSACQFVg==");
+
         private readonly char[] emptyPassphrase = Array.Empty<char>();
 
         [Test]
@@ -257,6 +288,31 @@ public void Version6PublicKeyCreationTest()
                 shouldFail: true);
         }
 
+
+        private void SignVerifyRoundtrip(PgpSecretKey signingKey, char[] passphrase)
+        {
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
+
+            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
+            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(passphrase);
+            spkGen.SetIssuerFingerprint(false, signingKey);
+            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+            sigGen.Update(data);
+            sigGen.SetHashedSubpackets(spkGen.Generate());
+            PgpSignature signature = sigGen.Generate();
+
+            AreEqual(signature.GetIssuerFingerprint(), signingKey.GetFingerprint());
+
+            VerifySignature(signature, data, signingKey.PublicKey);
+            VerifySignature(signature, wrongData, signingKey.PublicKey, shouldFail: true);
+
+            byte[] encodedSignature = signature.GetEncoded();
+            VerifyEncodedSignature(encodedSignature, data, signingKey.PublicKey);
+            VerifyEncodedSignature(encodedSignature, wrongData, signingKey.PublicKey, shouldFail: true);
+        }
+
         [Test]
         public void Version6UnlockedSecretKeyParsingTest()
         {
@@ -278,24 +334,7 @@ public void Version6UnlockedSecretKeyParsingTest()
             IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
             IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
 
-            // generate and verify a v6 signature
-            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
-            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
-            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
-            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
-            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(emptyPassphrase);
-            spkGen.SetIssuerFingerprint(false, signingKey);
-            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
-            sigGen.Update(data);
-            sigGen.SetHashedSubpackets(spkGen.Generate());
-            PgpSignature signature = sigGen.Generate();
-
-            VerifySignature(signature, data, signingKey.PublicKey);
-            VerifySignature(signature, wrongData, signingKey.PublicKey, shouldFail: true);
-
-            byte[] encodedSignature = signature.GetEncoded();
-            VerifyEncodedSignature(encodedSignature, data, signingKey.PublicKey);
-            VerifyEncodedSignature(encodedSignature, wrongData, signingKey.PublicKey, shouldFail: true);
+            SignVerifyRoundtrip(signingKey, emptyPassphrase);
 
             // encryption key
             PgpSecretKey encryptionKey = secretKeys[1];
@@ -317,8 +356,10 @@ public void Version6UnlockedSecretKeyParsingTest()
             // generate and verify a v6 userid self-cert
             string userId = "Alice <alice@example.com>";
             string wrongUserId = "Bob <bob@example.com>";
+            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(emptyPassphrase);
             sigGen.InitSign(PgpSignature.PositiveCertification, privKey, new SecureRandom());
-            signature = sigGen.GenerateCertification(userId, signingKey.PublicKey);
+            PgpSignature signature = sigGen.GenerateCertification(userId, signingKey.PublicKey);
             signature.InitVerify(signingKey.PublicKey);
             if (!signature.VerifyCertification(userId, signingKey.PublicKey))
             {
@@ -443,20 +484,7 @@ public void Version6Ed25519KeyPairCreationTest()
             IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
 
             // Sign-Verify roundtrip
-            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
-            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
-            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(pgppubkey.Algorithm, HashAlgorithmTag.Sha512);
-            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
-            PgpPrivateKey privKey = pgpseckey.ExtractPrivateKey(emptyPassphrase);
-            spkGen.SetIssuerFingerprint(false, pgpseckey);
-            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
-            sigGen.Update(data);
-            sigGen.SetHashedSubpackets(spkGen.Generate());
-            signature = sigGen.Generate();
-
-            AreEqual(signature.GetIssuerFingerprint(), expectedFingerprint);
-            VerifySignature(signature, data, pgppubkey);
-            VerifySignature(signature, wrongData, pgppubkey, shouldFail: true);
+            SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
 
             // encrypt-decrypt test
             AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
@@ -543,20 +571,7 @@ public void Version6Ed448KeyPairCreationTest()
             IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
 
             // Sign-Verify roundtrip
-            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
-            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
-            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(pgppubkey.Algorithm, HashAlgorithmTag.Sha512);
-            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
-            PgpPrivateKey privKey = pgpseckey.ExtractPrivateKey(emptyPassphrase);
-            spkGen.SetIssuerFingerprint(false, pgpseckey);
-            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, rand);
-            sigGen.Update(data);
-            sigGen.SetHashedSubpackets(spkGen.Generate());
-            signature = sigGen.Generate();
-
-            AreEqual(signature.GetIssuerFingerprint(), fpr);
-            VerifySignature(signature, data, pgppubkey);
-            VerifySignature(signature, wrongData, pgppubkey, shouldFail: true);
+            SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
 
             // Encrypt-Decrypt test
             AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
@@ -573,9 +588,6 @@ public void Version6LockedSecretKeyParsingTest()
              * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-locked-v6-secret-key
              * The same secret key as in Version6UnlockedSecretKeyParsingTest, but the secret key
              * material is locked with a passphrase using AEAD and Argon2.
-             * 
-             * AEAD/Argon passphrase decryption is not implemented yet, so we just test
-             * parsing and encoding
              */
 
             PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6LockedSecretKey);
@@ -589,6 +601,19 @@ public void Version6LockedSecretKeyParsingTest()
             IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
             IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
 
+            // try to decrypt with wrong passphrases
+            Assert.Throws<PgpException>(() =>
+            {
+                PgpPrivateKey pk = signingKey.ExtractPrivateKey(emptyPassphrase);
+            });
+            Assert.Throws<PgpException>(() =>
+            {
+                PgpPrivateKey pk = signingKey.ExtractPrivateKey("wrong".ToCharArray());
+            });
+
+            string passphrase = "correct horse battery staple";
+            SignVerifyRoundtrip(signingKey, passphrase.ToCharArray());
+
             // encryption key
             PgpSecretKey encryptionKey = secretKeys[1];
             IsEquals(encryptionKey.KeyEncryptionAlgorithm, SymmetricKeyAlgorithmTag.Aes256);
@@ -596,6 +621,14 @@ public void Version6LockedSecretKeyParsingTest()
             IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
             IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
 
+            // decrypt test
+            AsymmetricCipherKeyPair alice = GetKeyPair(encryptionKey, passphrase);
+            IAsymmetricCipherKeyPairGenerator kpGen = new X25519KeyPairGenerator();
+            kpGen.Init(new X25519KeyGenerationParameters(new SecureRandom()));
+            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
+            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, encryptionKey.PublicKey.Algorithm));
+
+
             // Encode-Decode roundtrip
             using (MemoryStream ms = new MemoryStream())
             {
@@ -785,11 +818,13 @@ private static bool EncryptThenDecryptTest(AsymmetricCipherKeyPair alice, Asymme
         [Test]
         public void Version6SkeskVersion2SeipdTest()
         {
+            // encrypts the cleartext string "Hello, world!" with the passphrase "password",
+            // S2K type iterated+salted, using AES-128 with AEAD encryption.
             byte[][] messages = new byte[][]
             {
-                v6skesk_aes128_eax,
-                v6skesk_aes128_ocb,
-                v6skesk_aes128_gcm
+                v6skesk_aes128_eax,   // from crypto-refresh A.9
+                v6skesk_aes128_ocb,   // from crypto-refresh A.10
+                v6skesk_aes128_gcm    // from crypto-refresh A.11
             };
 
             byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
@@ -817,6 +852,19 @@ public void Version6SkeskVersion2SeipdTest()
                 }
             }
 
+            // wrong passsword
+            byte[] wrongpassword = Encoding.UTF8.GetBytes("wrongpassword");
+            for (int i = 0; i < messages.Length; i++)
+            {
+                PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(wrongpassword);
+                });
+            }
+
             for (int i = 0; i < messages.Length; i++)
             {
                 // corrupt AEAD nonce
@@ -887,6 +935,89 @@ public void Version6SkeskVersion2SeipdTest()
                 });
             }
 
+            /*
+             *  V6 SKESK + V2 SEIPD AEAD encrypted message that spans over 4 chunks
+             *  (chunk size 512 octets)
+             *  2000 octets of /dev/zero encrypted with password "password" using Argon2
+             *  and AES-256 in OCB mode. Generated with gosop 2.0.0-alpha
+             *  Session key A96F671431CEB0F859CFC653976417CCC4126BC0F93C30C6E5F0073E0B91E65A
+             */
+            {
+                plaintext = new byte[2000];
+                Arrays.Fill(plaintext, 0);
+
+                Stream message = PgpUtilities.GetDecoderStream(
+                    SimpleTest.GetTestDataAsStream("openpgp.big-aead-msg.asc"));
+
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encData is null);
+
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                FailIf("invalid PgpPbeEncryptedData", encData0 is null);
+
+                using (var stream = encData0.GetDataStreamRaw(password))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (var ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        var decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
+        }
+
+        [Test]
+        public void SkeskWithArgon2Test()
+        {
+            byte[][] messages = new byte[][]
+            {
+                v4skesk_argon2_aes128,    // from crypto-refresh A.12.1
+                v4skesk_argon2_aes192,    // from crypto-refresh A.12.2
+                v4skesk_argon2_aes256,    // from crypto-refresh A.12.3
+                v6skesk_argon2_aes256_ocb // generated with gosop 2.0.0-alpha
+            };
+
+            byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+            byte[] password = Encoding.UTF8.GetBytes("password");
+
+            for (int i = 0; i < messages.Length; i++)
+            {
+                PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encData is null);
+
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                FailIf("invalid PgpPbeEncryptedData", encData0 is null);
+
+                using (var stream = encData0.GetDataStreamRaw(password))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (var ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        var decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
+
+            // wrong passsword
+            byte[] wrongpassword = Encoding.UTF8.GetBytes("wrongpassword");
+            for (int i = 0; i < messages.Length; i++)
+            {
+                PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                var encData0 = encData[0] as PgpPbeEncryptedData;
+                var err = Assert.Throws<PgpException>(() =>
+                {
+                    var stream = encData0.GetDataStreamRaw(wrongpassword);
+                });
+            }
 
         }
 
@@ -907,6 +1038,7 @@ public override void PerformTest()
             Version6SampleInlineSignedMessageVerifySignatureTest();
             Version6GenerateAndVerifyInlineSignatureTest();
             Version6SkeskVersion2SeipdTest();
+            SkeskWithArgon2Test();
         }
     }
 }
\ No newline at end of file

From cb265792870e0508f587dd0828ef1f55aefe42f5 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Tue, 16 Apr 2024 19:13:47 +0200
Subject: [PATCH 23/37] V6 PKESK decryption

---
 crypto/src/bcpg/AeadUtils.cs                  |  15 +-
 crypto/src/bcpg/PublicKeyEncSessionPacket.cs  | 196 +++++++++++--
 crypto/src/openpgp/PgpPbeEncryptedData.cs     |  12 +-
 .../src/openpgp/PgpPublicKeyEncryptedData.cs  | 268 +++++++++++++++---
 .../test/data/openpgp/big-pkesk-aead-msg.asc  |  57 ++++
 ...ig-aead-msg.asc => big-skesk-aead-msg.asc} |   0
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 122 +++++++-
 .../test/PgpInteroperabilityTestSuite.cs      |  78 +++++
 8 files changed, 668 insertions(+), 80 deletions(-)
 create mode 100644 crypto/test/data/openpgp/big-pkesk-aead-msg.asc
 rename crypto/test/data/openpgp/{big-aead-msg.asc => big-skesk-aead-msg.asc} (100%)

diff --git a/crypto/src/bcpg/AeadUtils.cs b/crypto/src/bcpg/AeadUtils.cs
index d2bda51e8b..e828f735a9 100644
--- a/crypto/src/bcpg/AeadUtils.cs
+++ b/crypto/src/bcpg/AeadUtils.cs
@@ -1,7 +1,8 @@
 using System;
-using System.IO;
 using Org.BouncyCastle.Bcpg.OpenPgp;
-using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
 
 namespace Org.BouncyCastle.Bcpg
 {
@@ -91,5 +92,15 @@ public static void SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgo
             Array.Copy(messageKeyAndIv, messageKey, messageKey.Length);
             Array.Copy(messageKeyAndIv, messageKey.Length, iv, 0, ivLen-8);
         }
+
+        public static BufferedAeadBlockCipher CreateAeadCipher(
+            SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
+        {
+            string algo = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+            string mode = GetAeadAlgorithmName(aeadAlgorithm);
+            string cName = $"{algo}/{mode}/NoPadding";
+
+            return CipherUtilities.GetCipher(cName) as BufferedAeadBlockCipher;
+        }
     }
 }
\ No newline at end of file
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index b38b19bd55..dd7e1d7f05 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -4,6 +4,7 @@
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Bcpg
 {
@@ -11,29 +12,78 @@ namespace Org.BouncyCastle.Bcpg
 	public class PublicKeyEncSessionPacket
 		: ContainedPacket //, PublicKeyAlgorithmTag
 	{
-		private readonly int version;
-		private readonly long keyId;
-		private readonly PublicKeyAlgorithmTag algorithm;
-        private readonly byte[][] data;
+        private readonly int version;						// V3, V6
+        private readonly long keyId;                        // V3 only
+        private readonly PublicKeyAlgorithmTag algorithm;	// V3, V6
+        private readonly byte[][] data;						// V3, V6
+        private readonly int keyVersion;					// V6 only
+        private readonly byte[] keyFingerprint;				// V6 only
 
-		internal PublicKeyEncSessionPacket(
+        /// <summary>
+        /// Version 3 PKESK packet.
+        /// </summary>
+        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-3-public-key-encryp"/>
+        public const int Version3 = 3;
+
+        /// <summary>
+        /// Version 6 PKESK packet.
+        /// </summary>
+		/// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-public-key-encryp"/>
+        public const int Version6 = 6;
+
+        internal PublicKeyEncSessionPacket(
 			BcpgInputStream bcpgIn)
 			:base(PacketTag.PublicKeyEncryptedSession)
 		{
 			version = bcpgIn.ReadByte();
+            switch (version)
+			{
+                case Version3:
+                    keyId |= (long)bcpgIn.ReadByte() << 56;
+                    keyId |= (long)bcpgIn.ReadByte() << 48;
+                    keyId |= (long)bcpgIn.ReadByte() << 40;
+                    keyId |= (long)bcpgIn.ReadByte() << 32;
+                    keyId |= (long)bcpgIn.ReadByte() << 24;
+                    keyId |= (long)bcpgIn.ReadByte() << 16;
+                    keyId |= (long)bcpgIn.ReadByte() << 8;
+                    keyId |= (uint)bcpgIn.ReadByte();
+                    break;
+                case Version6:
+                    int keyInfoLength = bcpgIn.ReadByte();
+                    if (keyInfoLength == 0)
+                    {
+                        // anonymous recipient
+                        keyVersion = 0;
+                        keyFingerprint = Array.Empty<byte>();
+                        keyId = 0;
+                    }
+                    else
+                    {
+                        keyVersion = bcpgIn.ReadByte();
+                        keyFingerprint = new byte[keyInfoLength - 1];
+                        bcpgIn.ReadFully(keyFingerprint);
 
-			keyId |= (long)bcpgIn.ReadByte() << 56;
-			keyId |= (long)bcpgIn.ReadByte() << 48;
-			keyId |= (long)bcpgIn.ReadByte() << 40;
-			keyId |= (long)bcpgIn.ReadByte() << 32;
-			keyId |= (long)bcpgIn.ReadByte() << 24;
-			keyId |= (long)bcpgIn.ReadByte() << 16;
-			keyId |= (long)bcpgIn.ReadByte() << 8;
-			keyId |= (uint)bcpgIn.ReadByte();
-
+                        switch (keyVersion)
+                        {
+                            case PublicKeyPacket.Version4:
+                                keyId = (long)Pack.BE_To_UInt64(keyFingerprint, keyFingerprint.Length - 8);
+                                break;
+                            case PublicKeyPacket.Version5:
+                            case PublicKeyPacket.Version6:
+                                keyId = (long)Pack.BE_To_UInt64(keyFingerprint);
+                                break;
+                            default:
+                                throw new InvalidOperationException($"unsupported OpenPGP key packet version: {keyVersion}");
+                        }
+                    }
+                    break;
+                default:
+                    throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {version}");
+            }
+			
 			algorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte();
 
-			switch ((PublicKeyAlgorithmTag) algorithm)
+            switch (algorithm)
 			{
 				case PublicKeyAlgorithmTag.RsaEncrypt:
 				case PublicKeyAlgorithmTag.RsaGeneral:
@@ -51,18 +101,49 @@ internal PublicKeyEncSessionPacket(
                 case PublicKeyAlgorithmTag.ECDH:
                     data = new byte[][]{ Streams.ReadAll(bcpgIn) };
                     break;
-				default:
+                case PublicKeyAlgorithmTag.X25519:
+                case PublicKeyAlgorithmTag.X448:
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-x
+
+                    // 32 (for X25519) or 56 (for X448) octets representing an ephemeral public key.
+                    int keylen = algorithm == PublicKeyAlgorithmTag.X25519 ? 32 : 56;
+                    byte[] ephemeralPubKey = new byte[keylen];
+                    bcpgIn.ReadFully(ephemeralPubKey);
+
+                    // A one-octet size of the following fields.
+                    int esklen = bcpgIn.ReadByte();
+
+                    // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet).
+                    // The encrypted session key.
+                    byte[] encryptedSessionKey = new byte[esklen];
+                    bcpgIn.ReadFully(encryptedSessionKey);
+
+                    data = new byte[][]{
+                        ephemeralPubKey,
+                        encryptedSessionKey,
+                    };
+                    break;
+
+                default:
 					throw new IOException("unknown PGP public key algorithm encountered");
 			}
 		}
 
+
+        /// <summary>
+        /// Create a new V3 PKESK packet.
+        /// </summary>
+        /// <param name="keyId">ID of the recipient key, 0 for anonymo</param>
+        /// <param name="algorithm">public key algorithm</param>
+        /// <param name="data">session data</param>
         public PublicKeyEncSessionPacket(
 			long                    keyId,
 			PublicKeyAlgorithmTag   algorithm,
 			byte[][]                data)
             : base(PacketTag.PublicKeyEncryptedSession)
         {
-			this.version = 3;
+			this.version = Version3;
 			this.keyId = keyId;
 			this.algorithm = algorithm;
             this.data = new byte[data.Length][];
@@ -82,7 +163,22 @@ public long KeyId
 			get { return keyId; }
 		}
 
-		public PublicKeyAlgorithmTag Algorithm
+        public bool IsRecipientAnonymous
+        {
+            get { return keyId == 0; }
+        }
+
+        public byte[] GetKeyFingerprint()
+        {
+            return Arrays.Clone(keyFingerprint);
+        }
+
+        public int KeyVersion
+        {
+            get { return keyVersion; }
+        }
+
+        public PublicKeyAlgorithmTag Algorithm
 		{
 			get { return algorithm; }
 		}
@@ -94,20 +190,58 @@ public byte[][] GetEncSessionKey()
 
         public override void Encode(BcpgOutputStream bcpgOut)
 		{
-			MemoryStream bOut = new MemoryStream();
-			using (var pOut = new BcpgOutputStream(bOut))
-			{
-				pOut.WriteByte((byte)version);
-				pOut.WriteLong(keyId);
-				pOut.WriteByte((byte)algorithm);
-
-				for (int i = 0; i < data.Length; ++i)
-				{
-					pOut.Write(data[i]);
-				}
-			}
+            using (MemoryStream bOut = new MemoryStream())
+            {
+                using (var pOut = new BcpgOutputStream(bOut))
+                {
+                    pOut.WriteByte((byte)version);
+
+                    switch (version)
+                    {
+                        case Version3:
+                            pOut.WriteLong(keyId);
+                            break;
+
+                        case Version6:
+                            if (keyVersion == 0)
+                            {
+                                // anonymous recipient
+                                pOut.WriteByte(0);
+                            }
+                            else
+                            {
+                                pOut.WriteByte((byte)(keyFingerprint.Length + 1));
+                                pOut.WriteByte((byte)keyVersion);
+                                pOut.Write(keyFingerprint);
+                            }
+                            break;
+                        default:
+                            throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {version}");
+                    }
 
-			bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray());
+                    pOut.WriteByte((byte)algorithm);
+
+                    if (algorithm == PublicKeyAlgorithmTag.X25519 || algorithm == PublicKeyAlgorithmTag.X448)
+                    {
+                        // ephemeral public key.
+                        pOut.Write(data[0]);
+                        // One-octet size of the encrypted session key (prefixed by the one-octet
+                        // algorithm identifier, in the case of a v3 PKESK packet).
+                        pOut.WriteByte((byte)(data[1].Length));
+                        // encrypted session key
+                        pOut.Write(data[1]);
+                    }
+                    else
+                    {
+                        for (int i = 0; i < data.Length; ++i)
+                        {
+                            pOut.Write(data[i]);
+                        }
+                    }
+                }
+
+                bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray());
+            }
 		}
 	}
 }
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index 91bec3fd0c..e007b6a6d1 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -238,7 +238,7 @@ private Stream DoGetDataStreamVersion2(SymmetricEncIntegrityPacket seipd, byte[]
                 var aadata = seipd.GetAAData();
                 var salt = seipd.GetSalt();
                 PgpUtilities.DeriveAeadMessageKeyAndIv(sessionKey, seipd.CipherAlgorithm, seipd.AeadAlgorithm, salt, aadata, out var messageKey, out var iv);
-                var cipher = CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
+                var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
 
                 var aeadStream = new AeadInputStream(
                     encData.GetInputStream(),
@@ -286,15 +286,5 @@ private IBufferedCipher CreateStreamCipher(
 			return CipherUtilities.GetCipher(cName);
 		}
 
-        private static BufferedAeadBlockCipher CreateAeadCipher(
-            SymmetricKeyAlgorithmTag keyAlgorithm, AeadAlgorithmTag aeadAlgorithm)
-        {
-            string algo = PgpUtilities.GetSymmetricCipherName(keyAlgorithm);
-            string mode = AeadUtils.GetAeadAlgorithmName(aeadAlgorithm);
-            string cName = $"{algo}/{mode}/NoPadding";
-
-            return CipherUtilities.GetCipher(cName) as BufferedAeadBlockCipher;
-        }
-
     }
 }
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 6459732156..36d23a7644 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -1,8 +1,10 @@
 using System;
 using System.IO;
+using System.Text;
 
 using Org.BouncyCastle.Asn1.Cryptlib;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
@@ -26,9 +28,56 @@ internal PgpPublicKeyEncryptedData(
             : base(encData)
         {
             this.keyData = keyData;
+            EnforceConstraints();
         }
 
-		private static IBufferedCipher GetKeyCipher(
+        private void EnforceConstraints()
+        {
+            switch (keyData.Version)
+            {
+                case PublicKeyEncSessionPacket.Version3:
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-3-public-key-encryp
+                    // A version 3 PKESK packet precedes a version 1 SEIPD packet. In historic data, it is sometimes
+                    // found preceding a deprecated SED packet.
+                    // A V3 PKESK packet MUST NOT precede a V2 SEIPD packet.
+                    if (encData is SymmetricEncDataPacket)
+                    {
+                        return;
+                    }
+                    if (encData is SymmetricEncIntegrityPacket seipd1)
+                    {
+                        if (seipd1.Version == SymmetricEncIntegrityPacket.Version1)
+                        {
+                            return;
+                        }
+                        throw new ArgumentException($"Version 3 PKESK cannot precede SEIPD of version {seipd1.Version}");
+                    }
+                    break;
+
+                case PublicKeyEncSessionPacket.Version6:
+                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-public-key-encryp
+                    //A version 6 PKESK packet precedes a version 2 SEIPD packet.
+                    //A V6 PKESK packet MUST NOT precede a V1 SEIPD packet or a deprecated SED packet.
+                    if (encData is SymmetricEncDataPacket)
+                    {
+                        throw new ArgumentException("Version 6 PKESK MUST NOT precede a deprecated SED packet.");
+                    }
+
+                    if (encData is SymmetricEncIntegrityPacket seipd2)
+                    {
+                        if (seipd2.Version == SymmetricEncIntegrityPacket.Version2)
+                        {
+                            return;
+                        }
+                        throw new ArgumentException($"Version 6 PKESK cannot precede SEIPD of version {seipd2.Version}");
+                    }
+                    break;
+                default:
+                    throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {keyData.Version}");
+            }
+        }
+
+        private static IBufferedCipher GetKeyCipher(
             PublicKeyAlgorithmTag algorithm)
         {
             try
@@ -58,9 +107,14 @@ private static IBufferedCipher GetKeyCipher(
 		private bool ConfirmCheckSum(
             byte[] sessionInfo)
         {
-            int check = 0;
+            // for X25519 and X448 no checksum or padding are appended to the session key before key wrapping
+            if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+            {
+                return true;
+            }
 
-			for (int i = 1; i != sessionInfo.Length - 2; i++)
+            int check = 0;
+            for (int i = 1; i != sessionInfo.Length - 2; i++)
             {
                 check += sessionInfo[i] & 0xff;
             }
@@ -75,16 +129,75 @@ 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[] sessionData = RecoverSessionData(privKey);
+        /// <summary>The key fingerprint for the key used to encrypt the data (v6 only).</summary>
+        public byte[] GetKeyFingerprint()
+        {
+            return keyData.GetKeyFingerprint();
+        }
 
-            return (SymmetricKeyAlgorithmTag)sessionData[0];
-		}
+        /// <summary>
+        /// Return the algorithm code for the symmetric algorithm used to encrypt the data.
+        /// </summary>
+        public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
+            PgpPrivateKey privKey)
+        {
+            if (keyData.Version == PublicKeyEncSessionPacket.Version3)
+            {
+                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-
+                // In V3 PKESK, the symmetric algorithm Id
+                //     * with X25519 and X448 is not encrypted, it's prepended in plaintext
+                //       to the encrypted session key.
+                //     * with other algorithms, it's is encrypted with the session key
+
+                if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+                {
+                    byte[][] secKeyData = keyData.GetEncSessionKey();
+                    return (SymmetricKeyAlgorithmTag)secKeyData[1][0];
+                }
+                else
+                {
+                    byte[] sessionData = RecoverSessionData(privKey);
+
+                    return (SymmetricKeyAlgorithmTag)sessionData[0];
+                }
+            }
+            else if (keyData.Version == PublicKeyEncSessionPacket.Version6)
+            {
+                // V6 PKESK stores the cipher algorithm in the V2 SEIPD packet fields.
+                return ((SymmetricEncIntegrityPacket)encData).CipherAlgorithm;
+            }
+            else
+            {
+                throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {keyData.Version}");
+            }
+        }
+
+        private Stream GetDataStreamSeipdVersion2(byte[] sessionData, SymmetricEncIntegrityPacket seipd)
+        {
+            var encAlgo = seipd.CipherAlgorithm;
+            var aeadAlgo = seipd.AeadAlgorithm;
+            var aadata = seipd.GetAAData();
+            var salt = seipd.GetSalt();
+
+            var sessionKey = ParameterUtilities.CreateKeyParameter(
+                PgpUtilities.GetSymmetricCipherName(encAlgo),
+                sessionData, 0, sessionData.Length);
+
+            PgpUtilities.DeriveAeadMessageKeyAndIv(sessionKey, encAlgo, aeadAlgo, salt, aadata, out var messageKey, out var iv);
+            var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
+
+            var aeadStream = new AeadInputStream(
+                encData.GetInputStream(),
+                cipher,
+                messageKey,
+                iv,
+                aeadAlgo,
+                seipd.ChunkSize,
+                aadata);
+
+            encStream = BcpgInputStream.Wrap(aeadStream);
+            return encStream;
+        }
 
         /// <summary>Return the decrypted data stream for the packet.</summary>
         public Stream GetDataStream(
@@ -92,10 +205,27 @@ public Stream GetDataStream(
         {
 			byte[] sessionData = RecoverSessionData(privKey);
 
+            if (keyData.Version == PublicKeyEncSessionPacket.Version6)
+            {
+                // V6 PKESK + V2 SEIPD
+                return GetDataStreamSeipdVersion2(sessionData, (SymmetricEncIntegrityPacket)encData);
+            }
+
             if (!ConfirmCheckSum(sessionData))
                 throw new PgpKeyValidationException("key checksum failed");
 
-            SymmetricKeyAlgorithmTag symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0];
+            SymmetricKeyAlgorithmTag symmAlg;
+            if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+            {
+                // with X25519 and X448 is not encrypted, the symmetric algorithm Id is
+                // prepended in plaintext to the encrypted session key.
+                byte[][] secKeyData = keyData.GetEncSessionKey();
+                symmAlg = (SymmetricKeyAlgorithmTag)secKeyData[1][0];
+            }
+            else
+            {
+                symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0];
+            }
             if (symmAlg == SymmetricKeyAlgorithmTag.Null)
                 return encData.GetInputStream();
 
@@ -127,8 +257,17 @@ public Stream GetDataStream(
 
             try
             {
-				KeyParameter key = ParameterUtilities.CreateKeyParameter(
-					cipherName, sessionData, 1, sessionData.Length - 3);
+                // no checksum and padding for X25519 and X448
+                int offset = 0;
+                int length = sessionData.Length;
+                if (keyData.Algorithm != PublicKeyAlgorithmTag.X25519 && keyData.Algorithm != PublicKeyAlgorithmTag.X448)
+                {
+                    offset = 1;
+                    length -= 3;
+                }
+
+                KeyParameter key = ParameterUtilities.CreateKeyParameter(
+                    cipherName, sessionData, offset, length);
 
                 byte[] iv = new byte[cipher.GetBlockSize()];
 
@@ -189,43 +328,102 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey)
 		{
             byte[][] secKeyData = keyData.GetEncSessionKey();
 
+            if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+            {
+                // See sect. 5.1.6. and 5.1.7 of crypto-refresh for the description of
+                // the key derivation algorithm for X25519 and X448 
+                //     https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-
+                //     https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-x
+                byte[] eph = secKeyData[0];
+                byte[] esk = secKeyData[1];
+
+                IRawAgreement agreement;
+                IDigest digestForHkdf;
+                byte[] hkdfInfo;
+                AsymmetricKeyParameter ephPubkey;
+                SymmetricKeyAlgorithmTag wrappingAlgo;
+
+                if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519)
+                {
+                    agreement = new X25519Agreement();
+                    ephPubkey = new X25519PublicKeyParameters(eph);
+                    digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+                    hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X25519");
+                    wrappingAlgo = SymmetricKeyAlgorithmTag.Aes128;
+                }
+                else
+                {
+                    agreement = new X448Agreement();
+                    ephPubkey = new X448PublicKeyParameters(eph);
+                    digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha512);
+                    hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X448");
+                    wrappingAlgo = SymmetricKeyAlgorithmTag.Aes256;
+                }
+
+                agreement.Init(privKey.Key);
+                byte[] sharedSecret = new byte[agreement.AgreementSize];
+                agreement.CalculateAgreement(ephPubkey, sharedSecret, 0);
+
+                byte[] pubKeyMaterial = ((OctetArrayBcpgKey)privKey.PublicKeyPacket.Key).GetKey();
+                byte[] ikm = Arrays.ConcatenateAll(eph, pubKeyMaterial, sharedSecret);
+                byte[] hkdfSalt = Array.Empty<byte>();
+                var hkdfParams = new HkdfParameters(ikm, hkdfSalt, hkdfInfo);
+                var hkdfGen = new HkdfBytesGenerator(digestForHkdf);
+                hkdfGen.Init(hkdfParams);
+                var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(wrappingAlgo)];
+                hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+                KeyParameter kek = ParameterUtilities.CreateKeyParameter("AES", hkdfOutput);
+                var wrapper = PgpUtilities.CreateWrapper(wrappingAlgo);
+                wrapper.Init(false, kek);
+                int offset = 0;
+                int length = esk.Length;
+                if (keyData.Version == PublicKeyEncSessionPacket.Version3)
+                {
+                    offset = 1;
+                    length--;
+                }
+                var keyBytes = wrapper.Unwrap(esk, offset, length);
+                return keyBytes;
+            }
+
             if (keyData.Algorithm != PublicKeyAlgorithmTag.ECDH)
             {
                 IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
 
                 try
-			    {
+                {
                     cipher.Init(false, privKey.Key);
-			    }
-			    catch (InvalidKeyException e)
-			    {
-				    throw new PgpException("error setting asymmetric cipher", e);
-			    }
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new PgpException("error setting asymmetric cipher", e);
+                }
 
                 if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
-				    || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
-			    {
+                    || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
+                {
                     byte[] bi = secKeyData[0];
 
                     cipher.ProcessBytes(bi, 2, bi.Length - 2);
-			    }
-			    else
-			    {
-				    ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
-				    int size = (k.Parameters.P.BitLength + 7) / 8;
+                }
+                else
+                {
+                    ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
+                    int size = (k.Parameters.P.BitLength + 7) / 8;
 
                     ProcessEncodedMpi(cipher, size, secKeyData[0]);
                     ProcessEncodedMpi(cipher, size, secKeyData[1]);
-			    }
+                }
 
                 try
-			    {
+                {
                     return cipher.DoFinal();
-			    }
-			    catch (Exception e)
-			    {
-				    throw new PgpException("exception decrypting secret key", e);
-			    }
+                }
+                catch (Exception e)
+                {
+                    throw new PgpException("exception decrypting secret key", e);
+                }
             }
 
             ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
diff --git a/crypto/test/data/openpgp/big-pkesk-aead-msg.asc b/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
new file mode 100644
index 0000000000..c481452ad7
--- /dev/null
+++ b/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
@@ -0,0 +1,57 @@
+-----BEGIN PGP MESSAGE-----
+Comment: V6 PKESK + V2 SEIPD AEAD encrypted message that spans
+Comment: over 4 chunks (chunk size 512 octets)
+Comment: 2000 octets of /dev/zero encrypted with sample V6
+Comment: certificate from crypto-refresh Appendix A.3
+Comment: generated with gosop 2.0.0-alpha
+Comment: Session key CFB73D46CF7C13B7535227BEDB5B2D8B4023C5B...
+Comment: Session key ...58289D19CF2C33B0DB388B0B6
+
+wW0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRkZAF3uCv64JHab
+dMihIp+6i/QylLjksSURIA+Z1HHoSChcgayIgr6j/Q4A8BYSTdpcMdlXUakz8dpw
+yxEWODQA2lQ5UMnl4+ZT0ukCCQIDOl2tRUXUTtbMzZx+EiDOerM0byHPZaEBHDhk
+t9kB+i4cYJRnBKylw8pgS7Xp0727plIARoGamB004i2eO2BEabdfstHli3dfsCpq
+esQeDKMVdUCPPaOb4FwcNUrSJPNWPQeDdQLHAUUXCFEalN5OZglmzLBDZNyb0LFR
+k/oVvPyNlmX5Rt+ZW3L8cEoHzfJdLNJWEC2TRKFg3GVFrwucEjEpJtQsha1/x7cd
+83Cvz9zEX/A3clkWj855KMx/AK4hkn9oKgQuWgFEIb5H4hyNLkpUPGlZg4o+fTwj
+vD6pcJIZBx/rxS7QHg9jsYQVLQUk2t4K9B3cq4bAKavWxVfISGMrs3VgCcG1ePKH
+FFfek6U98sL39mu+dklPGWP9rLTCk3dlAc0mMvadMapcH/ypk1YZtK0iEDEMODqL
+rxXFmE4Mi0FPemW+63/o0GGCFz5YRNOf0cqKwysLpQr7Sa4uGnZv3ykuXbtb9l65
+i6i0I+F5IojQfUaR6gjfOQS2DHXEOey+xP+ZbQ8+M61wr97IbEXXo5d3Z5Ytbqot
+s6YYHMWL1XkX0F4L+wTRWuDhIGTFCPqY5v1ejccT5SMmG1qcNZM1yaKXuaNWXWVa
+KAcGw5Zh2HRn3kkXAZTM/v/va5MZaYd+MbTfzrQ1Wym1YNl2L80uJFg6AK5aRc4s
+YukmxdeszVowz9LF+7tzr+TAY14+VJmiOB5yFue5dj6Rz0Z4veerutVz23T2izLQ
+UYdY48sQDYSipckg1vUbvTx2+3/B+AN/xFv9hb6whINLE1Ifg2zExCOxtZytmHN9
+kYNmHMTDkXjioxFepdHMv95DTZgwx5T3imOwvfNsYlGP1g74pjiG4AM0BSiOVUse
+QmhyTvm3bniSwcnkBwPrnUw4yiCVX796QL8IvZBfY2t0n5P/LUHnYQ3HTnfRwMC9
+Qj3U0lRQwAlkKLHcCapsq6XLwbiL6gozq0G13WVe3hdwi6Do1kYHpl66lmnPbW09
+8Nos1iEoptMdiesFmPqQ2d6peqY1QPfrG/MKGM/nH8+GiVo7qCDjdv1ZPW9ebHln
+hKOrA+5c0BpPozBUULSUYo1nIXv8/zGVuoQl9T/c1Be4BS/Ub8D4l1/T5EQ2qb5c
+vSrQ33Rx9uwSxWMZwZ41w53/JbgCVgZSpiAIXQdtdPBwBPn4YKis7DW1aiTqJJRH
+jvJaEveBzlZzPpNCZkL/vLKJC4b9UmPfzbUo9xoAFmDZo1c3hUGHDhNt/qEba2VW
+XaCYg5eJtYYtcH1dNg5O4Olc54a6mBpoc0KS3nvDzM/UhLub0AZfUM8vH/hXAEW+
+Efcb0fJzx5Yl3MNMvhAgxwN0u3icaBS03Yo7VBCmQ4ti+ela3h2zP5MxihBI4iXH
+gh1WSKrk0GMTK6uDC8TSolkclOdGx7+V2ftSrMk5lJ+qaGQatu6LJhjirjifYlGY
+cHRDOqGAbMvAhdaARcIxpi5Cu2HCk5myflAXN1t+O+s+N8VSyKPkIOMU1y4AVQ27
+kR7uPFNgd9F1AzKZzOVgZiP3Kqcjoo5sGJiqfVEbnsKidB4ddqgrmz40AgqkC9ZU
+WtKjMByUPvJo0S1ZvBlawgi038KiEh2vmbPSjgbLMrKPK9ZY45Y6kiW5Safu7N2d
+4TRIBByuF6dhKTMC0KoZqv7/CQ8XhZlWhbphNO7f0IeY5qGd/mhOPRbNRQEt0CWL
+N+NBELK9xB5nSEpUxejWq8XbBz+wtn0SE/mNev/ebVYNcWBVJdQJyBotfSCjeJh0
+dXAbxE/5X3x7no5S9MFmTn/BIBFTnK2RrNiaSPZUjEOVL8qDTnPSq5VLUVRK0QNl
+WEX7/Y5BCYlxFpc6inK1RvYIcU4ZR15ZyVsrcMvdapyU5XNL+wyucR8Qb09itulC
+VuWyjJoPyPUuL5ofaquAhHny9Owpwnwfatp/9Fobcklb8FcFQfonv/Laf8aOmv91
+g+C1HpSFRK6fUY97BokMIiovyVXRRSNki4wUoOacHwwU1WBi73qDNkJHaI9l9IpW
+CLgl5IAv1z52W/RXhGFo14D8Q+niQL6QlBYXSgBu+3B4PDrbr0ZTk86nL6N9GwIM
+yhuKAyZUbNrCZyPO1klkpxC06/7wQX85uq5a+myb+GANXT3bQ7wrhF3lcz3HSexH
+v36KG5s6TJObJde+f329eBM5u1skUOXqCREKgM/lZQNC4BxXtIGKlwJ5LXCsL42F
+bfo/5aUY/iKlfr940WnOHeh7uxYCqGjTDmv5uRn4sE/r8ZpZg4KLpKVU3TaXeOki
+TcoCKaBtDxq/6FqxWGN98BEVTd/2grQRU0kHg3a1UvOvW2ExUvS4Kvo2Izt4QINj
+pDFRCrpKwKH7kTeFt2RmmfQHiqDbAH78O6nnr49rGg9XcN+vKxtXRJJnvYwbHIAz
+5MT//uP6XYImianWVOyUpM1gCaUVdRqw9zKMvuttddLYxjSNCUOu0+hlUrQ00mvt
+QJU2PNqmFq+po7p5ZLk/4wcyJz/MxZ664mJLaecX2WknG6UZJ8vBGupxOTVGarn0
+U2RFe4OGIOx7aFFGDoi2JgOseUgTdMhUXiWIIZCy3tq992BaGG8mpMXoMWkdUYz9
+kC9dXONb8yYNwyL3WKEKfgWAJyuOWC428mvUYX9VpHSUHl7maEWNkZASm6coyrXN
+MbA5bioA+BdTTtPn/DzlvkfagxrWa1EAQhSN7QY9U/AaCkkIQxOJqMqD6FDjTzAD
+mAH5vE0dNlGZLWw5zMgI6nml71UrKwAUiL6oMhCH6OG/jSp9sQL9k9DhSYDPyGKm
+LKtOZ3eSQE/ltd8aA5iEXyfG3QSTOwmPFuMrI4LKvITlFA==
+-----END PGP MESSAGE-----
\ No newline at end of file
diff --git a/crypto/test/data/openpgp/big-aead-msg.asc b/crypto/test/data/openpgp/big-skesk-aead-msg.asc
similarity index 100%
rename from crypto/test/data/openpgp/big-aead-msg.asc
rename to crypto/test/data/openpgp/big-skesk-aead-msg.asc
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 09b4dbc5b1..fab7c485e3 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -144,6 +144,26 @@ public class PgpCryptoRefreshTest
             "Zjd4SG7Tv4RJHeycolKmqSHDoK5XlOsA7vlw50nKuRjDyRfsPOFDfHz8hR/z7D1i" +
             "HST68tjRCRmwqeqVgusCmBlXrXzYTkPXGtmZl2+EYazSACQFVg==");
 
+        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-x25519-aead-ocb-encr
+        // encrypts the cleartext string "Hello, world!" for the sample certificate v6Certificate
+        // V6 PKESK + V2 SEIPD X25519 AES-128 OCB
+        private readonly byte[] v6pkesk_v2seipd_aes128_ocb = Base64.Decode(
+            "wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO" +
+            "WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS" +
+            "aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l" +
+            "yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo" +
+            "bhF30A+IitsxxA==");
+
+        // from the "OpenPGP interoperability test suite"
+        // https://tests.sequoia-pgp.org/#Encrypt-Decrypt_roundtrip_with_minimal_key_from_RFC9760
+        // encrypts the cleartext string "Hello World :)" for the sample certificate v6Certificate
+        // V3 PKESK + V1 SEIPD X25519 AES-256
+        private readonly byte[] v3pkesk_v1seipd_aes256 = Base64.Decode(
+            "wVQDEsg/HnBvYwgZEFZQspuRTLEGqQ4oEX+0ap/cDogTvDbh+Fu5K6O7ZCkpCZu4" +
+            "g6JfGwkmmqn6Ekff2LPS+jcsgz4S3y+90y7zg+bw6jgy81vJYZLSPwHPmq3ld0oV" +
+            "codBvOUSOAvARPpDCHvAOyMT+ZmYEbbQK/ahc3P6HGDArsfcAETvsIHBE8U45o4g" +
+            "poZLYyxi0A==");
+
         private readonly char[] emptyPassphrase = Array.Empty<char>();
 
         [Test]
@@ -947,7 +967,7 @@ public void Version6SkeskVersion2SeipdTest()
                 Arrays.Fill(plaintext, 0);
 
                 Stream message = PgpUtilities.GetDecoderStream(
-                    SimpleTest.GetTestDataAsStream("openpgp.big-aead-msg.asc"));
+                    SimpleTest.GetTestDataAsStream("openpgp.big-skesk-aead-msg.asc"));
 
                 PgpObjectFactory factory = new PgpObjectFactory(message);
                 PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
@@ -1018,7 +1038,106 @@ public void SkeskWithArgon2Test()
                     var stream = encData0.GetDataStreamRaw(wrongpassword);
                 });
             }
+        }
+
+        [Test]
+        public void PkeskTest()
+        {
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6UnlockedSecretKey);
+            PgpSecretKey[] secretKeys = secretKeyRing.GetSecretKeys().ToArray();
+            PgpSecretKey encryptionSubkey = secretKeys[1];
+            PgpPrivateKey privKey = encryptionSubkey.ExtractPrivateKey(emptyPassphrase);
+
+            // V6 PKESK + V2 SEIPD X25519 AES-128 OCB
+            {
+                byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+                PgpObjectFactory factory = new PgpObjectFactory(v6pkesk_v2seipd_aes128_ocb);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encData is null);
+
+                var encData0 = encData[0] as PgpPublicKeyEncryptedData;
+                FailIf("invalid PgpPublicKeyEncryptedData", encData0 is null);
+
+                IsEquals(encryptionSubkey.KeyId, encData0.KeyId);
+                IsTrue(Arrays.AreEqual(encryptionSubkey.GetFingerprint(), encData0.GetKeyFingerprint()));
+                IsEquals(SymmetricKeyAlgorithmTag.Aes128, encData0.GetSymmetricAlgorithm(privKey));
+
+                using (var stream = encData0.GetDataStream(privKey))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (var ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        var decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
+
+            /*
+             *  V6 PKESK + V2 SEIPD AEAD encrypted message that spans over 4 chunks
+             *  (chunk size 512 octets)
+             *  2000 octets of /dev/zero encrypted with sample V6 certificate from
+             *  crypto-refresh Appendix A.3 and AES-256 in OCB mode.
+             *  Generated with gosop 2.0.0-alpha
+             *  Session key CFB73D46CF7C13B7535227BEDB5B2D8B4023C5B58289D19CF2C33B0DB388B0B6
+             */
+            {
+                var plaintext = new byte[2000];
+                Arrays.Fill(plaintext, 0);
+
+                Stream message = PgpUtilities.GetDecoderStream(
+                    SimpleTest.GetTestDataAsStream("openpgp.big-pkesk-aead-msg.asc"));
+
+                PgpObjectFactory factory = new PgpObjectFactory(message);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encData is null);
+
+                var encData0 = encData[0] as PgpPublicKeyEncryptedData;
+                FailIf("invalid PgpPublicKeyEncryptedData", encData0 is null);
+
+                IsEquals(encryptionSubkey.KeyId, encData0.KeyId);
+                IsEquals(SymmetricKeyAlgorithmTag.Aes256, encData0.GetSymmetricAlgorithm(privKey));
+
+                using (var stream = encData0.GetDataStream(privKey))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (var ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        var decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
 
+            // V3 PKESK + V1 SEIPD X25519 AES-256
+            {
+                byte[] plaintext = Encoding.UTF8.GetBytes("Hello World :)");
+                PgpObjectFactory factory = new PgpObjectFactory(v3pkesk_v1seipd_aes256);
+                PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encData is null);
+
+                var encData0 = encData[0] as PgpPublicKeyEncryptedData;
+                FailIf("invalid PgpPublicKeyEncryptedData", encData0 is null);
+
+                IsEquals(encryptionSubkey.KeyId, encData0.KeyId);
+                IsEquals(SymmetricKeyAlgorithmTag.Aes256, encData0.GetSymmetricAlgorithm(privKey));
+
+                using (var stream = encData0.GetDataStream(privKey))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (var ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        var decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
         }
 
         public override string Name => "PgpCryptoRefreshTest";
@@ -1039,6 +1158,7 @@ public override void PerformTest()
             Version6GenerateAndVerifyInlineSignatureTest();
             Version6SkeskVersion2SeipdTest();
             SkeskWithArgon2Test();
+            PkeskTest();
         }
     }
 }
\ No newline at end of file
diff --git a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
index 3513047523..a9122ba29c 100644
--- a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
+++ b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
@@ -1,5 +1,6 @@
 using NUnit.Framework;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.Test;
 using System;
@@ -114,6 +115,17 @@ private static PgpPublicKeyRingBundle CreateBundle(params PgpPublicKeyRing[] key
                 return new PgpPublicKeyRingBundle(ms.ToArray());
             }
         }
+        private static PgpSecretKeyRingBundle CreateBundle(params PgpSecretKeyRing[] keyrings)
+        {
+            using (MemoryStream ms = new MemoryStream())
+            {
+                foreach (var keyring in keyrings)
+                {
+                    keyring.Encode(ms);
+                }
+                return new PgpSecretKeyRingBundle(ms.ToArray());
+            }
+        }
 
         private void VerifyMultipleInlineSignaturesTest(byte[] message, PgpPublicKeyRingBundle bundle, bool shouldFail = false)
         {
@@ -149,6 +161,70 @@ private void VerifyMultipleInlineSignaturesTest(byte[] message, PgpPublicKeyRing
             }
         }
 
+        [Test]
+        public void MultiplePkeskTest()
+        {
+            // Encrypt-Decrypt roundtrip with multiple keys: the plaintext
+            // "Hello World :)" is encrypted with the X25519 sample key from
+            // Appendix A.3 of crypto-refresh and the 'Alice' ECDH key from
+            // "OpenPGP Example Keys and Certificates"
+            byte[] message = Base64.Decode(
+                "wVQDEsg/HnBvYwgZaeKxsIieN+FvNLxmgMfRKJZGKt8vAa5BYX2k0QAetCMpCQbE" +
+                "mvXtq2XatB3H8NG7zlY2dyYKcHAK0xvgAo8YbinpCZ+xOciOkmDBXgNHZva51fIe" +
+                "thIBB0CRPS2kBUVTTtVLGjBKVCmc+KoPTzUXqVpPJdgiPmNvGTBME7unL3IP2CdO" +
+                "hL+uO3LVBJGfRy3JJDH1SIQhQ7oS47AFIOjpG0R0CBtf8M6dzwDSPwG1BrsfRn86" +
+                "mFm666ZINIHL1IDH1HQVF5OYxcRRVFjTJhms03+nu6N8I6Vy2G5yekVb1Vh2tM39" +
+                "/aGWVXTHJw==");
+
+            PgpSecretKeyRingBundle bundle = CreateBundle(
+                new PgpSecretKeyRing(aliceSecretkey),
+                new PgpSecretKeyRing(v6UnlockedSecretKey));
+
+            byte[] plaintext = Encoding.UTF8.GetBytes("Hello World :)");
+            PgpObjectFactory factory = new PgpObjectFactory(message);
+            PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+            FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+            IsEquals(2, encDataList.Count);
+
+            // decrypt with crypto-refresh sample X25519 key
+            var encData = encDataList[0] as PgpPublicKeyEncryptedData;
+            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+            PgpSecretKey secKey = bundle.GetSecretKey(encData.KeyId);
+            PgpPrivateKey privKey = secKey.ExtractPrivateKey(emptyPassphrase);
+            using (var stream = encData.GetDataStream(privKey))
+            {
+                factory = new PgpObjectFactory(stream);
+                PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                using (var ms = new MemoryStream())
+                {
+                    lit.GetDataStream().CopyTo(ms);
+                    var decrypted = ms.ToArray();
+                    IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                }
+            }
+
+            // decrypt with 'Alice' ECDH key
+            factory = new PgpObjectFactory(message);
+            encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+            encData = encDataList[1] as PgpPublicKeyEncryptedData;
+            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+            secKey = bundle.GetSecretKey(encData.KeyId);
+            privKey = secKey.ExtractPrivateKey(emptyPassphrase);
+            using (var stream = encData.GetDataStream(privKey))
+            {
+                factory = new PgpObjectFactory(stream);
+                PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                using (var ms = new MemoryStream())
+                {
+                    lit.GetDataStream().CopyTo(ms);
+                    var decrypted = ms.ToArray();
+                    IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                }
+            }
+
+        }
+
         [Test]
         public void MultipleInlineSignatureTest()
         {
@@ -435,6 +511,8 @@ public void Version5InlineSignatureTest()
 
         public override void PerformTest()
         {
+            MultiplePkeskTest();
+
             MultipleInlineSignatureTest();
             GenerateAndVerifyMultipleInlineSignatureTest();
 

From b063307daf1bb2de4227640886e0ec7ac7470746 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 18 Apr 2024 18:26:41 +0200
Subject: [PATCH 24/37] V4 SKESK + V1 SEIPD Encryption with Argon2 s2k

---
 crypto/src/bcpg/S2k.cs                        | 125 ++++++++++++++++++
 .../src/openpgp/PgpEncryptedDataGenerator.cs  |  40 ++++++
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  |  53 ++++++++
 3 files changed, 218 insertions(+)

diff --git a/crypto/src/bcpg/S2k.cs b/crypto/src/bcpg/S2k.cs
index 3dafe87592..05adc530b3 100644
--- a/crypto/src/bcpg/S2k.cs
+++ b/crypto/src/bcpg/S2k.cs
@@ -1,5 +1,7 @@
 using System;
 using System.IO;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
@@ -123,6 +125,12 @@ public S2k(byte[] salt, int passes, int parallelism, int memorySizeExponent)
             this.memorySizeExponent = memorySizeExponent;
         }
 
+        /// <summary>Constructs a specifier for an S2K method using Argon2</summary>
+        public S2k(Argon2Parameters argon2Params)
+            :this(argon2Params.Salt, argon2Params.Passes, argon2Params.Parallelism, argon2Params.MemSizeExp)
+        {
+        }
+
         public virtual int Type
         {
 			get { return type; }
@@ -214,5 +222,122 @@ public override void Encode(
                     throw new InvalidOperationException($"Unknown S2K type {type}");
             }
         }
+
+        /// <summary>
+        /// Parameters for Argon2 S2K
+        /// <see href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#s2k-argon2">Sect. 3.7.1.4 of crypto-refresh</see>see>
+        /// </summary>
+        public class Argon2Parameters
+        {
+            private readonly byte[] salt;
+            private readonly int passes;
+            private readonly int parallelism;
+            private readonly int memSizeExp;
+
+            internal byte[] Salt => salt;
+            internal int Passes => passes;
+            internal int Parallelism => parallelism;
+            internal int MemSizeExp => memSizeExp;
+
+            /// <summary>
+            /// Uniformly safe and recommended parameters not tailored to any hardware.
+            /// Uses Argon2id, 1 pass, 4 parallelism, 2 GiB RAM.
+            /// <see href="https://www.rfc-editor.org/rfc/rfc9106.html#section-4-6.1"> RFC 9106: �4. Parameter Choice</see>
+            /// </summary>
+            public Argon2Parameters()
+                :this (CryptoServicesRegistrar.GetSecureRandom())
+            {
+            }
+
+
+            /// <summary>
+            /// Uniformly safe and recommended parameters not tailored to any hardware.
+            /// Uses Argon2id, 1 pass, 4 parallelism, 2 GiB RAM.
+            /// <see href="https://www.rfc-editor.org/rfc/rfc9106.html#section-4-6.1"> RFC 9106: �4. Parameter Choice</see>
+            /// </summary>
+            /// <param name="secureRandom"></param>
+            public Argon2Parameters(SecureRandom secureRandom)
+                : this(1, 4, 21, secureRandom)
+            {
+            }
+
+            /// <summary>
+            /// Create customized Argon2 S2K parameters.
+            /// </summary>
+            /// <param name="passes">number of iterations, must be greater than 0</param>
+            /// <param name="parallelism">number of lanes, must be greater 0</param>
+            /// <param name="memSizeExp">exponent for memory consumption, must be between 3+ceil(log_2(p)) and 31</param>
+            /// <param name="secureRandom">A secure random generator</param>
+            /// <exception cref="ArgumentException"></exception>
+            public Argon2Parameters(int passes, int parallelism, int memSizeExp, SecureRandom secureRandom)
+                :this(GenerateSalt(secureRandom), passes, parallelism, memSizeExp)
+            {
+            }
+
+            /// <summary>
+            /// Create customized Argon2 S2K parameters.
+            /// </summary>
+            /// <param name="salt">16 bytes of random salt</param>
+            /// <param name="passes">number of iterations, must be greater than 0</param>
+            /// <param name="parallelism">number of lanes, must be greater 0</param>
+            /// <param name="memSizeExp">exponent for memory consumption, must be between 3+ceil(log_2(p)) and 31</param>
+            /// <exception cref="ArgumentException"></exception>
+            public Argon2Parameters(byte[] salt, int passes, int parallelism, int memSizeExp)
+            {
+                if (salt.Length != 16)
+                {
+                    throw new ArgumentException("Argon2 uses 16 bytes of salt");
+                }
+                this.salt = salt;
+
+                if (passes < 1)
+                {
+                    throw new ArgumentException("Number of passes MUST be positive, non-zero");
+                }
+                this.passes = passes;
+
+                if (parallelism < 1)
+                {
+                    throw new ArgumentException("Parallelism MUST be positive, non-zero.");
+                }
+                this.parallelism = parallelism;
+
+                // log_2(p) = log_e(p) / log_e(2)
+                double log2_p = System.Math.Log(parallelism) / System.Math.Log(2);
+                // see https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-argon2
+                if (memSizeExp < (3 + System.Math.Ceiling(log2_p)) || memSizeExp > 31)
+                {
+                    throw new ArgumentException("Memory size exponent MUST be between 3+ceil(log_2(parallelism)) and 31");
+                }
+                this.memSizeExp = memSizeExp;
+            }
+            
+            /// <summary>
+            /// Uniformly safe and recommended parameters not tailored to any hardware.
+            /// Uses Argon2id, 1 pass, 4 parallelism, 2 GiB RAM.
+            /// <see href="https://www.rfc-editor.org/rfc/rfc9106.html#section-4-6.1"> RFC 9106: �4. Parameter Choice</see>
+            /// </summary>
+            public static Argon2Parameters UniversallyRecommendedParameters()
+            {
+                return new Argon2Parameters(1, 4, 21, CryptoServicesRegistrar.GetSecureRandom());
+            }
+
+            /// <summary>
+            /// Recommended parameters for memory constrained environments(64MiB RAM).
+            /// Uses Argon2id with 3 passes, 4 lanes and 64 MiB RAM.
+            /// <see href="https://www.rfc-editor.org/rfc/rfc9106.html#section-4-6.1"> RFC 9106: �4. Parameter Choice</see>
+            /// </summary>
+            public static Argon2Parameters MemoryConstrainedParameters()
+            {
+                return new Argon2Parameters(3, 4, 16, CryptoServicesRegistrar.GetSecureRandom());
+            }
+
+            private static byte[] GenerateSalt(SecureRandom secureRandom)
+            {
+                byte[] salt = new byte[16];
+                secureRandom.NextBytes(salt);
+                return salt;
+            }
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index 13648d1e8f..a423009bf1 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -402,6 +402,46 @@ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgori
             methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)));
         }
 
+        /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public void AddMethod(char[] passPhrase, S2k.Argon2Parameters argon2Parameters)
+        {
+            DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, false), true, argon2Parameters);
+        }
+
+        /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public void AddMethodUtf8(char[] passPhrase, S2k.Argon2Parameters argon2Parameters)
+        {
+            DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, true), true, argon2Parameters);
+        }
+
+        /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public void AddMethodRaw(byte[] rawPassPhrase, S2k.Argon2Parameters argon2Parameters)
+        {
+            DoAddMethod(rawPassPhrase, false, argon2Parameters);
+        }
+
+        internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, S2k.Argon2Parameters argon2Parameters)
+        {
+            S2k s2k = new S2k(argon2Parameters);
+
+            methods.Add(
+                new PbeMethod(
+                    defAlgorithm,
+                    s2k,
+                    PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)
+                ));
+        }
+
         /// <summary>Add a public key encrypted session key to the encrypted object.</summary>
         public void AddMethod(PgpPublicKey key)
         {
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index fab7c485e3..c7eb3aa637 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -1038,6 +1038,59 @@ public void SkeskWithArgon2Test()
                     var stream = encData0.GetDataStreamRaw(wrongpassword);
                 });
             }
+
+            // encrypt-decrypt roundtrip - V4 SKESK + V1 SEIPD
+            {
+                // encrypt
+                byte[] enc;
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    byte[] buffer = new byte[1024];
+                    PgpEncryptedDataGenerator endDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom());
+                    endDataGen.AddMethodRaw(password, S2k.Argon2Parameters.MemoryConstrainedParameters());
+
+                    using (Stream cOut = endDataGen.Open(ms, buffer))
+                    {
+                        using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
+                        {
+                            PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+                            DateTime modificationTime = DateTime.UtcNow;
+
+                            using (Stream lOut = literalDataGen.Open(
+                                new UncloseableStream(bcOut),
+                                PgpLiteralData.Utf8,
+                                PgpLiteralData.Console,
+                                plaintext.Length,
+                                modificationTime))
+                            {
+                                lOut.Write(plaintext, 0, plaintext.Length);
+                            }
+                        }
+                    }
+
+                    enc = ms.ToArray();
+                }
+
+                // decrypt
+                PgpObjectFactory factory = new PgpObjectFactory(enc);
+                PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+                FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+                PgpPbeEncryptedData encData = encDataList[0] as PgpPbeEncryptedData;
+                FailIf("invalid PgpPbeEncryptedData", encData is null);
+
+                using (Stream stream = encData.GetDataStreamRaw(password))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (MemoryStream ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        byte[] decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                    }
+                }
+            }
         }
 
         [Test]

From 68d3821c2446e6b36fc23c638a835cd566b76581 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sat, 20 Apr 2024 18:43:59 +0200
Subject: [PATCH 25/37] V3 PKESK + V1 SEIPD Encryption with X25519 and X448

---
 .../src/openpgp/PgpEncryptedDataGenerator.cs  | 157 ++++++++++++++----
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 131 ++++++++-------
 2 files changed, 203 insertions(+), 85 deletions(-)

diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index a423009bf1..afc7853007 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-
+using System.Text;
 using Org.BouncyCastle.Asn1.Cryptlib;
 using Org.BouncyCastle.Asn1.EdEC;
 using Org.BouncyCastle.Crypto;
@@ -46,16 +46,19 @@ private class PbeMethod
             : EncMethod
         {
             private readonly S2k s2k;
+            private readonly int skeskVersion;
 
             internal PbeMethod(
                 SymmetricKeyAlgorithmTag  encAlgorithm,
                 S2k                       s2k,
-                KeyParameter              key)
+                KeyParameter              key,
+                int                       skeskVersion)
                 : base(PacketTag.SymmetricKeyEncryptedSessionKey)
             {
                 this.encAlgorithm = encAlgorithm;
                 this.s2k = s2k;
                 this.key = key;
+                this.skeskVersion = skeskVersion;
             }
 
             public KeyParameter GetKey()
@@ -91,12 +94,14 @@ private class PubMethod
 			internal PgpPublicKey pubKey;
             internal bool sessionKeyObfuscation;
             internal byte[][] data;
+            private readonly int pkeskVersion;
 
-            internal PubMethod(PgpPublicKey pubKey, bool sessionKeyObfuscation)
+            internal PubMethod(PgpPublicKey pubKey, bool sessionKeyObfuscation, int pkeskVersion)
                 : base(PacketTag.PublicKeyEncryptedSession)
             {
                 this.pubKey = pubKey;
                 this.sessionKeyObfuscation = sessionKeyObfuscation;
+                this.pkeskVersion = pkeskVersion;
             }
 
             public override void AddSessionInfo(
@@ -112,6 +117,86 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
             {
                 var cryptoPublicKey = pubKey.GetKey();
 
+                if (pubKey.Algorithm == PublicKeyAlgorithmTag.X25519 || pubKey.Algorithm == PublicKeyAlgorithmTag.X448)
+                {
+                    IAsymmetricCipherKeyPairGenerator kpGen;
+                    IRawAgreement agreement;
+                    AsymmetricCipherKeyPair ephemeral;
+                    byte[] ephPubEncoding;
+                    IDigest digestForHkdf;
+                    byte[] hkdfInfo;
+                    SymmetricKeyAlgorithmTag wrappingAlgo;
+
+                    if (pubKey.Algorithm == PublicKeyAlgorithmTag.X25519)
+                    {
+                        agreement = new X25519Agreement();
+                        kpGen = new X25519KeyPairGenerator();
+                        kpGen.Init(new X25519KeyGenerationParameters(random));
+                        digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+                        hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X25519");
+                        wrappingAlgo = SymmetricKeyAlgorithmTag.Aes128;
+                        ephemeral = kpGen.GenerateKeyPair();
+                        ephPubEncoding = new byte[X25519PublicKeyParameters.KeySize];
+                        ((X25519PublicKeyParameters)ephemeral.Public).Encode(ephPubEncoding, 0);
+                    }
+                    else
+                    {
+                        // X448
+                        agreement = new X448Agreement();
+                        kpGen = new X448KeyPairGenerator();
+                        kpGen.Init(new X448KeyGenerationParameters(random));
+                        digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha512);
+                        hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X448");
+                        wrappingAlgo = SymmetricKeyAlgorithmTag.Aes256;
+                        ephemeral = kpGen.GenerateKeyPair();
+                        ephPubEncoding = new byte[X448PublicKeyParameters.KeySize];
+                        ((X448PublicKeyParameters)ephemeral.Public).Encode(ephPubEncoding, 0);
+                    }
+
+                    agreement.Init(ephemeral.Private);
+                    byte[] sharedSecret = new byte[agreement.AgreementSize];
+                    agreement.CalculateAgreement(cryptoPublicKey, sharedSecret, 0);
+
+                    byte[] pubKeyMaterial = ((OctetArrayBcpgKey)pubKey.PublicKeyPacket.Key).GetKey();
+                    byte[] ikm = Arrays.ConcatenateAll(ephPubEncoding, pubKeyMaterial, sharedSecret);
+                    byte[] hkdfSalt = Array.Empty<byte>();
+                    var hkdfParams = new HkdfParameters(ikm, hkdfSalt, hkdfInfo);
+                    var hkdfGen = new HkdfBytesGenerator(digestForHkdf);
+                    hkdfGen.Init(hkdfParams);
+                    var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(wrappingAlgo)];
+                    hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+                    
+                    KeyParameter kek = ParameterUtilities.CreateKeyParameter("AES", hkdfOutput);
+                    var wrapper = PgpUtilities.CreateWrapper(wrappingAlgo);
+                    wrapper.Init(true, kek);
+
+                    int offset = 0; 
+                    int length = sessionInfo.Length - 2; // no checksum for X25519 and X448 keys
+                    // for X25519 and X448 keys the SymmetricKeyAlgorithmTag, when present (V3 PKESK)
+                    // is not encrypted, is prepended to the ESK in plaintext
+                    if (pkeskVersion == PublicKeyEncSessionPacket.Version3)
+                    {
+                        offset = 1;
+                        length--;
+                    }
+                    var keyBytes = wrapper.Wrap(sessionInfo, offset, length);
+
+                    byte[] esk;
+                    using (var ms = new MemoryStream())
+                    {
+                        ms.Write(ephPubEncoding, 0, ephPubEncoding.Length);
+                        if (pkeskVersion == PublicKeyEncSessionPacket.Version3)
+                        {
+                            // Unencrypted SymmetricKeyAlgorithmTag (V3 PKESK only)
+                            ms.WriteByte(sessionInfo[0]);
+                        }
+                        ms.Write(keyBytes, 0, keyBytes.Length);
+                        esk = ms.ToArray();
+                    }
+
+                    return esk;
+                }
+
                 if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
                 {
                     IBufferedCipher c;
@@ -275,6 +360,16 @@ private byte[][] ProcessSessionInfo(byte[] encryptedSessionInfo)
                 case PublicKeyAlgorithmTag.ECDH:
                     data = new byte[1][]{ encryptedSessionInfo };
                     break;
+                case PublicKeyAlgorithmTag.X25519:
+                case PublicKeyAlgorithmTag.X448:
+                    int ephemeralKeyLen = pubKey.Algorithm == PublicKeyAlgorithmTag.X25519 ? 32 : 56;
+                    byte[] ephemeralKey = new byte[ephemeralKeyLen];
+                    byte[] encryptedSessionKey = new byte[encryptedSessionInfo.Length - ephemeralKeyLen];
+                    Array.Copy(encryptedSessionInfo, 0, ephemeralKey, 0, ephemeralKeyLen);
+                    Array.Copy(encryptedSessionInfo, ephemeralKeyLen, encryptedSessionKey, 0, encryptedSessionKey.Length);
+
+                    data = new byte[][] { ephemeralKey, encryptedSessionKey };
+                    break;
                 default:
                     throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
                 }
@@ -306,20 +401,21 @@ public override void Encode(BcpgOutputStream pOut)
         private readonly SymmetricKeyAlgorithmTag defAlgorithm;
         private readonly SecureRandom rand;
 
-		public PgpEncryptedDataGenerator(
+        private readonly int skeskVersion;
+        private readonly int pkeskVersion;
+        private readonly int seipdVersion;
+
+        public PgpEncryptedDataGenerator(
 			SymmetricKeyAlgorithmTag encAlgorithm)
+            :this(encAlgorithm, CryptoServicesRegistrar.GetSecureRandom(), false, false)
 		{
-			this.defAlgorithm = encAlgorithm;
-            this.rand = CryptoServicesRegistrar.GetSecureRandom();
 		}
 
 		public PgpEncryptedDataGenerator(
 			SymmetricKeyAlgorithmTag	encAlgorithm,
 			bool						withIntegrityPacket)
-		{
-			this.defAlgorithm = encAlgorithm;
-			this.withIntegrityPacket = withIntegrityPacket;
-            this.rand = CryptoServicesRegistrar.GetSecureRandom();
+            : this(encAlgorithm, CryptoServicesRegistrar.GetSecureRandom(), false, withIntegrityPacket)
+        {
         }
 
         /// <summary>Existing SecureRandom constructor.</summary>
@@ -328,12 +424,8 @@ public PgpEncryptedDataGenerator(
         public PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag	encAlgorithm,
             SecureRandom				random)
+            : this(encAlgorithm, random, false, false)
         {
-            if (random == null)
-                throw new ArgumentNullException(nameof(random));
-
-            this.defAlgorithm = encAlgorithm;
-            this.rand = random;
         }
 
 		/// <summary>Creates a cipher stream which will have an integrity packet associated with it.</summary>
@@ -341,13 +433,8 @@ public PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag	encAlgorithm,
             bool						withIntegrityPacket,
             SecureRandom				random)
+            :this(encAlgorithm, random, false, withIntegrityPacket)
         {
-            if (random == null)
-                throw new ArgumentNullException(nameof(random));
-
-            this.defAlgorithm = encAlgorithm;
-            this.rand = random;
-            this.withIntegrityPacket = withIntegrityPacket;
         }
 
         /// <summary>Base constructor.</summary>
@@ -358,13 +445,24 @@ public PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag	encAlgorithm,
             SecureRandom				random,
             bool						oldFormat)
+            :this (encAlgorithm, random, oldFormat, false)
         {
-            if (random == null)
-                throw new ArgumentNullException(nameof(random));
+        }
 
+        private PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            SecureRandom random,
+            bool oldFormat,
+            bool withIntegrityPacket)
+        {
+            this.rand = random ?? throw new ArgumentNullException(nameof(random));
             this.defAlgorithm = encAlgorithm;
-            this.rand = random;
             this.oldFormat = oldFormat;
+            this.withIntegrityPacket = withIntegrityPacket;
+
+            skeskVersion = SymmetricKeyEncSessionPacket.Version4;
+            pkeskVersion = PublicKeyEncSessionPacket.Version3;
+            seipdVersion = SymmetricEncIntegrityPacket.Version1;
         }
 
         /// <summary>Add a PBE encryption method to the encrypted object.</summary>
@@ -399,7 +497,7 @@ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgori
         {
             S2k s2k = PgpUtilities.GenerateS2k(s2kDigest, 0x60, rand);
 
-            methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)));
+            methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase), skeskVersion));
         }
 
         /// <summary>Add a PBE encryption method to the encrypted object.</summary>
@@ -438,7 +536,8 @@ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, S2k.Argon2
                 new PbeMethod(
                     defAlgorithm,
                     s2k,
-                    PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)
+                    PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase),
+                    skeskVersion
                 ));
         }
 
@@ -455,7 +554,7 @@ public void AddMethod(PgpPublicKey key, bool sessionKeyObfuscation)
                 throw new ArgumentException("passed in key not an encryption key!");
             }
 
-            methods.Add(new PubMethod(key, sessionKeyObfuscation));
+            methods.Add(new PubMethod(key, sessionKeyObfuscation, pkeskVersion));
         }
 
         private void AddCheckSum(
@@ -478,7 +577,7 @@ private void AddCheckSum(
 		private byte[] CreateSessionInfo(SymmetricKeyAlgorithmTag algorithm, KeyParameter key)
 		{
             int keyLength = key.KeyLength;
-			byte[] sessionInfo = new byte[keyLength + 3];
+            byte[] sessionInfo = new byte[keyLength + 3];
 			sessionInfo[0] = (byte)algorithm;
             key.CopyTo(sessionInfo, 1, keyLength);
 			AddCheckSum(sessionInfo);
@@ -516,7 +615,7 @@ private Stream Open(
             {
                 if (methods[0] is PbeMethod pbeMethod)
                 {
-					key = pbeMethod.GetKey();
+                    key = pbeMethod.GetKey();
                 }
                 else if (methods[0] is PubMethod pubMethod)
                 {
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index c7eb3aa637..74ad744f14 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -507,13 +507,10 @@ public void Version6Ed25519KeyPairCreationTest()
             SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
 
             // encrypt-decrypt test
-            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
-
-            PgpSecretKeyRing bobSecring = new PgpSecretKeyRing(v6UnlockedSecretKey);
-            PgpSecretKey bobEncryptionKey = bobSecring.GetSecretKeys().ToArray()[1];
-            AsymmetricCipherKeyPair bob = GetKeyPair(bobEncryptionKey);
-
-            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
+            EncryptDecryptRoundtrip(
+                Encoding.UTF8.GetBytes("Hello, World!"),
+                subKey.PublicKey,
+                subKey.ExtractPrivateKey(emptyPassphrase));
         }
 
         [Test]
@@ -593,12 +590,11 @@ public void Version6Ed448KeyPairCreationTest()
             // Sign-Verify roundtrip
             SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
 
-            // Encrypt-Decrypt test
-            AsymmetricCipherKeyPair alice = GetKeyPair(subKey);
-            IAsymmetricCipherKeyPairGenerator kpGen = new X448KeyPairGenerator();
-            kpGen.Init(new X448KeyGenerationParameters(rand));
-            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
-            IsTrue("X448 agreement failed", EncryptThenDecryptTest(alice, bob, subKey.PublicKey.Algorithm));
+            // Encrypt-Decrypt roundtrip
+            EncryptDecryptRoundtrip(
+                Encoding.UTF8.GetBytes("Hello, World!"),
+                subKey.PublicKey,
+                subKey.ExtractPrivateKey(emptyPassphrase));
         }
 
         [Test]
@@ -641,13 +637,11 @@ public void Version6LockedSecretKeyParsingTest()
             IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
             IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
 
-            // decrypt test
-            AsymmetricCipherKeyPair alice = GetKeyPair(encryptionKey, passphrase);
-            IAsymmetricCipherKeyPairGenerator kpGen = new X25519KeyPairGenerator();
-            kpGen.Init(new X25519KeyGenerationParameters(new SecureRandom()));
-            AsymmetricCipherKeyPair bob = kpGen.GenerateKeyPair();
-            IsTrue("X25519 agreement failed", EncryptThenDecryptTest(alice, bob, encryptionKey.PublicKey.Algorithm));
-
+            // encrypt-decrypt
+            EncryptDecryptRoundtrip(
+                Encoding.UTF8.GetBytes("Hello, World!"),
+                encryptionKey.PublicKey,
+                encryptionKey.ExtractPrivateKey(passphrase.ToCharArray()));
 
             // Encode-Decode roundtrip
             using (MemoryStream ms = new MemoryStream())
@@ -799,42 +793,6 @@ private void VerifyEncodedSignature(byte[] sigPacket, byte[] data, PgpPublicKey
             VerifySignature(signature, data, signer, shouldFail);
         }
 
-        private static AsymmetricCipherKeyPair GetKeyPair(PgpSecretKey secretKey, string password = "")
-        {
-            return new AsymmetricCipherKeyPair(
-                secretKey.PublicKey.GetKey(),
-                secretKey.ExtractPrivateKey(password.ToCharArray()).Key);
-        }
-
-        private static bool EncryptThenDecryptTest(AsymmetricCipherKeyPair alice, AsymmetricCipherKeyPair bob, PublicKeyAlgorithmTag algo)
-        {
-            IRawAgreement agreeA, agreeB;
-
-            switch (algo)
-            {
-                case PublicKeyAlgorithmTag.X25519:
-                    agreeA = new X25519Agreement();
-                    agreeB = new X25519Agreement();
-                    break;
-                case PublicKeyAlgorithmTag.X448:
-                    agreeA = new X448Agreement();
-                    agreeB = new X448Agreement();
-                    break;
-                default:
-                    throw new ArgumentException($"Unsupported algo {algo}");
-            }
-
-            agreeA.Init(alice.Private);
-            byte[] secretA = new byte[agreeA.AgreementSize];
-            agreeA.CalculateAgreement(bob.Public, secretA, 0);
-
-            agreeB.Init(bob.Private);
-            byte[] secretB = new byte[agreeB.AgreementSize];
-            agreeB.CalculateAgreement(alice.Public, secretB, 0);
-
-            return Arrays.AreEqual(secretA, secretB);
-        }
-
         [Test]
         public void Version6SkeskVersion2SeipdTest()
         {
@@ -1093,6 +1051,58 @@ public void SkeskWithArgon2Test()
             }
         }
 
+
+        private void EncryptDecryptRoundtrip(byte[] plaintext, PgpPublicKey pubKey, PgpPrivateKey privKey)
+        {
+            byte[] enc;
+            using (MemoryStream ms = new MemoryStream())
+            {
+                byte[] buffer = new byte[1024];
+                PgpEncryptedDataGenerator endDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom());
+                endDataGen.AddMethod(pubKey);
+
+                using (Stream cOut = endDataGen.Open(ms, buffer))
+                {
+                    using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
+                    {
+                        PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+                        DateTime modificationTime = DateTime.UtcNow;
+
+                        using (Stream lOut = literalDataGen.Open(
+                            new UncloseableStream(bcOut),
+                            PgpLiteralData.Utf8,
+                            PgpLiteralData.Console,
+                            plaintext.Length,
+                            modificationTime))
+                        {
+                            lOut.Write(plaintext, 0, plaintext.Length);
+                        }
+                    }
+                }
+                enc = ms.ToArray();
+            }
+
+            // decrypt
+            PgpObjectFactory factory = new PgpObjectFactory(enc);
+            PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+            FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+            PgpPublicKeyEncryptedData encData = encDataList[0] as PgpPublicKeyEncryptedData;
+            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+
+            using (Stream stream = encData.GetDataStream(privKey))
+            {
+                factory = new PgpObjectFactory(stream);
+                PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    lit.GetDataStream().CopyTo(ms);
+                    byte[] decrypted = ms.ToArray();
+                    IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                }
+            }
+        }
+
         [Test]
         public void PkeskTest()
         {
@@ -1191,6 +1201,15 @@ public void PkeskTest()
                     }
                 }
             }
+
+            // encrypt-decrypt roundtrip - V3 PKESK + V1 SEIPD X25519
+            {
+                byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+                PgpPublicKeyRing publicKeyRing = new PgpPublicKeyRing(v6Certificate);
+                PgpPublicKey pubKey = publicKeyRing.GetPublicKeys().First(k => k.IsEncryptionKey);
+
+                EncryptDecryptRoundtrip(plaintext, pubKey, privKey);
+            }
         }
 
         public override string Name => "PgpCryptoRefreshTest";

From 5e72362892f753a4850b7a670092a54c6bc5f6e0 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Wed, 24 Apr 2024 18:39:27 +0200
Subject: [PATCH 26/37] AEAD Encryption - V6 PKESK, V6 SKESK, V2 SEIPD

---
 crypto/src/bcpg/AeadEncDataPacket.cs          |  25 +-
 crypto/src/bcpg/AeadInputStream.cs            |  69 +--
 crypto/src/bcpg/AeadOutputStream.cs           | 228 +++++++++
 crypto/src/bcpg/AeadUtils.cs                  |  61 +++
 crypto/src/bcpg/PublicKeyEncSessionPacket.cs  |  26 +
 .../src/bcpg/SymmetricEncIntegrityPacket.cs   |   6 +-
 .../src/bcpg/SymmetricKeyEncSessionPacket.cs  |  39 +-
 .../src/openpgp/PgpEncryptedDataGenerator.cs  | 226 ++++++++-
 crypto/src/openpgp/PgpPbeEncryptedData.cs     |   2 +-
 .../src/openpgp/PgpPublicKeyEncryptedData.cs  |   2 +-
 crypto/src/openpgp/PgpUtilities.cs            |  30 --
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 480 ++++++++++--------
 12 files changed, 898 insertions(+), 296 deletions(-)
 create mode 100644 crypto/src/bcpg/AeadOutputStream.cs

diff --git a/crypto/src/bcpg/AeadEncDataPacket.cs b/crypto/src/bcpg/AeadEncDataPacket.cs
index 61368904b8..512a24e54b 100644
--- a/crypto/src/bcpg/AeadEncDataPacket.cs
+++ b/crypto/src/bcpg/AeadEncDataPacket.cs
@@ -20,11 +20,13 @@ public class AeadEncDataPacket
         private readonly byte m_chunkSize;
         private readonly byte[] m_iv;
 
+        public const int Version1 = 1;
+
         public AeadEncDataPacket(BcpgInputStream bcpgIn)
             : base(bcpgIn, PacketTag.ReservedAeadEncryptedData)
         {
             m_version = (byte)bcpgIn.ReadByte();
-            if (m_version != 1)
+            if (m_version != Version1)
                 throw new ArgumentException("wrong AEAD packet version: " + m_version);
 
             m_algorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
@@ -39,7 +41,7 @@ public AeadEncDataPacket(SymmetricKeyAlgorithmTag algorithm, AeadAlgorithmTag ae
             byte[] iv)
             : base(null, PacketTag.ReservedAeadEncryptedData)
         {
-            m_version = 1;
+            m_version = Version1;
             m_algorithm = algorithm;
             m_aeadAlgorithm = aeadAlgorithm;
             m_chunkSize = (byte)chunkSize;
@@ -73,5 +75,24 @@ public static int GetIVLength(AeadAlgorithmTag aeadAlgorithm)
                 throw new ArgumentException("unknown mode: " + aeadAlgorithm);
             }
         }
+
+        public byte[] GetAAData()
+        {
+            return CreateAAData(Version, Algorithm, AeadAlgorithm, ChunkSize);
+        }
+
+        public static byte[] CreateAAData(int version, SymmetricKeyAlgorithmTag symAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+        {
+            byte[] aaData = new byte[]
+            {
+                0xC0 | (byte)PacketTag.ReservedAeadEncryptedData,
+                (byte)version,
+                (byte)symAlgorithm,
+                (byte)aeadAlgorithm,
+                (byte)chunkSize
+            };
+
+            return aaData;
+        }
     }
 }
diff --git a/crypto/src/bcpg/AeadInputStream.cs b/crypto/src/bcpg/AeadInputStream.cs
index 39a5918341..e88334ab2e 100644
--- a/crypto/src/bcpg/AeadInputStream.cs
+++ b/crypto/src/bcpg/AeadInputStream.cs
@@ -26,12 +26,23 @@ internal class AeadInputStream
         private int dataOff;
         private long chunkIndex = 0;
         private long totalBytes = 0;
+        private readonly bool isV5StyleAead;
 
         private static int GetChunkLength(int chunkSize)
         {
             return 1 << (chunkSize + 6);
         }
 
+        /// <summary>
+        /// InputStream for decrypting AEAD encrypted data.
+        /// </summary>
+        /// <param name="inputStream">underlying InputStream</param>
+        /// <param name="cipher">encryption cipher</param>
+        /// <param name="secretKey">decryption key</param>
+        /// <param name="iv">initialization vector</param>
+        /// <param name="aeadAlgorithm">AEAD algorithm</param>
+        /// <param name="chunkSize">chunk size of the AEAD encryption</param>
+        /// <param name="aaData">associated data</param>
         public AeadInputStream(
             Stream inputStream,
             BufferedAeadBlockCipher cipher,
@@ -40,12 +51,37 @@ public AeadInputStream(
             AeadAlgorithmTag aeadAlgorithm,
             int chunkSize,
             byte[] aaData)
+            :this(false, inputStream, cipher, secretKey, iv, aeadAlgorithm, chunkSize, aaData)
+        {
+        }
+
+        /// <summary>
+        /// InputStream for decrypting AEAD encrypted data.
+        /// </summary>
+        /// <param name="isV5StyleAead">flavour of AEAD (OpenPGP v5 or v6)</param>
+        /// <param name="inputStream">underlying InputStream</param>
+        /// <param name="cipher">encryption cipher</param>
+        /// <param name="secretKey">decryption key</param>
+        /// <param name="iv">initialization vector</param>
+        /// <param name="aeadAlgorithm">AEAD algorithm</param>
+        /// <param name="chunkSize">chunk size of the AEAD encryption</param>
+        /// <param name="aaData">associated data</param>
+        public AeadInputStream(
+            bool isV5StyleAead,
+            Stream inputStream,
+            BufferedAeadBlockCipher cipher,
+            KeyParameter secretKey,
+            byte[] iv,
+            AeadAlgorithmTag aeadAlgorithm,
+            int chunkSize,
+            byte[] aaData)
         {
             this.inputStream = inputStream;
             this.cipher = cipher;
             this.secretKey = secretKey;
             this.aaData = aaData;
             this.iv = iv;
+            this.isV5StyleAead = isV5StyleAead;
 
             chunkLength = GetChunkLength(chunkSize);
             tagLen = AeadUtils.GetAuthTagLength(aeadAlgorithm);
@@ -87,33 +123,6 @@ public override int Read(byte[] buffer, int offset, int count)
             return supplyLen;
         }
 
-        private static byte[] GetNonce(byte[] iv, long chunkIndex)
-        {
-            byte[] nonce = Arrays.Clone(iv);
-            byte[] chunkid =  Pack.UInt64_To_BE((ulong)chunkIndex);
-            Array.Copy(chunkid, 0, nonce, nonce.Length - 8, 8);
-
-            return nonce;
-        }
-
-        private static byte[] GetAdata(bool isV5StyleAead, byte[] aaData, long chunkIndex, long totalBytes)
-        {
-            byte[] adata;
-            if (isV5StyleAead)
-            {
-                adata = new byte[13];
-                Array.Copy(aaData, 0, adata, 0, aaData.Length);
-                Array.Copy(Pack.UInt64_To_BE((ulong)chunkIndex), 0, adata, aaData.Length, 8);
-            }
-            else
-            {
-                adata = new byte[aaData.Length + 8];
-                Array.Copy(aaData, 0, adata, 0, aaData.Length);
-                Array.Copy(Pack.UInt64_To_BE((ulong)totalBytes), 0, adata, aaData.Length, 8);
-            }
-            return adata;
-        }
-
         private byte[] ReadBlock()
         {
             int dataLen = Streams.ReadFully(inputStream, buf, tagLen + tagLen, chunkLength);
@@ -130,7 +139,7 @@ private byte[] ReadBlock()
                 AeadParameters aeadParams = new AeadParameters(
                     secretKey,
                     8 * tagLen,
-                    GetNonce(iv, chunkIndex),
+                    AeadUtils.CreateNonce(iv, chunkIndex),
                     adata);
 
                 cipher.Init(false, aeadParams);
@@ -153,11 +162,11 @@ private byte[] ReadBlock()
                 // last block
                 try
                 {
-                    adata = GetAdata(false, aaData, chunkIndex, totalBytes);
+                    adata = AeadUtils.CreateLastBlockAAData(isV5StyleAead, aaData, chunkIndex, totalBytes);
                     AeadParameters aeadParams = new AeadParameters(
                         secretKey,
                         8 * tagLen,
-                        GetNonce(iv, chunkIndex),
+                        AeadUtils.CreateNonce(iv, chunkIndex),
                         adata);
 
                     cipher.Init(false, aeadParams);
diff --git a/crypto/src/bcpg/AeadOutputStream.cs b/crypto/src/bcpg/AeadOutputStream.cs
new file mode 100644
index 0000000000..8d49718661
--- /dev/null
+++ b/crypto/src/bcpg/AeadOutputStream.cs
@@ -0,0 +1,228 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities.IO;
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    internal class AeadOutputStream
+        : BaseOutputStream
+    {
+        // ported from bc-java
+
+        private readonly bool isV5StyleAead;
+        private readonly Stream outputStream;
+        private readonly byte[] data;
+        private readonly BufferedAeadBlockCipher cipher;
+        private readonly KeyParameter secretKey;
+        private readonly byte[] aaData;
+        private readonly byte[] iv;
+        private readonly int chunkLength;
+        private readonly int tagLen;
+
+        private int dataOff;
+        private long chunkIndex = 0;
+        private long totalBytes = 0;
+
+        private static int GetChunkLength(int chunkSize)
+        {
+            return 1 << (chunkSize + 6);
+        }
+
+        /// <summary>
+        /// OutputStream for AEAD encryption.
+        /// </summary>
+        /// <param name="outputStream">underlying OutputStream</param>
+        /// <param name="cipher">AEAD cipher</param>
+        /// <param name="secretKey">encryption key</param>
+        /// <param name="iv">initialization vector</param>
+        /// <param name="encAlgorithm">encryption algorithm</param>
+        /// <param name="aeadAlgorithm">AEAD algorithm</param>
+        /// <param name="chunkSize">chunk size octet of the AEAD encryption</param>
+        public AeadOutputStream(
+            Stream outputStream,
+            BufferedAeadBlockCipher cipher,
+            KeyParameter secretKey,
+            byte[] iv,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            int chunkSize)
+            :this(false, outputStream, cipher, secretKey, iv, encAlgorithm, aeadAlgorithm, chunkSize)
+        {
+            
+        }
+
+        /// <summary>
+        /// OutputStream for AEAD encryption.
+        /// </summary>
+        /// <param name="isV5StyleAead">flavour of AEAD (OpenPGP v5 or v6)</param>
+        /// <param name="outputStream">underlying OutputStream</param>
+        /// <param name="cipher">AEAD cipher</param>
+        /// <param name="secretKey">encryption key</param>
+        /// <param name="iv">initialization vector</param>
+        /// <param name="encAlgorithm">encryption algorithm</param>
+        /// <param name="aeadAlgorithm">AEAD algorithm</param>
+        /// <param name="chunkSize">chunk size octet of the AEAD encryption</param>
+        public AeadOutputStream(
+            bool isV5StyleAead,
+            Stream outputStream,
+            BufferedAeadBlockCipher cipher,
+            KeyParameter secretKey,
+            byte[] iv,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            int chunkSize)
+        {
+            this.isV5StyleAead = isV5StyleAead;
+            this.outputStream = outputStream;
+            this.iv = iv;
+            this.chunkLength = GetChunkLength(chunkSize);
+            this.tagLen = AeadUtils.GetAuthTagLength(aeadAlgorithm);
+            this.data = new byte[chunkLength];
+            this.cipher = cipher;
+            this.secretKey = secretKey;
+
+            aaData = CreateAAD(isV5StyleAead, encAlgorithm, aeadAlgorithm, chunkSize);
+        }
+
+        private static byte[] CreateAAD(bool isV5StyleAEAD, SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+        {
+            if (isV5StyleAEAD)
+            {
+                return AeadEncDataPacket.CreateAAData(AeadEncDataPacket.Version1, encAlgorithm, aeadAlgorithm, chunkSize);
+            }
+            else
+            {
+                return SymmetricEncIntegrityPacket.CreateAAData(SymmetricEncIntegrityPacket.Version2, encAlgorithm, aeadAlgorithm, chunkSize);
+            }
+        }
+
+        public override void WriteByte(byte b)
+        {
+            if (dataOff == data.Length)
+            {
+                WriteBlock();
+            }
+            data[dataOff++] = (byte) b;
+        }
+
+        public override void Write(byte[] b, int off, int len)
+        {
+            if (dataOff == data.Length)
+            {
+                WriteBlock();
+            }
+
+            if (len<data.Length - dataOff)
+            {
+                Array.Copy(b, off, data, dataOff, len);
+                dataOff += len;
+            }
+            else
+            {
+                int gap = data.Length - dataOff;
+                Array.Copy(b, off, data, dataOff, gap);
+                dataOff += gap;
+                WriteBlock();
+                
+                len -= gap;
+                off += gap;
+                while (len >= data.Length)
+                {
+                    Array.Copy(b, off, data, 0, data.Length);
+                    dataOff = data.Length;
+                    WriteBlock();
+                    len -= data.Length;
+                    off += data.Length;
+                }
+                if (len > 0)
+                {
+                    Array.Copy(b, off, data, 0, len);
+                    dataOff = len;
+                }
+            }
+        }
+        
+        private void WriteBlock()
+        {
+            //bool v5StyleAEAD = isV5StyleAEAD;
+
+            //byte[] adata = v5StyleAEAD ? new byte[13] : new byte[aaData.Length];
+            byte[] adata = new byte[aaData.Length];
+            Array.Copy(aaData, 0, adata, 0, aaData.Length);
+
+            //if (v5StyleAEAD)
+            //{
+            //    xorChunkId(adata, chunkIndex);
+            //}
+
+            try
+            {
+                AeadParameters aeadParams = new AeadParameters(
+                    secretKey,
+                    8 * tagLen,
+                    AeadUtils.CreateNonce(iv, chunkIndex),
+                    adata);
+
+                cipher.Init(true, aeadParams); 
+
+                int len = cipher.ProcessBytes(data, 0, dataOff, data, 0);
+                outputStream.Write(data, 0, len);
+
+                len = cipher.DoFinal(data, 0);
+                outputStream.Write(data, 0, len);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                throw new IOException("exception processing chunk " + chunkIndex + ": " + e.Message);
+            }
+
+            totalBytes += dataOff;
+            chunkIndex++;
+            dataOff = 0;
+        }
+
+        private bool disposed = false;
+        protected override void Dispose(bool disponing)
+        {
+            if (!disposed)
+            {
+                base.Dispose(disponing);
+                Finish();
+                disposed = true;
+            }
+        }
+
+        private void Finish()
+        {
+            if (dataOff > 0)
+            {
+                WriteBlock();
+            }
+
+            byte[] adata = AeadUtils.CreateLastBlockAAData(isV5StyleAead, aaData, chunkIndex, totalBytes);
+            try
+            {
+                AeadParameters aeadParams = new AeadParameters(
+                    secretKey,
+                    8 * tagLen,
+                    AeadUtils.CreateNonce(iv, chunkIndex),
+                    adata);
+
+                cipher.Init(true, aeadParams);
+                //if (isV5StyleAead)
+                //{
+                //    cipher.processAADBytes(Pack.longToBigEndian(totalBytes), 0, 8);
+                //}
+                cipher.DoFinal(data, 0);
+                outputStream.Write(data, 0, tagLen); // output final tag
+            }
+            catch (InvalidCipherTextException e)
+            {
+                throw new IOException("exception processing final tag: " + e.Message);
+            }
+            outputStream.Close();
+        }
+    }
+}
diff --git a/crypto/src/bcpg/AeadUtils.cs b/crypto/src/bcpg/AeadUtils.cs
index e828f735a9..0f2adce49f 100644
--- a/crypto/src/bcpg/AeadUtils.cs
+++ b/crypto/src/bcpg/AeadUtils.cs
@@ -1,7 +1,11 @@
 using System;
 using Org.BouncyCastle.Bcpg.OpenPgp;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 
 namespace Org.BouncyCastle.Bcpg
@@ -93,6 +97,63 @@ public static void SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgo
             Array.Copy(messageKeyAndIv, messageKey.Length, iv, 0, ivLen-8);
         }
 
+        /// <summary>
+        /// Derive a message key and IV from the given session key.
+        /// </summary>
+        /// <param name="sessionKey">session key</param>
+        /// <param name="encAlgorithm">symmetric cipher algorithm tag</param>
+        /// <param name="aeadAlgorithm">AEAD algorithm tag</param>
+        /// <param name="salt">salt</param>
+        /// <param name="hkdfInfo">HKDF info</param>
+        /// <param name="messageKey"></param>
+        /// <param name="iv"></param>
+        public static void DeriveAeadMessageKeyAndIv(
+            KeyParameter sessionKey,
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            byte[] salt,
+            byte[] hkdfInfo,
+            out KeyParameter messageKey,
+            out byte[] iv)
+        {
+            var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+            var hkdfParams = new HkdfParameters(sessionKey.GetKey(), salt, hkdfInfo);
+            hkdfGen.Init(hkdfParams);
+            var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(encAlgorithm) + AeadUtils.GetIVLength(aeadAlgorithm) - 8];
+            hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+            AeadUtils.SplitMessageKeyAndIv(hkdfOutput, encAlgorithm, aeadAlgorithm, out var messageKeyBytes, out iv);
+
+            messageKey = new KeyParameter(messageKeyBytes);
+        }
+
+        public static byte[] CreateNonce(byte[] iv, long chunkIndex)
+        {
+            byte[] nonce = Arrays.Clone(iv);
+            byte[] chunkid = Pack.UInt64_To_BE((ulong)chunkIndex);
+            Array.Copy(chunkid, 0, nonce, nonce.Length - 8, 8);
+
+            return nonce;
+        }
+
+        public static byte[] CreateLastBlockAAData(bool isV5StyleAead, byte[] aaData, long chunkIndex, long totalBytes)
+        {
+            byte[] adata;
+            if (isV5StyleAead)
+            {
+                adata = new byte[13];
+                Array.Copy(aaData, 0, adata, 0, aaData.Length);
+                Array.Copy(Pack.UInt64_To_BE((ulong)chunkIndex), 0, adata, aaData.Length, 8);
+            }
+            else
+            {
+                adata = new byte[aaData.Length + 8];
+                Array.Copy(aaData, 0, adata, 0, aaData.Length);
+                Array.Copy(Pack.UInt64_To_BE((ulong)totalBytes), 0, adata, aaData.Length, 8);
+            }
+            return adata;
+        }
+
         public static BufferedAeadBlockCipher CreateAeadCipher(
             SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
         {
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index dd7e1d7f05..2a23bc3f38 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -153,6 +153,32 @@ public PublicKeyEncSessionPacket(
             }
 		}
 
+        /// <summary>
+        /// Create a new V6 PKESK packet.
+        /// </summary>
+        /// <param name="keyVersion">version of the key</param>
+        /// <param name="keyFingerprint">fingerprint of the key</param>
+        /// <param name="algorithm">public key algorith</param>
+        /// <param name="data">session data</param>
+        public PublicKeyEncSessionPacket(
+            int keyVersion,
+            byte[] keyFingerprint,
+            PublicKeyAlgorithmTag algorithm,
+            byte[][] data)
+            : base(PacketTag.PublicKeyEncryptedSession)
+        {
+            this.version = Version6;
+            this.keyVersion = keyVersion;
+            this.keyFingerprint = Arrays.Clone(keyFingerprint);
+            this.algorithm = algorithm;
+            this.data = new byte[data.Length][];
+
+            for (int i = 0; i < data.Length; i++)
+            {
+                this.data[i] = Arrays.Clone(data[i]);
+            }
+        }
+
         public int Version
 		{
 			get { return version; }
diff --git a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
index e30da6f1e4..6692851beb 100644
--- a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
@@ -70,13 +70,13 @@ public byte[] GetSalt()
 
         internal byte[] GetAAData()
         {
-            return CreateAAData(Tag, Version, cipherAlgorithm, aeadAlgorithm, chunkSize);
+            return CreateAAData(Version, cipherAlgorithm, aeadAlgorithm, chunkSize);
         }
 
-        internal static byte[] CreateAAData(PacketTag tag, int version, SymmetricKeyAlgorithmTag cipherAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+        internal static byte[] CreateAAData(int version, SymmetricKeyAlgorithmTag cipherAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
         {
             return new byte[]{
-                (byte)(0xC0 | (byte)tag),
+                0xC0 | (byte)PacketTag.SymmetricEncryptedIntegrityProtected,
                 (byte)version,
                 (byte)cipherAlgorithm,
                 (byte)aeadAlgorithm,
diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
index 215ff98d0a..224ca8243b 100644
--- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
@@ -95,6 +95,39 @@ public SymmetricKeyEncSessionPacket(
             this.secKeyData = secKeyData;
         }
 
+
+        /// <summary>
+        /// Create a v6 SKESK packet.
+        /// </summary>
+        /// <param name="encAlgorithm"></param>
+        /// <param name="aeadAlgorithm"></param>
+        /// <param name="iv"></param>
+        /// <param name="s2k"></param>
+        /// <param name="secKeyData"></param>
+        /// <exception cref="IllegalArgumentException"></exception>
+        public SymmetricKeyEncSessionPacket(
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            byte[] iv,
+            S2k s2k,
+            byte[] secKeyData)
+            : base(PacketTag.SymmetricKeyEncryptedSessionKey)
+        {
+            this.version = Version6;
+            this.encAlgorithm = encAlgorithm;
+            this.aeadAlgorithm = aeadAlgorithm;
+            this.s2k = s2k;
+            this.secKeyData = Arrays.Clone(secKeyData);
+
+            int expectedIVLen = AeadUtils.GetIVLength(aeadAlgorithm);
+            if (expectedIVLen != iv.Length)
+            {
+                throw new ArgumentException($"Mismatched AEAD IV length. Expected {expectedIVLen}, got {iv.Length}");
+            }
+
+            this.iv = Arrays.Clone(iv);
+        }
+
         /**
         * @return int
         */
@@ -134,14 +167,14 @@ public int Version
 
         internal byte[] GetAAData()
         {
-            return CreateAAData(Tag, Version, EncAlgorithm, AeadAlgorithm);
+            return CreateAAData(Version, EncAlgorithm, AeadAlgorithm);
         }
 
-        internal static byte[] CreateAAData(PacketTag tag, int version, SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
+        internal static byte[] CreateAAData(int version, SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
         {
             return new byte[]
             {
-                (byte)(0xC0 | (byte)tag),
+                0xC0 | (byte)PacketTag.SymmetricKeyEncryptedSessionKey,
                 (byte)version,
                 (byte)encAlgorithm,
                 (byte)aeadAlgorithm
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index afc7853007..f32be2fec7 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -22,6 +22,7 @@ public class PgpEncryptedDataGenerator
     {
 		private BcpgOutputStream	pOut;
         private CipherStream		cOut;
+        private AeadOutputStream    aeadOut;
         private IBufferedCipher		c;
         private readonly bool		withIntegrityPacket;
         private readonly bool		oldFormat;
@@ -32,7 +33,9 @@ private abstract class EncMethod
         {
             protected byte[]                    sessionInfo;
             protected SymmetricKeyAlgorithmTag  encAlgorithm;
+            protected AeadAlgorithmTag          aeadAlgorithm;
             protected KeyParameter              key;
+            protected byte[]                    aeadIv;
 
             protected EncMethod(PacketTag packetTag)
                 : base(packetTag)
@@ -50,12 +53,14 @@ private class PbeMethod
 
             internal PbeMethod(
                 SymmetricKeyAlgorithmTag  encAlgorithm,
+                AeadAlgorithmTag          aeadAlgorithm,
                 S2k                       s2k,
                 KeyParameter              key,
                 int                       skeskVersion)
                 : base(PacketTag.SymmetricKeyEncryptedSessionKey)
             {
                 this.encAlgorithm = encAlgorithm;
+                this.aeadAlgorithm = aeadAlgorithm;
                 this.s2k = s2k;
                 this.key = key;
                 this.skeskVersion = skeskVersion;
@@ -66,23 +71,71 @@ public KeyParameter GetKey()
                 return key;
             }
 
-			public override void AddSessionInfo(
-                byte[]			si,
-				SecureRandom	random)
+            private byte[] EncryptSessionInfoForVersion4(byte[] si, SecureRandom random)
             {
                 string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
-                IBufferedCipher c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+                IBufferedCipher cipher = CipherUtilities.GetCipher($"{cName}/CFB/NoPadding");
 
-				byte[] iv = new byte[c.GetBlockSize()];
-                c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random));
+                byte[] iv = new byte[cipher.GetBlockSize()];
+                cipher.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random));
+
+                return cipher.DoFinal(si, 0, si.Length - 2);
+            }
+
+            private byte[] EncryptSessionInfoForVersion6(byte[] si, SecureRandom random)
+            {
+                byte[] aadata = SymmetricKeyEncSessionPacket.CreateAAData(skeskVersion, encAlgorithm, aeadAlgorithm);
+
+                // key-encryption key derivation
+                var hkdfParams = new HkdfParameters(key.GetKey(), Array.Empty<byte>(), aadata);
+                var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+                hkdfGen.Init(hkdfParams);
+                var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(encAlgorithm)];
+                hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+                BufferedAeadBlockCipher cipher = AeadUtils.CreateAeadCipher(encAlgorithm, aeadAlgorithm);
+
+                aeadIv = new byte[AeadUtils.GetIVLength(aeadAlgorithm)];
+                random.NextBytes(aeadIv);
+
+                var aeadParams = new AeadParameters(
+                    new KeyParameter(hkdfOutput),
+                    8 * AeadUtils.GetAuthTagLength(aeadAlgorithm),
+                    aeadIv,
+                    aadata);
 
-				this.sessionInfo = c.DoFinal(si, 0, si.Length - 2);
-			}
+                cipher.Init(true, aeadParams);
+                byte[] keyBytes = cipher.DoFinal(si, 0, si.Length-2);
+
+                return keyBytes;
+            }
+
+            public override void AddSessionInfo(
+                byte[]			si,
+				SecureRandom	random)
+            {
+                if (skeskVersion == SymmetricKeyEncSessionPacket.Version4)
+                {
+                    this.sessionInfo = EncryptSessionInfoForVersion4(si, random);
+                }
+                else if (skeskVersion == SymmetricKeyEncSessionPacket.Version6)
+                {
+                    this.sessionInfo = EncryptSessionInfoForVersion6(si, random);
+                }
+            }
 
 			public override void Encode(BcpgOutputStream pOut)
             {
-                SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(
-                    encAlgorithm, s2k, sessionInfo);
+                SymmetricKeyEncSessionPacket pk;
+                if (skeskVersion == SymmetricKeyEncSessionPacket.Version6)
+                {
+                    pk = new SymmetricKeyEncSessionPacket(
+                        encAlgorithm, aeadAlgorithm, aeadIv, s2k, sessionInfo);
+                }
+                else
+                {
+                    pk = new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, sessionInfo);
+                }
 
 				pOut.WritePacket(pk);
             }
@@ -391,7 +444,16 @@ private byte[] ConvertToEncodedMpi(byte[] encryptedSessionInfo)
 
             public override void Encode(BcpgOutputStream pOut)
             {
-                PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data);
+                PublicKeyEncSessionPacket pk;
+
+                if (pkeskVersion == PublicKeyEncSessionPacket.Version6)
+                {
+                    pk = new PublicKeyEncSessionPacket(pubKey.Version, pubKey.GetFingerprint(), pubKey.Algorithm, data);
+                }
+                else
+                {
+                    pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data);
+                }
 
                 pOut.WritePacket(pk);
             }
@@ -399,11 +461,13 @@ public override void Encode(BcpgOutputStream pOut)
 
         private readonly List<EncMethod> methods = new List<EncMethod>();
         private readonly SymmetricKeyAlgorithmTag defAlgorithm;
+        private readonly AeadAlgorithmTag  defAeadAlgorithm;
         private readonly SecureRandom rand;
 
         private readonly int skeskVersion;
         private readonly int pkeskVersion;
         private readonly int seipdVersion;
+        private readonly byte chunkSizeOctet = 6; // 1 << (chunkSize + 6) = 4096
 
         public PgpEncryptedDataGenerator(
 			SymmetricKeyAlgorithmTag encAlgorithm)
@@ -465,6 +529,39 @@ private PgpEncryptedDataGenerator(
             seipdVersion = SymmetricEncIntegrityPacket.Version1;
         }
 
+
+        public PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm)
+            : this(encAlgorithm, aeadAlgorithm, CryptoServicesRegistrar.GetSecureRandom(), false)
+        {
+        }
+
+        public PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            SecureRandom random)
+            : this(encAlgorithm, aeadAlgorithm, random, false)
+        {
+        }
+
+        public PgpEncryptedDataGenerator(
+            SymmetricKeyAlgorithmTag encAlgorithm,
+            AeadAlgorithmTag aeadAlgorithm,
+            SecureRandom random,
+            bool oldFormat)
+        {
+            this.rand = random ?? throw new ArgumentNullException(nameof(random));
+            this.defAlgorithm = encAlgorithm;
+            this.defAeadAlgorithm = aeadAlgorithm;
+            this.oldFormat = oldFormat;
+            this.withIntegrityPacket = true;
+
+            skeskVersion = SymmetricKeyEncSessionPacket.Version6;
+            pkeskVersion = PublicKeyEncSessionPacket.Version6;
+            seipdVersion = SymmetricEncIntegrityPacket.Version2;
+        }
+
         /// <summary>Add a PBE encryption method to the encrypted object.</summary>
         /// <remarks>
         /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
@@ -497,7 +594,7 @@ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgori
         {
             S2k s2k = PgpUtilities.GenerateS2k(s2kDigest, 0x60, rand);
 
-            methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase), skeskVersion));
+            methods.Add(new PbeMethod(defAlgorithm, defAeadAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase), skeskVersion));
         }
 
         /// <summary>Add a PBE encryption method to the encrypted object.</summary>
@@ -535,6 +632,7 @@ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, S2k.Argon2
             methods.Add(
                 new PbeMethod(
                     defAlgorithm,
+                    defAeadAlgorithm,
                     s2k,
                     PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase),
                     skeskVersion
@@ -577,9 +675,22 @@ private void AddCheckSum(
 		private byte[] CreateSessionInfo(SymmetricKeyAlgorithmTag algorithm, KeyParameter key)
 		{
             int keyLength = key.KeyLength;
-            byte[] sessionInfo = new byte[keyLength + 3];
-			sessionInfo[0] = (byte)algorithm;
-            key.CopyTo(sessionInfo, 1, keyLength);
+            int infoLen = keyLength + 2;
+            int offset = 0;
+
+            if (seipdVersion == SymmetricEncIntegrityPacket.Version1)
+            {
+                infoLen++;
+                offset = 1;
+            }
+
+            byte[] sessionInfo = new byte[infoLen];
+
+            if (seipdVersion == SymmetricEncIntegrityPacket.Version1)
+            {
+                sessionInfo[0] = (byte)algorithm;
+            }
+            key.CopyTo(sessionInfo, offset, keyLength);
 			AddCheckSum(sessionInfo);
 			return sessionInfo;
 		}
@@ -600,7 +711,7 @@ private Stream Open(
             long	length,
             byte[]	buffer)
         {
-			if (cOut != null)
+			if (cOut != null || aeadOut != null)
 				throw new InvalidOperationException("generator already in open state");
 			if (methods.Count == 0)
 				throw new InvalidOperationException("No encryption methods specified");
@@ -613,29 +724,31 @@ private Stream Open(
 
 			if (methods.Count == 1)
             {
-                if (methods[0] is PbeMethod pbeMethod)
+                if (methods[0] is PbeMethod pbeMethod && skeskVersion == SymmetricKeyEncSessionPacket.Version4)
                 {
+                    // For V4 SKESK, the encrypted session key is optional. If not present, the session key
+                    // is derived directly with the S2K algorithm applied to the passphrase
                     key = pbeMethod.GetKey();
                 }
-                else if (methods[0] is PubMethod pubMethod)
+                //else if (methods[0] is PubMethod pubMethod)
+                else
                 {
                     key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
-
 					byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
 
                     try
                     {
-                        pubMethod.AddSessionInfo(sessionInfo, rand);
+                        methods[0].AddSessionInfo(sessionInfo, rand);
                     }
                     catch (Exception e)
                     {
                         throw new PgpException("exception encrypting session key", e);
                     }
                 }
-                else
-                {
-                    throw new InvalidOperationException();
-                }
+                //else
+                //{
+                //    throw new InvalidOperationException();
+                //}
 
 				pOut.WritePacket(methods[0]);
             }
@@ -659,6 +772,63 @@ private Stream Open(
                 }
             }
 
+
+            if (seipdVersion == SymmetricEncIntegrityPacket.Version2)
+            {
+                if (buffer == null)
+                {
+                    int chunkSize = 1 << (chunkSizeOctet + 6);
+                    long chunks = ((length + chunkSize - 1) / chunkSize);
+
+                    long outputLength = length
+                        + 1     // version
+                        + 1     // algo ID
+                        + 1     // AEAD algo ID
+                        + 1     // chunk size octet
+                        + 32    // salt
+                        + AeadUtils.GetAuthTagLength(defAeadAlgorithm) * (chunks + 1); // one auth tag for each chunk plus final tag
+
+                    pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, outputLength);
+                }
+                else
+                {
+                    pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, buffer);
+                }
+
+                pOut.WriteByte(SymmetricEncIntegrityPacket.Version2);
+                pOut.WriteByte((byte)defAlgorithm);
+                pOut.WriteByte((byte)defAeadAlgorithm);
+                pOut.WriteByte((byte)chunkSizeOctet);
+
+                byte[] salt = new byte[32];
+                rand.NextBytes(salt);
+                pOut.Write(salt);
+
+                var cipher = AeadUtils.CreateAeadCipher(defAlgorithm, defAeadAlgorithm);
+                byte[] aadata = SymmetricEncIntegrityPacket.CreateAAData(SymmetricEncIntegrityPacket.Version2, defAlgorithm, defAeadAlgorithm, chunkSizeOctet);
+
+                AeadUtils.DeriveAeadMessageKeyAndIv(
+                    key,
+                    defAlgorithm,
+                    defAeadAlgorithm,
+                    salt,
+                    aadata,
+                    out var messageKey,
+                    out var iv);
+
+                aeadOut = new AeadOutputStream(
+                    pOut,
+                    cipher,
+                    messageKey,
+                    iv,
+                    defAlgorithm,
+                    defAeadAlgorithm,
+                    chunkSizeOctet);
+
+                return new WrappedGeneratorStream(this, aeadOut);
+
+            }
+
             string cName = PgpUtilities.GetSymmetricCipherName(defAlgorithm);
 			if (cName == null)
                 throw new PgpException("null cipher specified");
@@ -773,6 +943,14 @@ public Stream Open(
         [Obsolete("Dispose any opened Stream directly")]
         public void Close()
         {
+            if(aeadOut != null)
+            {
+                aeadOut.Close();
+                pOut.Finish();
+
+                aeadOut = null;
+                pOut = null;
+            }
             if (cOut != null)
             {
 				// TODO Should this all be under the try/catch block?
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index e007b6a6d1..8ba9b4bdf0 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -237,7 +237,7 @@ private Stream DoGetDataStreamVersion2(SymmetricEncIntegrityPacket seipd, byte[]
 
                 var aadata = seipd.GetAAData();
                 var salt = seipd.GetSalt();
-                PgpUtilities.DeriveAeadMessageKeyAndIv(sessionKey, seipd.CipherAlgorithm, seipd.AeadAlgorithm, salt, aadata, out var messageKey, out var iv);
+                AeadUtils.DeriveAeadMessageKeyAndIv(sessionKey, seipd.CipherAlgorithm, seipd.AeadAlgorithm, salt, aadata, out var messageKey, out var iv);
                 var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
 
                 var aeadStream = new AeadInputStream(
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 36d23a7644..859d48420f 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -183,7 +183,7 @@ private Stream GetDataStreamSeipdVersion2(byte[] sessionData, SymmetricEncIntegr
                 PgpUtilities.GetSymmetricCipherName(encAlgo),
                 sessionData, 0, sessionData.Length);
 
-            PgpUtilities.DeriveAeadMessageKeyAndIv(sessionKey, encAlgo, aeadAlgo, salt, aadata, out var messageKey, out var iv);
+            AeadUtils.DeriveAeadMessageKeyAndIv(sessionKey, encAlgo, aeadAlgo, salt, aadata, out var messageKey, out var iv);
             var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
 
             var aeadStream = new AeadInputStream(
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 5210f5d1e3..620724008d 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -278,36 +278,6 @@ public static int GetKeySizeInOctets(SymmetricKeyAlgorithmTag algorithm)
             return (GetKeySize(algorithm) + 7) / 8;
         }
 
-        /// <summary>
-        /// Derive a message key and IV from the given session key.
-        /// </summary>
-        /// <param name="sessionKey">session key</param>
-        /// <param name="encAlgorithm">symmetric cipher algorithm tag</param>
-        /// <param name="aeadAlgorithm">AEAD algorithm tag</param>
-        /// <param name="salt">salt</param>
-        /// <param name="hkdfInfo">HKDF info</param>
-        /// <param name="messageKey"></param>
-        /// <param name="iv"></param>
-        public static void DeriveAeadMessageKeyAndIv(
-            KeyParameter sessionKey,
-            SymmetricKeyAlgorithmTag encAlgorithm,
-            AeadAlgorithmTag aeadAlgorithm,
-            byte[] salt,
-            byte[] hkdfInfo,
-            out KeyParameter messageKey,
-            out byte[] iv)
-        {
-            var hkdfGen = new HkdfBytesGenerator(CreateDigest(HashAlgorithmTag.Sha256));
-            var hkdfParams = new HkdfParameters(sessionKey.GetKey(), salt, hkdfInfo);
-            hkdfGen.Init(hkdfParams);
-            var hkdfOutput = new byte[GetKeySizeInOctets(encAlgorithm) + AeadUtils.GetIVLength(aeadAlgorithm) - 8];
-            hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
-
-            AeadUtils.SplitMessageKeyAndIv(hkdfOutput, encAlgorithm, aeadAlgorithm, out var messageKeyBytes, out iv);
-
-            messageKey = new KeyParameter(messageKeyBytes);
-        }
-
         public static KeyParameter MakeKey(
 			SymmetricKeyAlgorithmTag	algorithm,
 			byte[]						keyBytes)
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 74ad744f14..87bf55ab13 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -1,6 +1,5 @@
 using NUnit.Framework;
 using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Agreement;
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
@@ -166,6 +165,228 @@ public class PgpCryptoRefreshTest
 
         private readonly char[] emptyPassphrase = Array.Empty<char>();
 
+        #region "Helpers"
+        private void SignVerifyRoundtrip(PgpSecretKey signingKey, char[] passphrase)
+        {
+            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
+
+            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
+            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
+            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(passphrase);
+            spkGen.SetIssuerFingerprint(false, signingKey);
+            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+            sigGen.Update(data);
+            sigGen.SetHashedSubpackets(spkGen.Generate());
+            PgpSignature signature = sigGen.Generate();
+
+            AreEqual(signature.GetIssuerFingerprint(), signingKey.GetFingerprint());
+
+            VerifySignature(signature, data, signingKey.PublicKey);
+            VerifySignature(signature, wrongData, signingKey.PublicKey, shouldFail: true);
+
+            byte[] encodedSignature = signature.GetEncoded();
+            VerifyEncodedSignature(encodedSignature, data, signingKey.PublicKey);
+            VerifyEncodedSignature(encodedSignature, wrongData, signingKey.PublicKey, shouldFail: true);
+        }
+
+        private void VerifyInlineSignature(byte[] message, PgpPublicKey signer, bool shouldFail = false)
+        {
+            byte[] data;
+            PgpObjectFactory factory = new PgpObjectFactory(message);
+
+            PgpOnePassSignatureList p1 = factory.NextPgpObject() as PgpOnePassSignatureList;
+            PgpOnePassSignature ops = p1[0];
+
+            PgpLiteralData p2 = factory.NextPgpObject() as PgpLiteralData;
+            Stream dIn = p2.GetInputStream();
+
+            ops.InitVerify(signer);
+
+            using (MemoryStream ms = new MemoryStream())
+            {
+                byte[] buffer = new byte[30];
+                int bytesRead;
+                while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+                {
+                    ops.Update(buffer, 0, bytesRead);
+                    ms.Write(buffer, 0, bytesRead);
+                }
+
+                data = ms.ToArray();
+            }
+            PgpSignatureList p3 = factory.NextPgpObject() as PgpSignatureList;
+            PgpSignature sig = p3[0];
+
+            bool result = ops.Verify(sig) != shouldFail;
+            IsTrue("signature test failed", result);
+
+            VerifySignature(sig, data, signer, shouldFail);
+        }
+
+        private void VerifySignature(PgpSignature signature, byte[] data, PgpPublicKey signer, bool shouldFail = false)
+        {
+            IsEquals(signature.KeyAlgorithm, signer.Algorithm);
+            // the version of the signature is bound to the version of the signing key
+            IsEquals(signature.Version, signer.Version);
+
+            if (signature.KeyId != 0)
+            {
+                IsEquals(signature.KeyId, signer.KeyId);
+            }
+            byte[] issuerFpt = signature.GetIssuerFingerprint();
+            if (issuerFpt != null)
+            {
+                IsTrue(AreEqual(issuerFpt, signer.GetFingerprint()));
+            }
+
+            signature.InitVerify(signer);
+            signature.Update(data);
+
+            bool result = signature.Verify() != shouldFail;
+            IsTrue("signature test failed", result);
+        }
+
+        private void VerifyEncodedSignature(byte[] sigPacket, byte[] data, PgpPublicKey signer, bool shouldFail = false)
+        {
+            PgpObjectFactory factory = new PgpObjectFactory(sigPacket);
+            PgpSignatureList sigList = factory.NextPgpObject() as PgpSignatureList;
+            PgpSignature signature = sigList[0];
+
+            VerifySignature(signature, data, signer, shouldFail);
+        }
+
+        private static PgpEncryptedDataGenerator CreatePgpEncryptedDataGenerator(bool useAead)
+        {
+            if (useAead)
+            {
+                return new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, AeadAlgorithmTag.Ocb, new SecureRandom());
+            }
+            else
+            {
+                return new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom());
+            }
+        }
+
+        private static byte[] EncryptPlaintext(PgpEncryptedDataGenerator encDataGen, byte[] plaintext, bool useBuffer)
+        {
+            byte[] enc;
+
+            if (useBuffer)
+            {
+                byte[] buffer = new byte[1024];
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    using (Stream cOut = encDataGen.Open(ms, buffer))
+                    {
+                        using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
+                        {
+                            PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+                            DateTime modificationTime = DateTime.UtcNow;
+
+                            using (Stream lOut = literalDataGen.Open(
+                                new UncloseableStream(bcOut),
+                                PgpLiteralData.Utf8,
+                                PgpLiteralData.Console,
+                                plaintext.Length,
+                                modificationTime))
+                            {
+                                lOut.Write(plaintext, 0, plaintext.Length);
+                            }
+                        }
+                    }
+                    enc = ms.ToArray();
+                }
+            }
+            else
+            {
+                byte[] literal;
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+                    DateTime modificationTime = DateTime.UtcNow;
+
+                    using (Stream lOut = literalDataGen.Open(
+                        new UncloseableStream(ms),
+                        PgpLiteralData.Utf8,
+                        PgpLiteralData.Console,
+                        plaintext.Length,
+                        modificationTime))
+                    {
+                        lOut.Write(plaintext, 0, plaintext.Length);
+                    }
+                    literal = ms.ToArray();
+                }
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    using (Stream cOut = encDataGen.Open(ms, literal.Length))
+                    {
+                        cOut.Write(literal, 0, literal.Length);
+                    }
+                    enc = ms.ToArray();
+                }
+            }
+
+            return enc;
+        }
+
+        private void SymmetricEncryptDecryptRoundtrip(byte[] plaintext, bool useAead, bool useBuffer, byte[] rawPassword)
+        {
+            // encrypt
+            PgpEncryptedDataGenerator encDataGen = CreatePgpEncryptedDataGenerator(useAead);
+            encDataGen.AddMethodRaw(rawPassword, S2k.Argon2Parameters.MemoryConstrainedParameters());
+            byte[] enc = EncryptPlaintext(encDataGen, plaintext, useBuffer);
+
+            // decrypt
+            PgpObjectFactory factory = new PgpObjectFactory(enc);
+            PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+            FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+            PgpPbeEncryptedData encData = encDataList[0] as PgpPbeEncryptedData;
+            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+
+            using (Stream stream = encData.GetDataStreamRaw(rawPassword))
+            {
+                factory = new PgpObjectFactory(stream);
+                PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    lit.GetDataStream().CopyTo(ms);
+                    byte[] decrypted = ms.ToArray();
+                    IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                }
+            }
+        }
+
+        private void PubkeyEncryptDecryptRoundtrip(byte[] plaintext, bool useAead,bool useBuffer,  PgpPublicKey pubKey, PgpPrivateKey privKey)
+        {
+            // encrypt
+            PgpEncryptedDataGenerator encDataGen = CreatePgpEncryptedDataGenerator(useAead);
+            encDataGen.AddMethod(pubKey);
+            byte[] enc = EncryptPlaintext(encDataGen, plaintext, useBuffer);
+
+            // decrypt
+            PgpObjectFactory factory = new PgpObjectFactory(enc);
+            PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+            FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+            PgpPublicKeyEncryptedData encData = encDataList[0] as PgpPublicKeyEncryptedData;
+            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+
+            using (Stream stream = encData.GetDataStream(privKey))
+            {
+                factory = new PgpObjectFactory(stream);
+                PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    lit.GetDataStream().CopyTo(ms);
+                    byte[] decrypted = ms.ToArray();
+                    IsTrue(Arrays.AreEqual(plaintext, decrypted));
+                }
+            }
+        }
+        #endregion
+
         [Test]
         public void Version4Ed25519LegacyPubkeySampleTest()
         {
@@ -308,31 +529,6 @@ public void Version6PublicKeyCreationTest()
                 shouldFail: true);
         }
 
-
-        private void SignVerifyRoundtrip(PgpSecretKey signingKey, char[] passphrase)
-        {
-            byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
-            byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
-
-            PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
-            PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
-            PgpPrivateKey privKey = signingKey.ExtractPrivateKey(passphrase);
-            spkGen.SetIssuerFingerprint(false, signingKey);
-            sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
-            sigGen.Update(data);
-            sigGen.SetHashedSubpackets(spkGen.Generate());
-            PgpSignature signature = sigGen.Generate();
-
-            AreEqual(signature.GetIssuerFingerprint(), signingKey.GetFingerprint());
-
-            VerifySignature(signature, data, signingKey.PublicKey);
-            VerifySignature(signature, wrongData, signingKey.PublicKey, shouldFail: true);
-
-            byte[] encodedSignature = signature.GetEncoded();
-            VerifyEncodedSignature(encodedSignature, data, signingKey.PublicKey);
-            VerifyEncodedSignature(encodedSignature, wrongData, signingKey.PublicKey, shouldFail: true);
-        }
-
         [Test]
         public void Version6UnlockedSecretKeyParsingTest()
         {
@@ -506,9 +702,20 @@ public void Version6Ed25519KeyPairCreationTest()
             // Sign-Verify roundtrip
             SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
 
-            // encrypt-decrypt test
-            EncryptDecryptRoundtrip(
+            // encrypt-decrypt roundtrip
+            // V3 PKESK + V1 SEIPD
+            PubkeyEncryptDecryptRoundtrip(
                 Encoding.UTF8.GetBytes("Hello, World!"),
+                false,
+                false,
+                subKey.PublicKey,
+                subKey.ExtractPrivateKey(emptyPassphrase));
+
+            // V6 PKESK + V2 SEIPD
+            PubkeyEncryptDecryptRoundtrip(
+                Encoding.UTF8.GetBytes("Hello, World!"),
+                true,
+                false,
                 subKey.PublicKey,
                 subKey.ExtractPrivateKey(emptyPassphrase));
         }
@@ -591,8 +798,10 @@ public void Version6Ed448KeyPairCreationTest()
             SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
 
             // Encrypt-Decrypt roundtrip
-            EncryptDecryptRoundtrip(
+            PubkeyEncryptDecryptRoundtrip(
                 Encoding.UTF8.GetBytes("Hello, World!"),
+                false,
+                false,
                 subKey.PublicKey,
                 subKey.ExtractPrivateKey(emptyPassphrase));
         }
@@ -638,8 +847,10 @@ public void Version6LockedSecretKeyParsingTest()
             IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
 
             // encrypt-decrypt
-            EncryptDecryptRoundtrip(
+            PubkeyEncryptDecryptRoundtrip(
                 Encoding.UTF8.GetBytes("Hello, World!"),
+                false,
+                false,
                 encryptionKey.PublicKey,
                 encryptionKey.ExtractPrivateKey(passphrase.ToCharArray()));
 
@@ -727,72 +938,6 @@ public void Version6GenerateAndVerifyInlineSignatureTest()
             VerifyInlineSignature(inlineSignatureMessage, signingKey.PublicKey, shouldFail: true);
         }
 
-        private void VerifyInlineSignature(byte[] message, PgpPublicKey signer, bool shouldFail = false)
-        {
-            byte[] data;
-            PgpObjectFactory factory = new PgpObjectFactory(message);
-
-            PgpOnePassSignatureList p1 = factory.NextPgpObject() as PgpOnePassSignatureList;
-            PgpOnePassSignature ops = p1[0];
-
-            PgpLiteralData p2 = factory.NextPgpObject() as PgpLiteralData;
-            Stream dIn = p2.GetInputStream();
-
-            ops.InitVerify(signer);
-
-            using (MemoryStream ms = new MemoryStream())
-            {
-                byte[] buffer = new byte[30];
-                int bytesRead;
-                while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
-                {
-                    ops.Update(buffer, 0, bytesRead);
-                    ms.Write(buffer, 0, bytesRead);
-                }
-
-                data = ms.ToArray();
-            }
-            PgpSignatureList p3 = factory.NextPgpObject() as PgpSignatureList;
-            PgpSignature sig = p3[0];
-
-            bool result = ops.Verify(sig) != shouldFail;
-            IsTrue("signature test failed", result);
-
-            VerifySignature(sig, data, signer, shouldFail);
-        }
-
-        private void VerifySignature(PgpSignature signature, byte[] data, PgpPublicKey signer, bool shouldFail = false)
-        {
-            IsEquals(signature.KeyAlgorithm, signer.Algorithm);
-            // the version of the signature is bound to the version of the signing key
-            IsEquals(signature.Version, signer.Version);
-
-            if (signature.KeyId != 0)
-            {
-                IsEquals(signature.KeyId, signer.KeyId);
-            }
-            byte[] issuerFpt = signature.GetIssuerFingerprint();
-            if (issuerFpt != null)
-            {
-                IsTrue(AreEqual(issuerFpt, signer.GetFingerprint()));
-            }
-
-            signature.InitVerify(signer);
-            signature.Update(data);
-
-            bool result = signature.Verify() != shouldFail;
-            IsTrue("signature test failed", result);
-        }
-
-        private void VerifyEncodedSignature(byte[] sigPacket, byte[] data, PgpPublicKey signer, bool shouldFail = false)
-        {
-            PgpObjectFactory factory = new PgpObjectFactory(sigPacket);
-            PgpSignatureList sigList = factory.NextPgpObject() as PgpSignatureList;
-            PgpSignature signature = sigList[0];
-
-            VerifySignature(signature, data, signer, shouldFail);
-        }
-
         [Test]
         public void Version6SkeskVersion2SeipdTest()
         {
@@ -997,112 +1142,26 @@ public void SkeskWithArgon2Test()
                 });
             }
 
-            // encrypt-decrypt roundtrip - V4 SKESK + V1 SEIPD
-            {
-                // encrypt
-                byte[] enc;
-                using (MemoryStream ms = new MemoryStream())
-                {
-                    byte[] buffer = new byte[1024];
-                    PgpEncryptedDataGenerator endDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom());
-                    endDataGen.AddMethodRaw(password, S2k.Argon2Parameters.MemoryConstrainedParameters());
-
-                    using (Stream cOut = endDataGen.Open(ms, buffer))
-                    {
-                        using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
-                        {
-                            PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
-                            DateTime modificationTime = DateTime.UtcNow;
-
-                            using (Stream lOut = literalDataGen.Open(
-                                new UncloseableStream(bcOut),
-                                PgpLiteralData.Utf8,
-                                PgpLiteralData.Console,
-                                plaintext.Length,
-                                modificationTime))
-                            {
-                                lOut.Write(plaintext, 0, plaintext.Length);
-                            }
-                        }
-                    }
-
-                    enc = ms.ToArray();
-                }
-
-                // decrypt
-                PgpObjectFactory factory = new PgpObjectFactory(enc);
-                PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
-                FailIf("invalid PgpEncryptedDataList", encDataList is null);
-
-                PgpPbeEncryptedData encData = encDataList[0] as PgpPbeEncryptedData;
-                FailIf("invalid PgpPbeEncryptedData", encData is null);
-
-                using (Stream stream = encData.GetDataStreamRaw(password))
-                {
-                    factory = new PgpObjectFactory(stream);
-                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
-                    using (MemoryStream ms = new MemoryStream())
-                    {
-                        lit.GetDataStream().CopyTo(ms);
-                        byte[] decrypted = ms.ToArray();
-                        IsTrue(Arrays.AreEqual(plaintext, decrypted));
-                    }
-                }
-            }
+            // encrypt-decrypt roundtrip
+            byte[] largePlaintext = new byte[50000];
+            Arrays.Fill(largePlaintext, 0);
+            // V4 SKESK + V1 SEIPD
+            //    Using length in PgpEncryptedDataGenerator.Open
+            SymmetricEncryptDecryptRoundtrip(plaintext, false, false, password);
+            SymmetricEncryptDecryptRoundtrip(largePlaintext, false, false, password);
+            //    Using buffer in PgpEncryptedDataGenerator.Open
+            SymmetricEncryptDecryptRoundtrip(plaintext, false, true, password);
+            SymmetricEncryptDecryptRoundtrip(largePlaintext, false, true, password);
+            // AEAD V6 SKESK + V2 SEIPD
+            //    Using length
+            SymmetricEncryptDecryptRoundtrip(plaintext, true, false, password);
+            SymmetricEncryptDecryptRoundtrip(largePlaintext, true, false, password);
+            //    Using buffer
+            SymmetricEncryptDecryptRoundtrip(plaintext, true, true, password);
+            SymmetricEncryptDecryptRoundtrip(largePlaintext, true, true, password);
         }
 
 
-        private void EncryptDecryptRoundtrip(byte[] plaintext, PgpPublicKey pubKey, PgpPrivateKey privKey)
-        {
-            byte[] enc;
-            using (MemoryStream ms = new MemoryStream())
-            {
-                byte[] buffer = new byte[1024];
-                PgpEncryptedDataGenerator endDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom());
-                endDataGen.AddMethod(pubKey);
-
-                using (Stream cOut = endDataGen.Open(ms, buffer))
-                {
-                    using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
-                    {
-                        PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
-                        DateTime modificationTime = DateTime.UtcNow;
-
-                        using (Stream lOut = literalDataGen.Open(
-                            new UncloseableStream(bcOut),
-                            PgpLiteralData.Utf8,
-                            PgpLiteralData.Console,
-                            plaintext.Length,
-                            modificationTime))
-                        {
-                            lOut.Write(plaintext, 0, plaintext.Length);
-                        }
-                    }
-                }
-                enc = ms.ToArray();
-            }
-
-            // decrypt
-            PgpObjectFactory factory = new PgpObjectFactory(enc);
-            PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
-            FailIf("invalid PgpEncryptedDataList", encDataList is null);
-
-            PgpPublicKeyEncryptedData encData = encDataList[0] as PgpPublicKeyEncryptedData;
-            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
-
-            using (Stream stream = encData.GetDataStream(privKey))
-            {
-                factory = new PgpObjectFactory(stream);
-                PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
-                using (MemoryStream ms = new MemoryStream())
-                {
-                    lit.GetDataStream().CopyTo(ms);
-                    byte[] decrypted = ms.ToArray();
-                    IsTrue(Arrays.AreEqual(plaintext, decrypted));
-                }
-            }
-        }
-
         [Test]
         public void PkeskTest()
         {
@@ -1202,13 +1261,30 @@ public void PkeskTest()
                 }
             }
 
-            // encrypt-decrypt roundtrip - V3 PKESK + V1 SEIPD X25519
+            // encrypt-decrypt roundtrip
             {
-                byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+                byte[] shortPlaintext = Encoding.UTF8.GetBytes("Hello, world!");
+                byte[] largePlaintext = new byte[50000];
+                Arrays.Fill(largePlaintext, 0);
+
                 PgpPublicKeyRing publicKeyRing = new PgpPublicKeyRing(v6Certificate);
                 PgpPublicKey pubKey = publicKeyRing.GetPublicKeys().First(k => k.IsEncryptionKey);
 
-                EncryptDecryptRoundtrip(plaintext, pubKey, privKey);
+                // V3 PKESK + V1 SEIPD X25519
+                //    Using length in PgpEncryptedDataGenerator.Open
+                PubkeyEncryptDecryptRoundtrip(shortPlaintext, false, false, pubKey, privKey);
+                PubkeyEncryptDecryptRoundtrip(largePlaintext, false, false, pubKey, privKey);
+                //    Using buffer in PgpEncryptedDataGenerator.Open
+                PubkeyEncryptDecryptRoundtrip(shortPlaintext, false, true, pubKey, privKey);
+                PubkeyEncryptDecryptRoundtrip(largePlaintext, false, true, pubKey, privKey);
+
+                // V6 PKESK + V2 SEIPD X25519
+                //    Using length
+                PubkeyEncryptDecryptRoundtrip(shortPlaintext, true, false, pubKey, privKey);
+                PubkeyEncryptDecryptRoundtrip(largePlaintext, true, false, pubKey, privKey);
+                //    Using buffer
+                PubkeyEncryptDecryptRoundtrip(shortPlaintext, true, true, pubKey, privKey);
+                PubkeyEncryptDecryptRoundtrip(largePlaintext, true, true, pubKey, privKey);
             }
         }
 

From b0848c052253a50a79de942e01c9d098fa463ab4 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 25 Apr 2024 12:09:33 +0200
Subject: [PATCH 27/37] Correct usage of RSA and ECDH in V6 PKESK

---
 .../src/openpgp/PgpEncryptedDataGenerator.cs  |   6 +
 .../src/openpgp/PgpPublicKeyEncryptedData.cs  |  28 +-
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  |   2 +-
 .../test/PgpInteroperabilityTestSuite.cs      | 298 +++++++++++++++++-
 4 files changed, 325 insertions(+), 9 deletions(-)

diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index f32be2fec7..a80578549d 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -652,6 +652,12 @@ public void AddMethod(PgpPublicKey key, bool sessionKeyObfuscation)
                 throw new ArgumentException("passed in key not an encryption key!");
             }
 
+            if (pkeskVersion == PublicKeyEncSessionPacket.Version6
+                && (key.Algorithm == PublicKeyAlgorithmTag.ElGamalEncrypt || key.Algorithm == PublicKeyAlgorithmTag.ElGamalGeneral))
+            {
+                throw new PgpException("cannot generate ElGamal v6 PKESK (see https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-fo)");
+            }
+
             methods.Add(new PubMethod(key, sessionKeyObfuscation, pkeskVersion));
         }
 
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 859d48420f..64d02ebfe4 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -179,9 +179,16 @@ private Stream GetDataStreamSeipdVersion2(byte[] sessionData, SymmetricEncIntegr
             var aadata = seipd.GetAAData();
             var salt = seipd.GetSalt();
 
+            // no checksum and padding for X25519 and X448
+            int length = sessionData.Length;
+            if (keyData.Algorithm != PublicKeyAlgorithmTag.X25519 && keyData.Algorithm != PublicKeyAlgorithmTag.X448)
+            {
+                length -= 2;
+            }
+
             var sessionKey = ParameterUtilities.CreateKeyParameter(
                 PgpUtilities.GetSymmetricCipherName(encAlgo),
-                sessionData, 0, sessionData.Length);
+                sessionData, 0, length);
 
             AeadUtils.DeriveAeadMessageKeyAndIv(sessionKey, encAlgo, aeadAlgo, salt, aadata, out var messageKey, out var iv);
             var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
@@ -205,15 +212,26 @@ public Stream GetDataStream(
         {
 			byte[] sessionData = RecoverSessionData(privKey);
 
+            if (!ConfirmCheckSum(sessionData))
+                throw new PgpKeyValidationException("key checksum failed");
+
             if (keyData.Version == PublicKeyEncSessionPacket.Version6)
             {
                 // V6 PKESK + V2 SEIPD
-                return GetDataStreamSeipdVersion2(sessionData, (SymmetricEncIntegrityPacket)encData);
+                try
+                { 
+                    return GetDataStreamSeipdVersion2(sessionData, (SymmetricEncIntegrityPacket)encData);
+                }
+                catch (PgpException)
+                {
+                    throw;
+                }
+                catch (Exception e)
+                {
+                    throw new PgpException("Exception starting decryption", e);
+                }
             }
 
-            if (!ConfirmCheckSum(sessionData))
-                throw new PgpKeyValidationException("key checksum failed");
-
             SymmetricKeyAlgorithmTag symmAlg;
             if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
             {
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 87bf55ab13..053d08f34f 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -343,7 +343,7 @@ private void SymmetricEncryptDecryptRoundtrip(byte[] plaintext, bool useAead, bo
             FailIf("invalid PgpEncryptedDataList", encDataList is null);
 
             PgpPbeEncryptedData encData = encDataList[0] as PgpPbeEncryptedData;
-            FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+            FailIf("invalid PgpPbeEncryptedData", encData is null);
 
             using (Stream stream = encData.GetDataStreamRaw(rawPassword))
             {
diff --git a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
index a9122ba29c..d021955669 100644
--- a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
+++ b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
@@ -6,6 +6,7 @@
 using System;
 using System.IO;
 using System.Linq;
+using System.Runtime.Serialization.Formatters;
 using System.Text;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
@@ -14,7 +15,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
     public class PgpInteroperabilityTestSuite
         : SimpleTest
     {
-        // v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+        // v4 EdDSA/ECDH key "Alice" from "OpenPGP Example Keys and Certificates"
         // https://www.ietf.org/archive/id/draft-bre-openpgp-samples-01.html#name-alices-ed25519-samples
         private static readonly byte[] alicePubkey = Base64.Decode(
             "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U" +
@@ -40,7 +41,191 @@ public class PgpInteroperabilityTestSuite
             "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb" +
             "Pnn+We1aTBhaGa86AQ==");
 
-        // v6 keys from crypto-refresh
+        // v4 RSA-3072 key "Bob" from "OpenPGP Example Keys and Certificates"
+        // https://www.ietf.org/archive/id/draft-bre-openpgp-samples-01.html#name-bobs-rsa-3072-samples
+        private static readonly byte[] bobPubkey = Base64.Decode(
+            "mQGNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv" +
+            "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz" +
+            "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/" +
+            "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3" +
+            "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv" +
+            "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0" +
+            "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb" +
+            "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb" +
+            "vLIwa3T4CyshfT0AEQEAAbQhQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w" +
+            "bGU+iQHOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx" +
+            "gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz" +
+            "XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO" +
+            "ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g" +
+            "9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF" +
+            "DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c" +
+            "ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1" +
+            "6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ" +
+            "ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo" +
+            "zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGuQGNBF2lnPIBDADW" +
+            "ML9cbGMrp12CtF9b2P6z9TTT74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvI" +
+            "DEINOQ6A9QxdxoqWdCHrOuW3ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+" +
+            "Uzula/6k1DogDf28qhCxMwG/i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AO" +
+            "baifV7wIhEJnvqgFXDN2RXGjLeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT" +
+            "86Rafp1qKlgPNbiIlC1g9RY/iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh" +
+            "827KVZW4lXvqsge+wtnWlszcselGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6" +
+            "vdRBUnkCaEkOtl1mr2JpQi5nTU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76U" +
+            "qVC7KidNepdHbZjjXCt8/Zo+Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48A" +
+            "EQEAAYkBtgQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJ" +
+            "EPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcS" +
+            "KhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSx" +
+            "cVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14i" +
+            "tcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHV" +
+            "dTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+w" +
+            "qMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6Vy" +
+            "jP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xj" +
+            "zRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PV" +
+            "NEJd3XZRzaXZE2aAMQ==");
+
+        private static readonly byte[] bobSecretkey = Base64.Decode(
+            "lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv" +
+            "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz" +
+            "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/" +
+            "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3" +
+            "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv" +
+            "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0" +
+            "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb" +
+            "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb" +
+            "vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM" +
+            "cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK" +
+            "3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z" +
+            "Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs" +
+            "hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ" +
+            "bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4" +
+            "i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI" +
+            "1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP" +
+            "fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6" +
+            "fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E" +
+            "LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx" +
+            "+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL" +
+            "hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN" +
+            "WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/" +
+            "MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC" +
+            "mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC" +
+            "YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E" +
+            "he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8" +
+            "zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P" +
+            "NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT" +
+            "t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w" +
+            "ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC" +
+            "F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U" +
+            "2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX" +
+            "yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe" +
+            "doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3" +
+            "BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl" +
+            "sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN" +
+            "4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+" +
+            "L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG" +
+            "ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad" +
+            "BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD" +
+            "bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar" +
+            "29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2" +
+            "WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB" +
+            "leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te" +
+            "g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj" +
+            "Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn" +
+            "JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx" +
+            "IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp" +
+            "SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h" +
+            "OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np" +
+            "Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c" +
+            "+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0" +
+            "tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o" +
+            "BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny" +
+            "zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK" +
+            "clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl" +
+            "zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr" +
+            "gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ" +
+            "aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5" +
+            "fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/" +
+            "ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5" +
+            "HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf" +
+            "SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd" +
+            "5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ" +
+            "E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM" +
+            "GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY" +
+            "vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ" +
+            "26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP" +
+            "eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX" +
+            "c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief" +
+            "rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0" +
+            "JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg" +
+            "71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH" +
+            "s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd" +
+            "NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91" +
+            "6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7" +
+            "xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=");
+
+        // v4 DSA/ElGamal key "Carol" from "OpenPGP interoperability test suite"
+        // https://tests.sequoia-pgp.org/#Encrypt-Decrypt_roundtrip_with_key__Carol_
+        private static readonly byte[] carolPubkey = Base64.Decode(
+            "xsPuBF3+CmgRDADZhdKTM3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0" +
+            "OJz2vh59nusbBLzgI//Y1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vh" +
+            "yVeJt0k/NnxvNhMd0587KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0Uj" +
+            "REWs5Jpj/XU9LhEoyXZkeJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcG" +
+            "zYgeMNOvdWJwn43dNhxoeuXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7" +
+            "MNuQx/ejIMZHl+Iaf7hG976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9" +
+            "+4dq6ybUM65tnozRyyN+1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpX" +
+            "duVd32MA33UVNH5/KXMVczVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0" +
+            "SFhlfnBEUj1my1sBAMOSO/I67BvBS3IPHZWXHjgclhs26mPzRlZLryAUWR2DDACH" +
+            "5fx+yUAdZ8Vu/2zWTHxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwS" +
+            "KJUBSA75HExbv0naWg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwp" +
+            "dr1ZwEbb3L6IGQ5i/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdP" +
+            "xGhM8w6a18+fdQr22f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV8" +
+            "2hP4K+rb9FwknYdV9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzo" +
+            "mYmaTO7mp6xFAu43yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4" +
+            "xwfOQ7pf3kC7r9fm8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnU" +
+            "yQs4ksAfIHTzTdLttRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL" +
+            "/0rUAxwegTNIG/5M612s2a45QvYK1turZ7spI1RGitJUIjBXUuR76jIsyqagIhBl" +
+            "5nEsQ4HLv8OQ3EgJ5T9gldLFpHNczLxBQnnNwfPoD2e0kC/iy0rfiNX8HWpTgQpb" +
+            "zAosLj5/E0iNlildynIhuqBosyRWFqGva0O6qioL90srlzlfKCloe9R9w3HizjCb" +
+            "f59yEspuJt9iHVNOPOW2Wj5ub0KTiJPp9vBmrFaB79/IlgojpQoYvQ77Hx5A9CJq" +
+            "paMCHGOW6Uz9euN1ozzETEkIPtL8XAxcogfpe2JKE1uS7ugxsKEGEDfxOQFKAGV0" +
+            "XFtIx50vFCr2vQro0WB858CGN47dCxChhNUxNtGc11JNEkNv/X7hKtRf/5VCmnaz" +
+            "GWwNK47cqZ7GJfEBnElD7s/tQvTC5Qp7lg9gEt47TUX0bjzUTCxNvLosuKL9+J1W" +
+            "ln1myRpff/5ZOAnZTPHR+AbX4bRB4sK5zijQe4139Dn2oRYK+EIYoBAxFxSOzehP" +
+            "IcKKBB8RCAA8BQJd/gppAwsJCgkQm6eJ3HbWhJoEFQoJCAIWAQIXgAIbAwIeARYh" +
+            "BHH/2gBECeXdsMPo8Zunidx21oSaAABihQD/VWnF1HbBhP+kLwWsqxuYjEslEsM2" +
+            "UQPeKGK9an8HZ78BAJPaiL3OpuOmsIoCfOghhMZOKXjIV+Z57LwaMw7FQfPgzSZD" +
+            "YXJvbCBPbGRzdHlsZSA8Y2Fyb2xAb3BlbnBncC5leGFtcGxlPsKKBBMRCAA8BQJd" +
+            "/gppAwsJCgkQm6eJ3HbWhJoEFQoJCAIWAQIXgAIbAwIeARYhBHH/2gBECeXdsMPo" +
+            "8Zunidx21oSaAABQTAD/ZMXAvSbKaMJJpAfwp1C7KAj6K2k2CAz5jwUXyGf1+jUA" +
+            "/2iAMiX1XcLy3n0L8ytzge8/UAFHafBl4rn4DmUugfhjzsPMBF3+CmgQDADZhdKT" +
+            "M3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0OJz2vh59nusbBLzgI//Y" +
+            "1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vhyVeJt0k/NnxvNhMd0587" +
+            "KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0UjREWs5Jpj/XU9LhEoyXZk" +
+            "eJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcGzYgeMNOvdWJwn43dNhxo" +
+            "euXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7MNuQx/ejIMZHl+Iaf7hG" +
+            "976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9+4dq6ybUM65tnozRyyN+" +
+            "1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpXduVd32MA33UVNH5/KXMV" +
+            "czVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0SFhlfnBEUj1my1sMAIfl" +
+            "/H7JQB1nxW7/bNZMfHBYn9fqAZMupr0KZ8OrlQOpgUXO5bA3gcn6vI65qTUIbBIo" +
+            "lQFIDvkcTFu/SdpaD6y7L6kQO8XRUAs9T1VSRJC0fJHXRg7YVY57cAS2ltgNHCl2" +
+            "vVnARtvcvogZDmL/gI0dsna7fJR5ewM0C+ulVIRwiMDTVE8I4qZ/nxINmnjIN0/E" +
+            "aEzzDprXz591CvbZ/ZwnTGB8+VvMVs74VSwSAq+fpBMuFtpjDjOzut1AN6NYdXza" +
+            "E/gr6tv0XCSdh1X26jibvsyAaVT7jK8mcYRhovePCMjdsf1qig06Xpdu9UDM3OiZ" +
+            "iZpM7uanrEUC7jfK4bJ30r7UTiTsJBNE7FNn5F21CNX3mFKwSYyDv3adC8NIFbjH" +
+            "B85Dul/eQLuv1+by72cGUQ3XYextDxi+7H+V3mrlFoiUPX2PN9VHr6EnNuPZmdTJ" +
+            "CziSwB8gdPNN0u21HFL2VNFORXHa9tSehIHLpNgXWZ/qdE+lKbWuJnGeRHj4FAv+" +
+            "MQaafW0uHF+N8MDm8UWPvf4Vd0UJ0UpIjRWl2hTV+BHkNfvZlBRhhQIphNiKRe/W" +
+            "ap0f/lW2Gm2uS0KgByjjNXEzTiwrte2GX65M6F6Lz8N31kt1Iig1xGOuv+6HmxTN" +
+            "R8gL2K5PdJeJn8PTJWrRS7+BY8Hdkgb+wVpzE5cCvpFiG/P0yqfBdLWxVPlPI7dc" +
+            "hDkmx4iAhHJX9J/gX/hC6L3AzPNJqNPAKy20wYp/ruTbbwBolW/4ikWij460JrvB" +
+            "sm6Sp81A3ebaiN9XkJygLOyhGyhMieGulCYz6AahAFcECtPXGTcordV1mJth8yjF" +
+            "4gZfDQyg0nMW4Yr49yeFXcRMUw1yzN3Q9v2zzqDuFi2lGYTXYmVqLYzM9KbLO2Wx" +
+            "E/21xnBjLsl09l/FdA/bhdZq3t4/apbFOeQQ/j/AphvzWbsJnhG9Q7+d3VoDlz0g" +
+            "FiSduCYIAAq8dUOJNjrUTkZsL1pOIjhYjCMi2uiKS6RQkT6nvuumPF/D/VTnUGeZ" +
+            "wooEGBEIADwFAl3+CmkDCwkKCRCbp4ncdtaEmgQVCgkIAhYBAheAAhsMAh4BFiEE" +
+            "cf/aAEQJ5d2ww+jxm6eJ3HbWhJoAAEEpAP91hFqmcb2ZqVcaRDMSVmhkEcFIRmpH" +
+            "vDoQtVn8sArWqwEAi8HwbMhL+YwRItRZDknpC4vFjTHVMd1zMrz/JyeuT9k="
+            );
+
+        // v6 Ed25519/X25519 key from crypto-refresh
         // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
         // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
         private static readonly byte[] v6Certificate = Base64.Decode(
@@ -67,7 +252,7 @@ public class PgpInteroperabilityTestSuite
             "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr" +
             "k0mXubZvyl4GBg==");
 
-        // v5 keys from "OpenPGP interoperability test suite"
+        // v5 key "Emma" from "OpenPGP interoperability test suite"
         // https://tests.sequoia-pgp.org/#Inline_Sign-Verify_roundtrip_with_key__Emma_
         private static readonly byte[] v5Certificate = Base64.Decode(
             "mDcFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd" +
@@ -506,12 +691,119 @@ public void Version5InlineSignatureTest()
             IsTrue(ops.Verify(sigs[0], metadata));
         }
 
+        [Test]
+        public void SeipdVersion2WithMultipleMethodsTest()
+        {
+            // Encrypt-Decrypt roundtrip V6 SKESK/PKESK, V2 SEIPD, AES-256 in EAX mode
+            // multiple different passwords and public keys with different algorithms
+            // and S2K schemes
+            byte[] largePlaintext = new byte[50000];
+            Arrays.Fill(largePlaintext, (byte)'A');
+            byte[] enc;
+
+            byte[][] passwords = new byte[][]
+            {
+                Encoding.UTF8.GetBytes("password"),
+                Encoding.UTF8.GetBytes("drowssap"),
+                Encoding.UTF8.GetBytes("P@ssw0rd")
+            };
+
+            var alice = new PgpPublicKeyRing(alicePubkey).GetPublicKeys().First(k => k.IsEncryptionKey);
+            var bob = new PgpPublicKeyRing(bobPubkey).GetPublicKeys().First(k => k.IsEncryptionKey);
+            var v6 = new PgpPublicKeyRing(v6Certificate).GetPublicKeys().First(k => k.IsEncryptionKey);
+            var carol = new PgpPublicKeyRing(carolPubkey).GetPublicKeys().First(k => k.IsEncryptionKey);
+
+            PgpPrivateKey[] privateKeys = new PgpPrivateKey[]
+            {
+                new PgpSecretKeyRing(aliceSecretkey).GetSecretKey(alice.KeyId).ExtractPrivateKey(emptyPassphrase),
+                new PgpSecretKeyRing(bobSecretkey).GetSecretKey(bob.KeyId).ExtractPrivateKey(emptyPassphrase),
+                new PgpSecretKeyRing(v6UnlockedSecretKey).GetSecretKey(v6.KeyId).ExtractPrivateKey(emptyPassphrase)
+            };
+
+            var gen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, AeadAlgorithmTag.Eax);
+            gen.AddMethodRaw(passwords[0], S2k.Argon2Parameters.UniversallyRecommendedParameters());
+            gen.AddMethodRaw(passwords[1], S2k.Argon2Parameters.MemoryConstrainedParameters());
+            gen.AddMethodRaw(passwords[2], HashAlgorithmTag.Sha256);
+            gen.AddMethod(alice);
+            gen.AddMethod(bob);
+            gen.AddMethod(v6);
+
+            // Check constraint: An implementation MUST NOT generate ElGamal v6 PKESKs.
+            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-fo
+            Assert.Throws<PgpException>(() =>
+            {
+                gen.AddMethod(carol);
+            });
+
+            using (MemoryStream ms = new MemoryStream())
+            {
+                byte[] buffer = new byte[3000];
+                using (Stream cOut = gen.Open(ms, buffer))
+                {
+                    using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
+                    {
+                        PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+                        DateTime modificationTime = DateTime.UtcNow;
+
+                        using (Stream lOut = literalDataGen.Open(
+                            new UncloseableStream(bcOut),
+                            PgpLiteralData.Utf8,
+                            PgpLiteralData.Console,
+                            largePlaintext.Length,
+                            modificationTime))
+                        {
+                            lOut.Write(largePlaintext, 0, largePlaintext.Length);
+                        }
+                    }
+                }
+                enc = ms.ToArray();
+            }
+
+            // decrypt
+            for (int i = 0; i < passwords.Length; i++)
+            {
+                PgpObjectFactory factory = new PgpObjectFactory(enc);
+                PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+                PgpPbeEncryptedData encData = encDataList[i] as PgpPbeEncryptedData;
+                using (Stream stream = encData.GetDataStreamRaw(passwords[i]))
+                {
+                    factory = new PgpObjectFactory(stream);
+                    PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                    using (MemoryStream ms = new MemoryStream())
+                    {
+                        lit.GetDataStream().CopyTo(ms);
+                        byte[] decrypted = ms.ToArray();
+                        IsTrue(Arrays.AreEqual(largePlaintext, decrypted));
+                    }
+                }
+            }
+
+            for (int i = 0; i < privateKeys.Length; i++)
+            {
+                PgpObjectFactory factory = new PgpObjectFactory(enc);
+                PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+                PgpPublicKeyEncryptedData encData = encDataList[3+i] as PgpPublicKeyEncryptedData;
+                
+                using (Stream stream = encData.GetDataStream(privateKeys[i]))
+                    {
+                        factory = new PgpObjectFactory(stream);
+                        PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+                        using (MemoryStream ms = new MemoryStream())
+                        {
+                            lit.GetDataStream().CopyTo(ms);
+                            byte[] decrypted = ms.ToArray();
+                            IsTrue(Arrays.AreEqual(largePlaintext, decrypted));
+                        }
+                    }
+            }
+        }
 
         public override string Name => "PgpInteroperabilityTestSuite";
 
         public override void PerformTest()
         {
             MultiplePkeskTest();
+            SeipdVersion2WithMultipleMethodsTest();
 
             MultipleInlineSignatureTest();
             GenerateAndVerifyMultipleInlineSignatureTest();

From bdbb66cf50e59a3b6e48d4fbe21006751f63d4c9 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Fri, 16 Aug 2024 17:50:52 +0200
Subject: [PATCH 28/37] Update references to "crypto-refresh" to point to RFC
 9580.

---
 crypto/src/bcpg/ArmoredOutputStream.cs        |  4 +-
 crypto/src/bcpg/Ed25519PublicBCPGKey.cs       |  2 +-
 crypto/src/bcpg/Ed25519SecretBCPGKey.cs       |  2 +-
 crypto/src/bcpg/Ed448PublicBCPGKey.cs         |  2 +-
 crypto/src/bcpg/Ed448SecretBCPGKey.cs         |  2 +-
 crypto/src/bcpg/OnePassSignaturePacket.cs     |  4 +-
 crypto/src/bcpg/Packet.cs                     |  2 +-
 crypto/src/bcpg/PacketTags.cs                 |  2 +-
 crypto/src/bcpg/PublicKeyAlgorithmTags.cs     |  4 +-
 crypto/src/bcpg/PublicKeyEncSessionPacket.cs  |  8 +-
 crypto/src/bcpg/S2k.cs                        |  4 +-
 crypto/src/bcpg/SignaturePacket.cs            | 12 +--
 crypto/src/bcpg/SignatureSubpacketTags.cs     |  2 +-
 .../src/bcpg/SymmetricEncIntegrityPacket.cs   |  4 +-
 .../src/bcpg/SymmetricKeyEncSessionPacket.cs  |  2 +-
 crypto/src/bcpg/X25519PublicBCPGKey.cs        |  2 +-
 crypto/src/bcpg/X25519SecretBCPGKey.cs        |  2 +-
 crypto/src/bcpg/X448PublicBCPGKey.cs          |  2 +-
 crypto/src/bcpg/X448SecretBCPGKey.cs          |  2 +-
 .../src/openpgp/PgpEncryptedDataGenerator.cs  |  2 +-
 crypto/src/openpgp/PgpKeyRingGenerator.cs     |  5 +-
 crypto/src/openpgp/PgpOnePassSignature.cs     |  2 +-
 crypto/src/openpgp/PgpPbeEncryptedData.cs     |  4 +-
 .../src/openpgp/PgpPublicKeyEncryptedData.cs  | 12 +--
 crypto/src/openpgp/PgpSecretKey.cs            |  5 +-
 crypto/src/openpgp/PgpSignatureGenerator.cs   | 20 ++--
 crypto/src/openpgp/PgpUtilities.cs            |  2 +-
 .../test/data/openpgp/big-pkesk-aead-msg.asc  |  2 +-
 .../src/openpgp/test/PgpCryptoRefreshTest.cs  | 96 +++++++++++++------
 .../test/PgpInteroperabilityTestSuite.cs      | 20 ++--
 30 files changed, 139 insertions(+), 95 deletions(-)

diff --git a/crypto/src/bcpg/ArmoredOutputStream.cs b/crypto/src/bcpg/ArmoredOutputStream.cs
index f39e5095b8..b27d540082 100644
--- a/crypto/src/bcpg/ArmoredOutputStream.cs
+++ b/crypto/src/bcpg/ArmoredOutputStream.cs
@@ -263,7 +263,7 @@ public void BeginClearText()
         /// This header is deprecated and SHOULD NOT be emitted unless:
         ///    * The cleartext signed message contains a v4 signature made using a SHA2-based digest(SHA224, SHA256, SHA384, or SHA512), and
         ///    * The cleartext signed message might be verified by a legacy OpenPGP implementation that requires this header.
-        ///    <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-hash-armor-header"/>
+        ///    <seealso href="https://www.rfc-editor.org/rfc/rfc9580#name-hash-armor-header"/>
         /// </summary>
         /// <param name="hashAlgorithm"></param>
         /// <exception cref="IOException"></exception>
@@ -373,7 +373,7 @@ public override void WriteByte(byte value)
 
                 DoWrite(headerStart + type + headerTail + NewLine);
 
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-armor-header
+                // https://www.rfc-editor.org/rfc/rfc9580#name-version-armor-header
                 // To minimize metadata, implementations SHOULD NOT emit this key and its corresponding value except
                 // for debugging purposes with explicit user consent.
                 if (showVersion && m_headers.TryGetValue(HeaderVersion, out var versionHeaders))
diff --git a/crypto/src/bcpg/Ed25519PublicBCPGKey.cs b/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
index 8026579097..8c23d82180 100644
--- a/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
+++ b/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class Ed25519PublicBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed2
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed2
         public const int length = 32;
 
         public Ed25519PublicBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/Ed25519SecretBCPGKey.cs b/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
index e161707e9b..67e5497387 100644
--- a/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
+++ b/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class Ed25519SecretBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed2
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed2
         public const int length = 32;
 
         public Ed25519SecretBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/Ed448PublicBCPGKey.cs b/crypto/src/bcpg/Ed448PublicBCPGKey.cs
index fc0283969a..102fbf7a5f 100644
--- a/crypto/src/bcpg/Ed448PublicBCPGKey.cs
+++ b/crypto/src/bcpg/Ed448PublicBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class Ed448PublicBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed4
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed4
         public const int length = 57;
 
         public Ed448PublicBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/Ed448SecretBCPGKey.cs b/crypto/src/bcpg/Ed448SecretBCPGKey.cs
index 3b355ceb95..489692cf3f 100644
--- a/crypto/src/bcpg/Ed448SecretBCPGKey.cs
+++ b/crypto/src/bcpg/Ed448SecretBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class Ed448SecretBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-ed4
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed4
         public const int length = 57;
 
         public Ed448SecretBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/OnePassSignaturePacket.cs b/crypto/src/bcpg/OnePassSignaturePacket.cs
index cde5501b4c..3f22a8f169 100644
--- a/crypto/src/bcpg/OnePassSignaturePacket.cs
+++ b/crypto/src/bcpg/OnePassSignaturePacket.cs
@@ -29,9 +29,9 @@ private void EnforceConstraints()
 
             if (version == Version6 && salt.Length != expectedSaltSize)
             {
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-one-pass-signature-packet-t
+                // https://www.rfc-editor.org/rfc/rfc9580#name-one-pass-signature-packet-t
                 // The salt size MUST match the value defined for the hash algorithm as specified in Table 23
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#hash-algorithms-registry
+                // https://www.rfc-editor.org/rfc/rfc9580#hash-algorithms-registry
                 throw new IOException($"invalid salt size for v6 signature: expected {expectedSaltSize} got {salt.Length}");
             }
         }
diff --git a/crypto/src/bcpg/Packet.cs b/crypto/src/bcpg/Packet.cs
index 6c8b95b6d1..ffca0bf939 100644
--- a/crypto/src/bcpg/Packet.cs
+++ b/crypto/src/bcpg/Packet.cs
@@ -28,7 +28,7 @@ public Packet(PacketTag packetTag)
         ///    * Packets with tags less or equal to 39 are critical.
         ///    * Tags 40 to 59 are reserved for unassigned, non-critical packets.
         ///    * Tags 60 to 63 are non-critical private or experimental packets.
-        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-packet-criticality"/>
+        /// <seealso href="https://www.rfc-editor.org/rfc/rfc9580#name-packet-criticality"/>
         /// </summary>
         public bool IsCritical => (int)Tag <= 39;
     }
diff --git a/crypto/src/bcpg/PacketTags.cs b/crypto/src/bcpg/PacketTags.cs
index d6c2fc0757..43590e5f9c 100644
--- a/crypto/src/bcpg/PacketTags.cs
+++ b/crypto/src/bcpg/PacketTags.cs
@@ -24,7 +24,7 @@ public enum PacketTag
         ModificationDetectionCode = 19,		        // Reserved (formerly Modification Detection Code Packet)
         ReservedAeadEncryptedData = 20,             // Reserved (defined as AEAD Encrypted Data Packet in retired draft [draft-koch-openpgp-rfc4880bis])
 
-        Padding = 21,                               // Padding Packet [https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#padding-packet]
+        Padding = 21,                               // Padding Packet (https://www.rfc-editor.org/rfc/rfc9580#padding-packet)
 
         Experimental1 = 60,							// Private or Experimental Values
         Experimental2 = 61,
diff --git a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
index a88a84c0d6..07ac3c2602 100644
--- a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
@@ -21,11 +21,11 @@ public enum PublicKeyAlgorithmTag
         EdDsa = 22,             // EdDSA - (internet draft, but appearing in use)
         EdDsa_Legacy = 22,      // new name for old EdDSA tag.
 
-        // defined as Reserved by crypto-refresh draft
+        // defined as Reserved by RFC 9580
         AEDH = 23,
         AEDSA = 24,
 
-        // https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/
+        // https://www.rfc-editor.org/rfc/rfc9580
         X25519 = 25,
         X448 = 26,
         Ed25519 = 27,
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index 2a23bc3f38..20a980504b 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -22,13 +22,13 @@ public class PublicKeyEncSessionPacket
         /// <summary>
         /// Version 3 PKESK packet.
         /// </summary>
-        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-3-public-key-encryp"/>
+        /// <seealso href="https://www.rfc-editor.org/rfc/rfc9580#name-version-3-public-key-encryp"/>
         public const int Version3 = 3;
 
         /// <summary>
         /// Version 6 PKESK packet.
         /// </summary>
-		/// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-public-key-encryp"/>
+		/// <seealso href="https://www.rfc-editor.org/rfc/rfc9580#name-version-6-public-key-encryp"/>
         public const int Version6 = 6;
 
         internal PublicKeyEncSessionPacket(
@@ -103,8 +103,8 @@ internal PublicKeyEncSessionPacket(
                     break;
                 case PublicKeyAlgorithmTag.X25519:
                 case PublicKeyAlgorithmTag.X448:
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-x
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-x
 
                     // 32 (for X25519) or 56 (for X448) octets representing an ephemeral public key.
                     int keylen = algorithm == PublicKeyAlgorithmTag.X25519 ? 32 : 56;
diff --git a/crypto/src/bcpg/S2k.cs b/crypto/src/bcpg/S2k.cs
index 05adc530b3..0274138202 100644
--- a/crypto/src/bcpg/S2k.cs
+++ b/crypto/src/bcpg/S2k.cs
@@ -225,7 +225,7 @@ public override void Encode(
 
         /// <summary>
         /// Parameters for Argon2 S2K
-        /// <see href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#s2k-argon2">Sect. 3.7.1.4 of crypto-refresh</see>see>
+        /// <see href="https://www.rfc-editor.org/rfc/rfc9580#s2k-argon2">Sect. 3.7.1.4 of RFC 9580</see>
         /// </summary>
         public class Argon2Parameters
         {
@@ -304,7 +304,7 @@ public Argon2Parameters(byte[] salt, int passes, int parallelism, int memSizeExp
 
                 // log_2(p) = log_e(p) / log_e(2)
                 double log2_p = System.Math.Log(parallelism) / System.Math.Log(2);
-                // see https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-argon2
+                // see https://www.rfc-editor.org/rfc/rfc9580#name-argon2
                 if (memSizeExp < (3 + System.Math.Ceiling(log2_p)) || memSizeExp > 31)
                 {
                     throw new ArgumentException("Memory size exponent MUST be between 3+ceil(log_2(parallelism)) and 31");
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 5af8843876..1f364fdf1d 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -59,8 +59,8 @@ private void CheckIssuerSubpacket(SignatureSubpacket p)
 
             else if (p is IssuerKeyId issuerKeyId && !keyIdAlreadySet)
             {
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-issuer-key-id
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#issuer-fingerprint-subpacket
+                // https://www.rfc-editor.org/rfc/rfc9580#name-issuer-key-id
+                // https://www.rfc-editor.org/rfc/rfc9580#issuer-fingerprint-subpacket
                 // V6 signatures MUST NOT include an IssuerKeyId subpacket and SHOULD include an IssuerFingerprint subpacket
                 if (version == Version6)
                 {
@@ -197,9 +197,9 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
 
                 if (saltSize != PgpUtilities.GetSaltSize(hashAlgorithm))
                 {
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-4-and-6-signature-p
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-versions-4-and-6-signature-
                     // The salt size MUST match the value defined for the hash algorithm as specified in Table 23
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#hash-algorithms-registry
+                    // https://www.rfc-editor.org/rfc/rfc9580#hash-algorithms-registry
 
                     throw new IOException($"invalid salt size for v6 signature: expected {PgpUtilities.GetSaltSize(hashAlgorithm)} got {saltSize}");
                 }
@@ -235,13 +235,13 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
                 break;
 
             case PublicKeyAlgorithmTag.Ed25519:
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-ed2
+                // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-ed2
                 signature = null;
                 signatureEncoding = new byte[64];
                 bcpgIn.ReadFully(signatureEncoding);
                 break;
             case PublicKeyAlgorithmTag.Ed448:
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-ed4
+                // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-ed4
                 signature = null;
                 signatureEncoding = new byte[114];
                 bcpgIn.ReadFully(signatureEncoding);
diff --git a/crypto/src/bcpg/SignatureSubpacketTags.cs b/crypto/src/bcpg/SignatureSubpacketTags.cs
index 0574c274b7..082d668393 100644
--- a/crypto/src/bcpg/SignatureSubpacketTags.cs
+++ b/crypto/src/bcpg/SignatureSubpacketTags.cs
@@ -30,7 +30,7 @@ public enum SignatureSubpacketTag
         SignatureTarget = 31,                   // signature target
         EmbeddedSignature = 32,					// embedded signature
         IssuerFingerprint = 33,                 // issuer key fingerprint
-        //PreferredAeadAlgorithms = 34,         // RESERVED since crypto-refresh-05
+        //PreferredAeadAlgorithms = 34,         // RESERVED since RFC 9580
         IntendedRecipientFingerprint = 35,      // intended recipient fingerprint
         AttestedCertifications = 37,            // attested certifications (RESERVED)
         KeyBlock = 38,                          // Key Block (RESERVED)
diff --git a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
index 6692851beb..fbb08b6945 100644
--- a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
@@ -10,12 +10,12 @@ public class SymmetricEncIntegrityPacket
         /// <summary>
         /// Version 3 SEIPD packet.
         /// </summary>
-        /// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-symmetrically-encrypted-int"/>
+        /// <seealso href="https://www.rfc-editor.org/rfc/rfc9580#name-symmetrically-encrypted-dat"/>
         public const int Version1 = 1;
         /// <summary>
         /// Version 2 SEIPD packet.
         /// </summary>
-		/// <seealso href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-symmetrically-encrypted-int"/>
+		/// <seealso href="https://www.rfc-editor.org/rfc/rfc9580#name-symmetrically-encrypted-dat"/>
         public const int Version2 = 2;
 
         private readonly int version;                               // V1, V2
diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
index 224ca8243b..cb55357333 100644
--- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
@@ -56,7 +56,7 @@ public SymmetricKeyEncSessionPacket(
 
                 case Version5:
                 case Version6:
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-symmetric-key-enc
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-version-6-symmetric-key-enc
                     // SymmAlgo + AEADAlgo + S2KCount + S2K + IV
                     int next5Fields5Count = bcpgIn.ReadByte();
                     encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
diff --git a/crypto/src/bcpg/X25519PublicBCPGKey.cs b/crypto/src/bcpg/X25519PublicBCPGKey.cs
index b8eb52a4c7..edb8f67e8e 100644
--- a/crypto/src/bcpg/X25519PublicBCPGKey.cs
+++ b/crypto/src/bcpg/X25519PublicBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class X25519PublicBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x
         public const int length = 32;
 
         public X25519PublicBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/X25519SecretBCPGKey.cs b/crypto/src/bcpg/X25519SecretBCPGKey.cs
index 3da6f01b6a..cbdf8abd58 100644
--- a/crypto/src/bcpg/X25519SecretBCPGKey.cs
+++ b/crypto/src/bcpg/X25519SecretBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class X25519SecretBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x
         public const int length = 32;
 
         public X25519SecretBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/X448PublicBCPGKey.cs b/crypto/src/bcpg/X448PublicBCPGKey.cs
index 9b35cbd3b5..c6f370bb12 100644
--- a/crypto/src/bcpg/X448PublicBCPGKey.cs
+++ b/crypto/src/bcpg/X448PublicBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class X448PublicBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x4
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x4
         public const int length = 56;
 
         public X448PublicBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/bcpg/X448SecretBCPGKey.cs b/crypto/src/bcpg/X448SecretBCPGKey.cs
index a8ce5e8097..16bfab2f5d 100644
--- a/crypto/src/bcpg/X448SecretBCPGKey.cs
+++ b/crypto/src/bcpg/X448SecretBCPGKey.cs
@@ -3,7 +3,7 @@
     public sealed class X448SecretBcpgKey
         : OctetArrayBcpgKey
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-part-for-x4
+        // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x4
         public const int length = 56;
 
         public X448SecretBcpgKey(BcpgInputStream bcpgIn)
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index a80578549d..2c55469530 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -655,7 +655,7 @@ public void AddMethod(PgpPublicKey key, bool sessionKeyObfuscation)
             if (pkeskVersion == PublicKeyEncSessionPacket.Version6
                 && (key.Algorithm == PublicKeyAlgorithmTag.ElGamalEncrypt || key.Algorithm == PublicKeyAlgorithmTag.ElGamalGeneral))
             {
-                throw new PgpException("cannot generate ElGamal v6 PKESK (see https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-fo)");
+                throw new PgpException("cannot generate ElGamal v6 PKESK (see https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-fo)");
             }
 
             methods.Add(new PubMethod(key, sessionKeyObfuscation, pkeskVersion));
diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs
index 9f9bbd6ff4..6ef7af816f 100644
--- a/crypto/src/openpgp/PgpKeyRingGenerator.cs
+++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs
@@ -272,7 +272,8 @@ public void AddSubKey(
 			PgpSignatureSubpacketVector	hashedPackets,
 			PgpSignatureSubpacketVector	unhashedPackets)
 		{
-            AddSubKey(keyPair, hashedPackets, unhashedPackets, HashAlgorithmTag.Sha1);
+            AddSubKey(keyPair, hashedPackets, unhashedPackets,
+                keyPair.PublicKey.Version > PublicKeyPacket.Version4 ? HashAlgorithmTag.Sha256 : HashAlgorithmTag.Sha1);
         }
 
         /// <summary>
@@ -397,7 +398,7 @@ public PgpPublicKeyRing GeneratePublicKeyRing()
                 pgpSecretKey = enumerator.Current;
 
 				PgpPublicKey k = new PgpPublicKey(pgpSecretKey.PublicKey);
-				k.publicPk = new PublicSubkeyPacket(k.Algorithm, k.CreationTime, k.publicPk.Key);
+				k.publicPk = new PublicSubkeyPacket(k.Version, k.Algorithm, k.CreationTime, k.publicPk.Key);
 
 				pubKeys.Add(k);
 			}
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index 704cc7bd9e..c7505b072b 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -155,7 +155,7 @@ public bool Verify(PgpSignature pgpSig)
         public bool Verify(PgpSignature pgpSig, byte[] additionalMetadata)
         {
             // the versions of the Signature and the One-Pass Signature must be aligned as specified in
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#signed-message-versions
+            // https://www.rfc-editor.org/rfc/rfc9580#signed-message-versions
             if (pgpSig.Version == SignaturePacket.Version6 && sigPack.Version != OnePassSignaturePacket.Version6)
             {
                 return false;
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index 8ba9b4bdf0..c4bca5b20c 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -29,7 +29,7 @@ private void EnforceConstraints()
             switch (keyData.Version)
             {
                 case SymmetricKeyEncSessionPacket.Version4:
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-4-symmetric-key-enc
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-version-4-symmetric-key-enc
                     // A version 4 SKESK packet precedes a version 1 SEIPD packet. In historic data, it is sometimes found
                     // preceding a deprecated SED packet. A v4 SKESK packet MUST NOT precede a v2 SEIPD packet.
                     if (encData is SymmetricEncDataPacket)
@@ -54,7 +54,7 @@ private void EnforceConstraints()
                     break;
 
                 case SymmetricKeyEncSessionPacket.Version6:
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-symmetric-key-enc
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-version-6-symmetric-key-enc
                     // A version 6 SKESK packet precedes a version 2 SEIPD packet. A v6 SKESK packet MUST NOT precede a v1 SEIPD
                     // packet or a deprecated Symmetrically Encrypted Data.
                     if (encData is SymmetricEncDataPacket)
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 64d02ebfe4..402351bdaf 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -36,7 +36,7 @@ private void EnforceConstraints()
             switch (keyData.Version)
             {
                 case PublicKeyEncSessionPacket.Version3:
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-3-public-key-encryp
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-version-3-public-key-encryp
                     // A version 3 PKESK packet precedes a version 1 SEIPD packet. In historic data, it is sometimes
                     // found preceding a deprecated SED packet.
                     // A V3 PKESK packet MUST NOT precede a V2 SEIPD packet.
@@ -55,7 +55,7 @@ private void EnforceConstraints()
                     break;
 
                 case PublicKeyEncSessionPacket.Version6:
-                    // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-public-key-encryp
+                    // https://www.rfc-editor.org/rfc/rfc9580#name-version-6-public-key-encryp
                     //A version 6 PKESK packet precedes a version 2 SEIPD packet.
                     //A V6 PKESK packet MUST NOT precede a V1 SEIPD packet or a deprecated SED packet.
                     if (encData is SymmetricEncDataPacket)
@@ -143,7 +143,7 @@ public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
         {
             if (keyData.Version == PublicKeyEncSessionPacket.Version3)
             {
-                // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-
+                // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-
                 // In V3 PKESK, the symmetric algorithm Id
                 //     * with X25519 and X448 is not encrypted, it's prepended in plaintext
                 //       to the encrypted session key.
@@ -348,10 +348,10 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey)
 
             if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
             {
-                // See sect. 5.1.6. and 5.1.7 of crypto-refresh for the description of
+                // See sect. 5.1.6. and 5.1.7 of RFC 9580 for the description of
                 // the key derivation algorithm for X25519 and X448 
-                //     https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-
-                //     https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-for-x
+                //     https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-
+                //     https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-x
                 byte[] eph = secKeyData[0];
                 byte[] esk = secKeyData[1];
 
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 7ecad0b99b..57b3609488 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -158,7 +158,7 @@ internal PgpSecretKey(
                     s2kUsage = SecretKeyPacket.UsageNone;
                 }
 
-                // RFC 4880 � 5.5.3 + "RFC 4880bis" � 5.5.3 + Crypto-refresh � 5.5.3. 
+                // RFC 4880 � 5.5.3 + "RFC 4880bis" � 5.5.3 + RFC 9580 � 5.5.3. 
                 // v6       keys with UsageNone:    No checksum
                 // v5 v6    keys with MalleableCFB: Not allowed
                 // v3 v4 v5 keys with UsageNone:    two-octet checksum
@@ -277,7 +277,8 @@ private static PgpPublicKey CertifiedPublicKey(
             PgpSignatureSubpacketVector unhashedPackets,
             SecureRandom rand)
         {
-            return CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, HashAlgorithmTag.Sha1, rand);
+            return CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets,
+                keyPair.PublicKey.Version > PublicKeyPacket.Version4 ? HashAlgorithmTag.Sha256 : HashAlgorithmTag.Sha1, rand);
         }
 
 
diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs
index dbfe93e6ff..b5c71ff940 100644
--- a/crypto/src/openpgp/PgpSignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -1,4 +1,5 @@
 using System;
+using System.ComponentModel;
 using System.IO;
 
 using Org.BouncyCastle.Bcpg.Sig;
@@ -52,14 +53,21 @@ public void InitSign(int sigType, PgpPrivateKey privKey)
         /// <summary>Initialise the generator for signing.</summary>
         public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
 		{
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-signature-packet-type-id-2
+            // https://www.rfc-editor.org/rfc/rfc9580#name-signature-packet-type-id-2
             // An implementation MUST generate a version 6 signature when signing with a version 6 key.
-			// An implementation MUST generate a version 4 signature when signing with a version 4 key. 
+            // An implementation MUST generate a version 4 signature when signing with a version 4 key. 
             this.version = privKey.Version;
 			this.privKey = privKey;
 			this.signatureType = sigType;
 
-			AsymmetricKeyParameter key = privKey.Key;
+            // https://www.rfc-editor.org/rfc/rfc9580#name-hash-algorithms
+            // avoid V6 signatures with weak hash algorithms
+            if (this.version > SignaturePacket.Version4 && (hashAlgorithm == HashAlgorithmTag.MD5 || hashAlgorithm == HashAlgorithmTag.Sha1 || hashAlgorithm == HashAlgorithmTag.RipeMD160))
+			{
+				throw new PgpException("avoid V6 signatures with weak hash algorithms");
+			}
+
+            AsymmetricKeyParameter key = privKey.Key;
 
             this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key);
 
@@ -216,7 +224,7 @@ public PgpOnePassSignature GenerateOnePassVersion(
 			OnePassSignaturePacket opsPkt;
 
             // the versions of the Signature and the One-Pass Signature must be aligned as specified in
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#signed-message-versions
+            // https://www.rfc-editor.org/rfc/rfc9580#signed-message-versions
             if (version == SignaturePacket.Version6)
 			{
 				opsPkt = new OnePassSignaturePacket(
@@ -242,8 +250,8 @@ public PgpSignature Generate()
 				hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
 			}
 
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-issuer-key-id
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#issuer-fingerprint-subpacket
+            // https://www.rfc-editor.org/rfc/rfc9580#name-issuer-key-id
+            // https://www.rfc-editor.org/rfc/rfc9580#issuer-fingerprint-subpacket
             bool containsIssuerKeyId = IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId) || IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId);
             bool containsIssuerKeyFpr = IsPacketPresent(hashed, SignatureSubpacketTag.IssuerFingerprint) || IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerFingerprint);
             switch (version)
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 620724008d..477e9e906c 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -118,7 +118,7 @@ public static string GetDigestName(
 
         /// <summary>
         /// Returns the V6 signature salt size for a hash algorithm.
-        /// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-hash-algorithms
+        /// https://www.rfc-editor.org/rfc/rfc9580#name-hash-algorithms
         /// </summary>
         public static int GetSaltSize(HashAlgorithmTag hashAlgorithm)
         {
diff --git a/crypto/test/data/openpgp/big-pkesk-aead-msg.asc b/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
index c481452ad7..7380c636e8 100644
--- a/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
+++ b/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
@@ -2,7 +2,7 @@
 Comment: V6 PKESK + V2 SEIPD AEAD encrypted message that spans
 Comment: over 4 chunks (chunk size 512 octets)
 Comment: 2000 octets of /dev/zero encrypted with sample V6
-Comment: certificate from crypto-refresh Appendix A.3
+Comment: certificate from RFC 9580 Appendix A.3
 Comment: generated with gosop 2.0.0-alpha
 Comment: Session key CFB73D46CF7C13B7535227BEDB5B2D8B4023C5B...
 Comment: Session key ...58289D19CF2C33B0DB388B0B6
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 053d08f34f..7677a2c27d 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -17,17 +17,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
     public class PgpCryptoRefreshTest
         : SimpleTest
     {
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519leg
         private readonly byte[] v4Ed25519LegacyPubkeySample = Base64.Decode(
             "xjMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" +
             "Q+47JAY=");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-sig
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519lega
         private readonly byte[] v4Ed25519LegacySignatureSample = Base64.Decode(
             "iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgD/VvkMypjiECY3vZg/2xbBMd/S" +
             "ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
         private readonly byte[] v6Certificate = Base64.Decode(
             "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf" +
             "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy" +
@@ -39,7 +39,7 @@ public class PgpCryptoRefreshTest
             "j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805" +
             "I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-secret-key
         private readonly byte[] v6UnlockedSecretKey = Base64.Decode(
             "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB" +
             "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ" +
@@ -53,7 +53,7 @@ public class PgpCryptoRefreshTest
             "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr" +
             "k0mXubZvyl4GBg==");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-locked-v6-secret-key
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-locked-version-6-sec
         private readonly byte[] v6LockedSecretKey = Base64.Decode(
             "xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC" +
             "FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS" +
@@ -69,7 +69,7 @@ public class PgpCryptoRefreshTest
             "Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt" +
             "ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-cleartext-signed-mes
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-cleartext-signed-mes
         private readonly string v6SampleCleartextSignedMessage = "What we need from the grocery store:\r\n\r\n- tofu\r\n- vegetables\r\n- noodles\r\n";
         private readonly byte[] v6SampleCleartextSignedMessageSignature = Base64.Decode(
             "wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
@@ -77,7 +77,7 @@ public class PgpCryptoRefreshTest
             "/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr" +
             "NK2ay45cX1IVAQ==");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-inline-signed-messag
         private readonly byte[] v6SampleInlineSignedMessage = Base64.Decode(
             "xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk" +
             "1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv" +
@@ -88,7 +88,7 @@ public class PgpCryptoRefreshTest
             "FtCgazStmsuOXF9SFQE=");
 
         // Sample AEAD encryption and decryption - V6 SKESK + V2 SEIPD
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-eax-encryption-
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-aead-eax-encryption-
         // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
         // using AES-128 with AEAD-EAX encryption.
         private readonly byte[] v6skesk_aes128_eax = Base64.Decode(
@@ -97,7 +97,7 @@ public class PgpCryptoRefreshTest
             "pJ8EwuZ0F11KPSJu1q/LnKmsEiwUcOEcY9TAqyQcapOK1Iv5mlqZuQu6gyXeYQR1" +
             "QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o=");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-ocb-encryption-
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-aead-ocb-encryption-
         // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
         // using AES-128 with AEAD-OCB encryption.
         private readonly byte[] v6skesk_aes128_ocb = Base64.Decode(
@@ -106,7 +106,7 @@ public class PgpCryptoRefreshTest
             "WRDQns3WQf+f04VidYA1vEl1TOG/P/+n2tCjuBBPUTPPQqQQCoPu9MobSAGohGv0" +
             "K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw==");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-aead-gcm-encryption-
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-aead-gcm-encryption-
         // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
         // using AES-128 with AEAD-GCM encryption.
         private readonly byte[] v6skesk_aes128_gcm = Base64.Decode(
@@ -115,7 +115,7 @@ public class PgpCryptoRefreshTest
             "Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS" +
             "+pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q==");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-messages-encrypted-u
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-messages-encrypted-u
         // V4 SKESK + V1 SEIPD using Argon2 (t=1, p=4, m=21) with AES-128/192/256,
         // cleartext string "Hello, world!", passphrase "password"
         private readonly byte[] v4skesk_argon2_aes128 = Base64.Decode(
@@ -143,7 +143,7 @@ public class PgpCryptoRefreshTest
             "Zjd4SG7Tv4RJHeycolKmqSHDoK5XlOsA7vlw50nKuRjDyRfsPOFDfHz8hR/z7D1i" +
             "HST68tjRCRmwqeqVgusCmBlXrXzYTkPXGtmZl2+EYazSACQFVg==");
 
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-x25519-aead-ocb-encr
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-x25519-aead-ocb-encr
         // encrypts the cleartext string "Hello, world!" for the sample certificate v6Certificate
         // V6 PKESK + V2 SEIPD X25519 AES-128 OCB
         private readonly byte[] v6pkesk_v2seipd_aes128_ocb = Base64.Decode(
@@ -390,7 +390,7 @@ private void PubkeyEncryptDecryptRoundtrip(byte[] plaintext, bool useAead,bool u
         [Test]
         public void Version4Ed25519LegacyPubkeySampleTest()
         {
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
+            // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519leg
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v4Ed25519LegacyPubkeySample);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
@@ -406,7 +406,7 @@ public void Version4Ed25519LegacyPubkeySampleTest()
         public void Version4Ed25519LegacyCreateTest()
         {
             // create a v4 EdDsa_Legacy Pubkey with the same key material and creation datetime as the test vector
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-key
+            // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519leg
             // then check KeyId/Fingerprint
             var key = new Ed25519PublicKeyParameters(Hex.Decode("3f098994bdd916ed4053197934e4a87c80733a1280d62f8010992e43ee3b2406"));
             var pubKey = new PgpPublicKey(PublicKeyAlgorithmTag.EdDsa_Legacy, key, DateTime.Parse("2014-08-19 14:28:27Z"));
@@ -421,7 +421,7 @@ public void Version4Ed25519LegacyCreateTest()
         [Test]
         public void Version4Ed25519LegacySignatureSampleTest()
         {
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-sig
+            // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519lega
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v4Ed25519LegacyPubkeySample);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
@@ -446,7 +446,7 @@ public void Version4Ed25519LegacySignatureSampleTest()
         public void Version6CertificateParsingTest()
         {
             /*
-             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+             * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
              * A Transferable Public Key consisting of:
              *     A v6 Ed25519 Public-Key packet
              *     A v6 direct key self-signature
@@ -504,7 +504,7 @@ public void Version6PublicKeyCreationTest()
         {
             /* 
              * Create a v6 Ed25519 pubkey with the same key material and creation datetime as the test vector
-             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
+             * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
              * then check the fingerprint and verify a signature
             */
             byte[] keyMaterial = Hex.Decode("f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3");
@@ -533,7 +533,7 @@ public void Version6PublicKeyCreationTest()
         public void Version6UnlockedSecretKeyParsingTest()
         {
             /*
-             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
+             * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
              * A Transferable Secret Key consisting of:
              *     A v6 Ed25519 Secret-Key packet
              *     A v6 direct key self-signature
@@ -612,8 +612,8 @@ public void Version6Ed25519KeyPairCreationTest()
         {
             /* 
              * Create a v6 Ed25519 keypair with the same key material and creation datetime as the test vector
-             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
-             * then check the fingerprint and verify a signature
+             * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+             * then check the fingerprint and perform encode-decode, sign-verify, encrypt-decrypt roundtrips
              */
             byte[] keyMaterial = Hex.Decode("1972817b12be707e8d5f586ce61361201d344eb266a2c82fde6835762b65b0b7");
             Ed25519PrivateKeyParameters seckey = new Ed25519PrivateKeyParameters(keyMaterial);
@@ -718,13 +718,21 @@ public void Version6Ed25519KeyPairCreationTest()
                 false,
                 subKey.PublicKey,
                 subKey.ExtractPrivateKey(emptyPassphrase));
+
+            PgpPublicKeyRing pubring = keyRingGen.GeneratePublicKeyRing();
+            PgpPublicKey[] keys = pubring.GetPublicKeys().ToArray();
+            IsEquals(keys[0].Version, PublicKeyPacket.Version6);
+            IsEquals(keys[0].Algorithm, PublicKeyAlgorithmTag.Ed25519);
+            IsTrue("wrong master key fingerprint", AreEqual(keys[0].GetFingerprint(), expectedFingerprint));
+            IsEquals(keys[1].Version, PublicKeyPacket.Version6);
+            IsEquals(keys[1].Algorithm, PublicKeyAlgorithmTag.X25519);
         }
 
         [Test]
         public void Version6Ed448KeyPairCreationTest()
         {
             /* 
-             * Create a v6 Ed448 keypair, then perform encode-decode and sign-verify roundtrips
+             * Create a v6 Ed448 keypair, then perform encode-decode, sign-verify, encrypt-decrypt roundtrips
              */
             SecureRandom rand = new SecureRandom();
             DateTime now = DateTime.UtcNow;
@@ -804,13 +812,39 @@ public void Version6Ed448KeyPairCreationTest()
                 false,
                 subKey.PublicKey,
                 subKey.ExtractPrivateKey(emptyPassphrase));
+
+            PgpPublicKeyRing pubring = keyRingGen.GeneratePublicKeyRing();
+            PgpPublicKey[] keys = pubring.GetPublicKeys().ToArray();
+            IsEquals(keys[0].Version, PublicKeyPacket.Version6);
+            IsEquals(keys[0].Algorithm, PublicKeyAlgorithmTag.Ed448);
+            IsTrue("wrong master key fingerprint", AreEqual(keys[0].GetFingerprint(), fpr));
+            IsEquals(keys[1].Version, PublicKeyPacket.Version6);
+            IsEquals(keys[1].Algorithm, PublicKeyAlgorithmTag.X448);
+
+            using (var ms = new MemoryStream())
+            {
+                using (var arms = new ArmoredOutputStream(ms))
+                {
+                    decodedsecring.Encode(arms);
+                }
+                string armored = Encoding.ASCII.GetString(ms.ToArray());
+            }
+
+            using (var ms = new MemoryStream())
+            {
+                using (var arms = new ArmoredOutputStream(ms))
+                {
+                    pubring.Encode(arms);
+                }
+                string armored = Encoding.ASCII.GetString(ms.ToArray());
+            }
         }
 
         [Test]
         public void Version6LockedSecretKeyParsingTest()
         {
             /*
-             * https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-locked-v6-secret-key
+             * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
              * The same secret key as in Version6UnlockedSecretKeyParsingTest, but the secret key
              * material is locked with a passphrase using AEAD and Argon2.
              */
@@ -870,7 +904,7 @@ public void Version6LockedSecretKeyParsingTest()
         [Test]
         public void Version6SampleCleartextSignedMessageVerifySignatureTest()
         {
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-cleartext-signed-mes
+            // https://www.rfc-editor.org/rfc/rfc9580#name-sample-cleartext-signed-mes
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
@@ -889,7 +923,7 @@ public void Version6SampleCleartextSignedMessageVerifySignatureTest()
         [Test]
         public void Version6SampleInlineSignedMessageVerifySignatureTest()
         {
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag
+            // https://www.rfc-editor.org/rfc/rfc9580#name-sample-inline-signed-messag
             PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
             PgpPublicKey pubKey = pubRing.GetPublicKey();
 
@@ -945,9 +979,9 @@ public void Version6SkeskVersion2SeipdTest()
             // S2K type iterated+salted, using AES-128 with AEAD encryption.
             byte[][] messages = new byte[][]
             {
-                v6skesk_aes128_eax,   // from crypto-refresh A.9
-                v6skesk_aes128_ocb,   // from crypto-refresh A.10
-                v6skesk_aes128_gcm    // from crypto-refresh A.11
+                v6skesk_aes128_eax,   // from RFC 9580 A.9
+                v6skesk_aes128_ocb,   // from RFC 9580 A.10
+                v6skesk_aes128_gcm    // from RFC 9580 A.11
             };
 
             byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
@@ -1098,9 +1132,9 @@ public void SkeskWithArgon2Test()
         {
             byte[][] messages = new byte[][]
             {
-                v4skesk_argon2_aes128,    // from crypto-refresh A.12.1
-                v4skesk_argon2_aes192,    // from crypto-refresh A.12.2
-                v4skesk_argon2_aes256,    // from crypto-refresh A.12.3
+                v4skesk_argon2_aes128,    // from RFC 9580 A.12.1
+                v4skesk_argon2_aes192,    // from RFC 9580 A.12.2
+                v4skesk_argon2_aes256,    // from RFC 9580 A.12.3
                 v6skesk_argon2_aes256_ocb // generated with gosop 2.0.0-alpha
             };
 
@@ -1201,7 +1235,7 @@ public void PkeskTest()
              *  V6 PKESK + V2 SEIPD AEAD encrypted message that spans over 4 chunks
              *  (chunk size 512 octets)
              *  2000 octets of /dev/zero encrypted with sample V6 certificate from
-             *  crypto-refresh Appendix A.3 and AES-256 in OCB mode.
+             *  RFC 9580 Appendix A.3 and AES-256 in OCB mode.
              *  Generated with gosop 2.0.0-alpha
              *  Session key CFB73D46CF7C13B7535227BEDB5B2D8B4023C5B58289D19CF2C33B0DB388B0B6
              */
diff --git a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
index d021955669..f3791c6558 100644
--- a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
+++ b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
@@ -225,9 +225,9 @@ public class PgpInteroperabilityTestSuite
             "vDoQtVn8sArWqwEAi8HwbMhL+YwRItRZDknpC4vFjTHVMd1zMrz/JyeuT9k="
             );
 
-        // v6 Ed25519/X25519 key from crypto-refresh
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-certificate-trans
-        // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v6-secret-key-transf
+        // v6 Ed25519/X25519 key from RFC 9580
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+        // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-secret-key
         private static readonly byte[] v6Certificate = Base64.Decode(
             "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf" +
             "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy" +
@@ -351,7 +351,7 @@ public void MultiplePkeskTest()
         {
             // Encrypt-Decrypt roundtrip with multiple keys: the plaintext
             // "Hello World :)" is encrypted with the X25519 sample key from
-            // Appendix A.3 of crypto-refresh and the 'Alice' ECDH key from
+            // Appendix A.3 of RFC 9580 and the 'Alice' ECDH key from
             // "OpenPGP Example Keys and Certificates"
             byte[] message = Base64.Decode(
                 "wVQDEsg/HnBvYwgZaeKxsIieN+FvNLxmgMfRKJZGKt8vAa5BYX2k0QAetCMpCQbE" +
@@ -372,7 +372,7 @@ public void MultiplePkeskTest()
 
             IsEquals(2, encDataList.Count);
 
-            // decrypt with crypto-refresh sample X25519 key
+            // decrypt with RFC 9580 sample X25519 key
             var encData = encDataList[0] as PgpPublicKeyEncryptedData;
             FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
             PgpSecretKey secKey = bundle.GetSecretKey(encData.KeyId);
@@ -414,7 +414,7 @@ public void MultiplePkeskTest()
         public void MultipleInlineSignatureTest()
         {
             // Verify Inline Signature with multiple keys:
-            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
             // https://tests.sequoia-pgp.org/#Inline_Sign_with_minimal_key_from_RFC9760_and_key__Alice___verify_with_key_from_RFC9760
 
             // inline signed message generated by GopenPGP 3.0.0-alpha
@@ -452,7 +452,7 @@ public void MultipleInlineSignatureTest()
         public void GenerateAndVerifyMultipleInlineSignatureTest()
         {
             // Inline Sign-Verify roundtrip test with multiple keys:
-            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
             byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
             byte[] message;
 
@@ -535,7 +535,7 @@ private void VerifyMultipleDetachedSignaturesTest(byte[] signaturePacket, byte[]
         public void MultipleDetachedSignatureTest()
         {
             // Verify Detached Signature with multiple keys:
-            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
             // https://tests.sequoia-pgp.org/#Detached_Sign_with_minimal_key_from_RFC9760_and_key__Alice___verify_with_key_from_RFC9760
 
             byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
@@ -575,7 +575,7 @@ public void MultipleDetachedSignatureTest()
         public void GenerateAndVerifyMultipleDetachedSignatureTest()
         {
             // Inline Sign-Verify roundtrip test with multiple keys:
-            // v6 key from crypto-refresh and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+            // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
 
             byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
             byte[] corruptedData = Encoding.UTF8.GetBytes("Hello World :(");
@@ -729,7 +729,7 @@ public void SeipdVersion2WithMultipleMethodsTest()
             gen.AddMethod(v6);
 
             // Check constraint: An implementation MUST NOT generate ElGamal v6 PKESKs.
-            // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-algorithm-specific-fields-fo
+            // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-fo
             Assert.Throws<PgpException>(() =>
             {
                 gen.AddMethod(carol);

From a1496efc6b3f9d945730a4047e5554c1b75fed84 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sat, 28 Sep 2024 18:44:09 +0200
Subject: [PATCH 29/37] Feature flag for Version 2 SEIPD

---
 crypto/src/bcpg/sig/Features.cs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/crypto/src/bcpg/sig/Features.cs b/crypto/src/bcpg/sig/Features.cs
index a04d2cf9da..df80bde32e 100644
--- a/crypto/src/bcpg/sig/Features.cs
+++ b/crypto/src/bcpg/sig/Features.cs
@@ -19,6 +19,10 @@ public class Features
            fingerprint format */
         public static readonly byte FEATURE_VERSION_5_PUBLIC_KEY = 0x04;
 
+        /** Identifier for the Version 2 Symmetrically Encrypted and Integrity Protected
+            Data packet */
+        public static readonly byte FEATURE_VERSION_2_SEIPD = 0x08;
+
         private static byte[] FeatureToByteArray(byte feature)
         {
             return new byte[1]{ feature };

From 28db83ebbb2cfb1a7c6005b800a04c3673283a8a Mon Sep 17 00:00:00 2001
From: Peter Dettman <peter.dettman@bouncycastle.org>
Date: Wed, 2 Oct 2024 23:44:48 +0700
Subject: [PATCH 30/37] Followup changes to Argon2 PR

---
 crypto/src/crypto/ICharToByteConverter.cs     |  37 +--
 crypto/src/crypto/PasswordConverter.cs        |  54 ++--
 .../crypto/generators/Argon2BytesGenerator.cs | 305 +++++++-----------
 .../src/crypto/parameters/Argon2Parameters.cs | 142 +++-----
 crypto/test/src/crypto/test/Argon2Test.cs     | 217 ++++++-------
 5 files changed, 284 insertions(+), 471 deletions(-)

diff --git a/crypto/src/crypto/ICharToByteConverter.cs b/crypto/src/crypto/ICharToByteConverter.cs
index 94e0279e98..2f88ee61a8 100644
--- a/crypto/src/crypto/ICharToByteConverter.cs
+++ b/crypto/src/crypto/ICharToByteConverter.cs
@@ -1,35 +1,16 @@
 namespace Org.BouncyCastle.Crypto
 {
+    /// <summary>
+    /// Interface for a converter that produces a byte encoding for a char array.
+    /// </summary>
     public interface ICharToByteConverter
     {
-        /**
-         * Return the type of the conversion.
-         *
-         * @return a type name for the conversion.
-         */
-        string GetName();
+        /// <summary>The name of the conversion.</summary>
+        string Name { get; }
 
-        /**
-         * Return a byte encoded representation of the passed in password.
-         *
-         * @param password the characters to encode.
-         * @return a byte encoding of password.
-         */
+        /// <summary>Return a byte encoded representation of the passed in password.</summary>
+        /// <param name="password">the characters to encode.</param>
+        /// <return>a byte encoding of password.</return>
         byte[] Convert(char[] password);
     }
-
-    public static class CharToByteConverterExtensions
-    {
-
-        /**
-         * Return a byte encoded representation of the passed in password.
-         *
-         * @param password the string to encode.
-         * @return a byte encoding of password.
-         */
-        public static byte[] Convert(this ICharToByteConverter converter, string password)
-        {
-            return converter.Convert(password.ToCharArray());
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/PasswordConverter.cs b/crypto/src/crypto/PasswordConverter.cs
index c2786152c0..0e78a57d27 100644
--- a/crypto/src/crypto/PasswordConverter.cs
+++ b/crypto/src/crypto/PasswordConverter.cs
@@ -1,44 +1,36 @@
-using System;
-using System.Text;
-
-namespace Org.BouncyCastle.Crypto
+namespace Org.BouncyCastle.Crypto
 {
-    public class PasswordConverter
+    /// <summary>
+    /// Standard char[] to byte[] converters for password based derivation algorithms.
+    /// </summary>
+    public sealed class PasswordConverter
         : ICharToByteConverter
     {
-        private readonly string name;
-        private readonly Func<char[], byte[]> converterFunction;
-
-        public PasswordConverter(string name, Func<char[], byte[]> converterFunction)
-        {
-            this.name = name;
-            this.converterFunction = converterFunction;
-        }
+        private delegate byte[] ConverterFunction(char[] password);
 
-        public byte[] Convert(char[] password)
-        {
-            return converterFunction.Invoke(password);
-        }
+        private readonly string m_name;
+        private readonly ConverterFunction m_converterFunction;
 
-        public string GetName()
+        private PasswordConverter(string name, ConverterFunction converterFunction)
         {
-            return name;
+            m_name = name;
+            m_converterFunction = converterFunction;
         }
 
-        public readonly static ICharToByteConverter ASCII = new PasswordConverter("ASCII", PbeParametersGenerator.Pkcs5PasswordToBytes);
-
-        public readonly static ICharToByteConverter UTF8 = new PasswordConverter("UTF8", PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes);
-
-        public readonly static ICharToByteConverter PKCS12 = new PasswordConverter("PKCS12", PbeParametersGenerator.Pkcs12PasswordToBytes);
+        public byte[] Convert(char[] password) => m_converterFunction(password);
 
-        public readonly static ICharToByteConverter UTF32 = new PasswordConverter("UTF32", Encoding.UTF32.GetBytes);
+        public string Name => m_name;
 
-        public readonly static ICharToByteConverter Unicode = new PasswordConverter("Unicode", Encoding.Unicode.GetBytes);
+        /// <summary>Do a straight char[] to 8 bit conversion.</summary>
+        public readonly static ICharToByteConverter Ascii = new PasswordConverter("ASCII",
+            PbeParametersGenerator.Pkcs5PasswordToBytes);
 
-        public readonly static ICharToByteConverter BigEndianUnicode = new PasswordConverter("BigEndianUnicode", Encoding.BigEndianUnicode.GetBytes);
+        /// <summary>Do a char[] conversion by producing UTF-8 data.</summary>
+        public readonly static ICharToByteConverter Utf8 = new PasswordConverter("UTF8",
+            PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes);
 
-#if NET6_0_OR_GREATER
-        public readonly static ICharToByteConverter Latin1 = new PasswordConverter("Latin1", Encoding.Latin1.GetBytes);
-#endif
+        /// <summary>Do char[] to BMP conversion (i.e. 2 bytes per character).</summary>
+        public readonly static ICharToByteConverter Pkcs12 = new PasswordConverter("PKCS12",
+            PbeParametersGenerator.Pkcs12PasswordToBytes);
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/generators/Argon2BytesGenerator.cs b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
index 3fb0ea7f9f..3800ffb129 100644
--- a/crypto/src/crypto/generators/Argon2BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
@@ -1,36 +1,38 @@
-using Org.BouncyCastle.Crypto.Digests;
+using System;
+
+using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math.Raw;
 using Org.BouncyCastle.Utilities;
-using System;
 
 namespace Org.BouncyCastle.Crypto.Generators
 {
     public sealed class Argon2BytesGenerator
     {
-        private const int ARGON2_BLOCK_SIZE = 1024;
-        private const int ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8;
+        private const int Argon2BlockSize = 1024;
+        private const int Argon2QwordsInBlock = Argon2BlockSize / 8;
 
-        private const int ARGON2_ADDRESSES_IN_BLOCK = 128;
+        private const int Argon2AddressesInBlock = 128;
 
-        private const int ARGON2_PREHASH_DIGEST_LENGTH = 64;
-        private const int ARGON2_PREHASH_SEED_LENGTH = 72;
+        private const int Argon2PrehashDigestLength = 64;
+        private const int Argon2PrehashSeedLength = 72;
 
-        private const int ARGON2_SYNC_POINTS = 4;
+        private const int Argon2SyncPoints = 4;
 
         /* Minimum and maximum number of lanes (degree of parallelism) */
-        private const int MIN_PARALLELISM = 1;
-        private const int MAX_PARALLELISM = 16777216;
+        private const int MinParallelism = 1;
+        private const int MaxParallelism = 16777216;
 
         /* Minimum and maximum digest size in bytes */
-        private const int MIN_OUTLEN = 4;
+        private const int MinOutlen = 4;
 
         /* Minimum and maximum number of passes */
-        private const int MIN_ITERATIONS = 1;
+        private const int MinIterations = 1;
 
-        private const long M32L = 0xFFFFFFFFL;
+        private const ulong M32L = 0xFFFFFFFFL;
 
-        private readonly byte[] ZERO_BYTES = new byte[4];
+        private readonly byte[] ZeroBytes = new byte[4];
 
         private Argon2Parameters parameters;
         private Block[] memory;
@@ -50,59 +52,32 @@ public void Init(Argon2Parameters parameters)
         {
             this.parameters = parameters;
 
-            if (parameters.GetLanes() < MIN_PARALLELISM)
-            {
-                throw new InvalidOperationException($"lanes must be greater than " + MIN_PARALLELISM);
-            }
-            else if (parameters.GetLanes() > MAX_PARALLELISM)
-            {
-                throw new InvalidOperationException("lanes must be less than " + MAX_PARALLELISM);
-            }
-            else if (parameters.GetMemory() < 2 * parameters.GetLanes())
-            {
-                throw new InvalidOperationException("memory is less than: " + (2 * parameters.GetLanes()) + " expected " + (2 * parameters.GetLanes()));
-            }
-            else if (parameters.GetIterations() < MIN_ITERATIONS)
-            {
-                throw new InvalidOperationException("iterations is less than: " + MIN_ITERATIONS);
-            }
+            if (parameters.Lanes < MinParallelism)
+                throw new InvalidOperationException($"lanes must be greater than " + MinParallelism);
+            if (parameters.Lanes > MaxParallelism)
+                throw new InvalidOperationException("lanes must be less than " + MaxParallelism);
+            if (parameters.Memory < 2 * parameters.Lanes)
+                throw new InvalidOperationException("memory is less than: " + (2 * parameters.Lanes) + " expected " + (2 * parameters.Lanes));
+            if (parameters.Iterations < MinIterations)
+                throw new InvalidOperationException("iterations is less than: " + MinIterations);
 
             DoInit(parameters);
         }
 
-        public int GenerateBytes(string password, byte[] output)
-        {
-            return GenerateBytes(password.ToCharArray(), output);
-        }
+        public int GenerateBytes(char[] password, byte[] output) =>
+            GenerateBytes(parameters.CharToByteConverter.Convert(password), output);
 
-        public int GenerateBytes(string password, byte[] output, int outOff, int outLen)
-        {
-            return GenerateBytes(password.ToCharArray(), output, outOff, outLen);
-        }
+        public int GenerateBytes(char[] password, byte[] output, int outOff, int outLen) =>
+            GenerateBytes(parameters.CharToByteConverter.Convert(password), output, outOff, outLen);
 
-        public int GenerateBytes(char[] password, byte[] output)
-        {
-            return GenerateBytes(parameters.GetCharToByteConverter().Convert(password), output);
-        }
-
-        public int GenerateBytes(char[] password, byte[] output, int outOff, int outLen)
-        {
-            return GenerateBytes(parameters.GetCharToByteConverter().Convert(password), output, outOff, outLen);
-        }
-
-        public int GenerateBytes(byte[] password, byte[] output)
-        {
-            return GenerateBytes(password, output, 0, output.Length);
-        }
+        public int GenerateBytes(byte[] password, byte[] output) => GenerateBytes(password, output, 0, output.Length);
 
         public int GenerateBytes(byte[] password, byte[] output, int outOff, int outLen)
         {
-            if (outLen < MIN_OUTLEN)
-            {
-                throw new InvalidOperationException("output length less than " + MIN_OUTLEN);
-            }
+            if (outLen < MinOutlen)
+                throw new InvalidOperationException("output length less than " + MinOutlen);
 
-            byte[] tmpBlockBytes = new byte[ARGON2_BLOCK_SIZE];
+            byte[] tmpBlockBytes = new byte[Argon2BlockSize];
 
             Initialize(tmpBlockBytes, password, outLen);
             FillMemoryBlocks();
@@ -131,18 +106,13 @@ private void DoInit(Argon2Parameters parameters)
         {
             /* 2. Align memory size */
             /* Minimum memoryBlocks = 8L blocks, where L is the number of lanes */
-            int memoryBlocks = parameters.GetMemory();
-
-            if (memoryBlocks < 2 * ARGON2_SYNC_POINTS * parameters.GetLanes())
-            {
-                memoryBlocks = 2 * ARGON2_SYNC_POINTS * parameters.GetLanes();
-            }
+            int memoryBlocks = System.Math.Max(parameters.Memory, 2 * Argon2SyncPoints * parameters.Lanes);
 
-            this.segmentLength = memoryBlocks / (parameters.GetLanes() * ARGON2_SYNC_POINTS);
-            this.laneLength = segmentLength * ARGON2_SYNC_POINTS;
+            this.segmentLength = memoryBlocks / (parameters.Lanes * Argon2SyncPoints);
+            this.laneLength = segmentLength * Argon2SyncPoints;
 
             /* Ensure that all segments have equal length */
-            memoryBlocks = segmentLength * (parameters.GetLanes() * ARGON2_SYNC_POINTS);
+            memoryBlocks = segmentLength * (parameters.Lanes * Argon2SyncPoints);
 
             InitMemory(memoryBlocks);
         }
@@ -161,15 +131,15 @@ private void FillMemoryBlocks()
         {
             FillBlock filler = new FillBlock();
             Position position = new Position();
-            for (int pass = 0; pass < parameters.GetIterations(); ++pass)
+            for (int pass = 0; pass < parameters.Iterations; ++pass)
             {
                 position.pass = pass;
 
-                for (int slice = 0; slice < ARGON2_SYNC_POINTS; ++slice)
+                for (int slice = 0; slice < Argon2SyncPoints; ++slice)
                 {
                     position.slice = slice;
 
-                    for (int lane = 0; lane < parameters.GetLanes(); ++lane)
+                    for (int lane = 0; lane < parameters.Lanes; ++lane)
                     {
                         position.lane = lane;
 
@@ -200,7 +170,7 @@ private void FillSegment(FillBlock filler, Position position)
 
             for (int index = startingIndex; index < segmentLength; ++index)
             {
-                long pseudoRandom = GetPseudoRandom(
+                ulong pseudoRandom = GetPseudoRandom(
                     filler,
                     index,
                     addressBlock,
@@ -232,21 +202,21 @@ private void FillSegment(FillBlock filler, Position position)
 
         private bool IsDataIndependentAddressing(Position position)
         {
-            return (parameters.GetArgonType() == Argon2Parameters.ARGON2_i) ||
-                (parameters.GetArgonType() == Argon2Parameters.ARGON2_id
+            return (parameters.Type == Argon2Parameters.Argon2_i) ||
+                (parameters.Type == Argon2Parameters.Argon2_id
                     && (position.pass == 0)
-                    && (position.slice < ARGON2_SYNC_POINTS / 2)
+                    && (position.slice < Argon2SyncPoints / 2)
                 );
         }
 
         private void InitAddressBlocks(FillBlock filler, Position position, Block inputBlock, Block addressBlock)
         {
-            inputBlock.v[0] = (long)position.pass;
-            inputBlock.v[1] = (long)position.lane;
-            inputBlock.v[2] = (long)position.slice;
-            inputBlock.v[3] = (long)memory.Length;
-            inputBlock.v[4] = (long)parameters.GetIterations();
-            inputBlock.v[5] = (long)parameters.GetArgonType();
+            inputBlock.v[0] = (ulong)position.pass;
+            inputBlock.v[1] = (ulong)position.lane;
+            inputBlock.v[2] = (ulong)position.slice;
+            inputBlock.v[3] = (ulong)memory.Length;
+            inputBlock.v[4] = (ulong)parameters.Iterations;
+            inputBlock.v[5] = (ulong)parameters.Type;
 
             if ((position.pass == 0) && (position.slice == 0))
             {
@@ -257,7 +227,7 @@ private void InitAddressBlocks(FillBlock filler, Position position, Block inputB
 
         private bool IsWithXor(Position position)
         {
-            return !(position.pass == 0 || parameters.GetVersion() == Argon2Parameters.ARGON2_VERSION_10);
+            return !(position.pass == 0 || parameters.Version == Argon2Parameters.Argon2_Version10);
         }
 
         private int GetPrevOffset(int currentOffset)
@@ -295,7 +265,7 @@ private static void NextAddresses(FillBlock filler, Block inputBlock, Block addr
 
         /* 1.2 Computing the index of the reference block */
         /* 1.2.1 Taking pseudo-random value from the previous block */
-        private long GetPseudoRandom(
+        private ulong GetPseudoRandom(
             FillBlock filler,
             int index,
             Block addressBlock,
@@ -305,7 +275,7 @@ private long GetPseudoRandom(
         {
             if (dataIndependentAddressing)
             {
-                int addressIndex = index % ARGON2_ADDRESSES_IN_BLOCK;
+                int addressIndex = index % Argon2AddressesInBlock;
                 if (addressIndex == 0)
                 {
                     NextAddresses(filler, inputBlock, addressBlock);
@@ -318,11 +288,9 @@ private long GetPseudoRandom(
             }
         }
 
-        private int GetRefLane(Position position, long pseudoRandom)
+        private int GetRefLane(Position position, ulong pseudoRandom)
         {
-            // Double-casting to/from ulong required because unsigned right shift operator
-            // >>> is supported only in C# 11 (.NET 7 or greater)
-            int refLane = (int)(((ulong)pseudoRandom >> 32) % (ulong)parameters.GetLanes());
+            int refLane = (int)((long)(pseudoRandom >> 32) % parameters.Lanes);
 
             if ((position.pass == 0) && (position.slice == 0))
             {
@@ -332,10 +300,10 @@ private int GetRefLane(Position position, long pseudoRandom)
             return refLane;
         }
 
-        private int GetRefColumn(Position position, int index, long pseudoRandom, bool sameLane)
+        private int GetRefColumn(Position position, int index, ulong pseudoRandom, bool sameLane)
         {
-            long referenceAreaSize;
-            long startPosition;
+            ulong referenceAreaSize;
+            ulong startPosition;
 
             if (position.pass == 0)
             {
@@ -344,33 +312,31 @@ private int GetRefColumn(Position position, int index, long pseudoRandom, bool s
                 if (sameLane)
                 {
                     /* The same lane => add current segment */
-                    referenceAreaSize = position.slice * segmentLength + index - 1;
+                    referenceAreaSize = (ulong)(position.slice * segmentLength + index - 1);
                 }
                 else
                 {
                     /* pass == 0 && !sameLane => position.slice > 0*/
-                    referenceAreaSize = position.slice * segmentLength + ((index == 0) ? (-1) : 0);
+                    referenceAreaSize = (ulong)(position.slice * segmentLength + ((index == 0) ? (-1) : 0));
                 }
             }
             else
             {
-                startPosition = ((position.slice + 1) * segmentLength) % laneLength;
+                startPosition = (ulong)(((position.slice + 1) * segmentLength) % laneLength);
 
                 if (sameLane)
                 {
-                    referenceAreaSize = laneLength - segmentLength + index - 1;
+                    referenceAreaSize = (ulong)(laneLength - segmentLength + index - 1);
                 }
                 else
                 {
-                    referenceAreaSize = laneLength - segmentLength + ((index == 0) ? (-1) : 0);
+                    referenceAreaSize = (ulong)(laneLength - segmentLength + ((index == 0) ? (-1) : 0));
                 }
             }
 
-            long relativePosition = pseudoRandom & 0xFFFFFFFFL;
+            ulong relativePosition = pseudoRandom & 0xFFFFFFFFUL;
 
-            // Double-casting to/from ulong required because unsigned right shift operator
-            // >>> is supported only in C# 11 (.NET 7 or greater)
-            relativePosition = (long)((ulong)(relativePosition * relativePosition) >> 32);
+            relativePosition = (relativePosition * relativePosition) >> 32;
             relativePosition = referenceAreaSize - 1 - ((referenceAreaSize * relativePosition) >> 32);
 
             return (int)(startPosition + relativePosition) % laneLength;
@@ -381,7 +347,7 @@ private void Digest(byte[] tmpBlockBytes, byte[] output, int outOff, int outLen)
             Block finalBlock = memory[laneLength - 1];
 
             /* XOR the last blocks */
-            for (int i = 1; i < parameters.GetLanes(); i++)
+            for (int i = 1; i < parameters.Lanes; i++)
             {
                 int lastBlockInLane = i * laneLength + (laneLength - 1);
                 finalBlock.XorWith(memory[lastBlockInLane]);
@@ -451,7 +417,7 @@ private static void RoundFunction(Block block,
                                           int v8, int v9, int v10, int v11,
                                           int v12, int v13, int v14, int v15)
         {
-            long[] v = block.v;
+            ulong[] v = block.v;
 
             F(v, v0, v4, v8, v12);
             F(v, v1, v5, v9, v13);
@@ -464,7 +430,7 @@ private static void RoundFunction(Block block,
             F(v, v3, v4, v9, v14);
         }
 
-        private static void F(long[] v, int a, int b, int c, int d)
+        private static void F(ulong[] v, int a, int b, int c, int d)
         {
             QuarterRound(v, a, b, d, 32);
             QuarterRound(v, c, d, b, 24);
@@ -472,12 +438,12 @@ private static void F(long[] v, int a, int b, int c, int d)
             QuarterRound(v, c, d, b, 63);
         }
 
-        private static void QuarterRound(long[] v, int x, int y, int z, int s)
+        private static void QuarterRound(ulong[] v, int x, int y, int z, int s)
         {
             //        fBlaMka(v, x, y);
             //        rotr64(v, z, x, s);
 
-            long a = v[x], b = v[y], c = v[z];
+            ulong a = v[x], b = v[y], c = v[z];
 
             a += b + 2 * (a & M32L) * (b & M32L);
             c = Longs.RotateRight(c ^ a, s);
@@ -492,44 +458,44 @@ private static void QuarterRound(long[] v, int x, int y, int z, int s)
          * aL = least 32 bit */
         //    private static void fBlaMka(long[] v, int x, int y)
         //    {
-        //        final long a = v[x], b = v[y];
-        //        final long ab = (a & M32L) * (b & M32L);
+        //        final ulong a = v[x], b = v[y];
+        //        final ulong ab = (a & M32L) * (b & M32L);
         //
         //        v[x] = a + b + 2 * ab;
         //    }
         //
         //    private static void rotr64(long[] v, int x, int y, int s)
         //    {
-        //        v[x] = Longs.rotateRight(v[x] ^ v[y], s);
+        //        v[x] = ulongs.rotateRight(v[x] ^ v[y], s);
         //    }
 
         private void Initialize(byte[] tmpBlockBytes, byte[] password, int outputLength)
         {
             /*
              * H0 = H64(p, τ, m, t, v, y, |P|, P, |S|, S, |L|, K, |X|, X)
-             * -> 64 byte (ARGON2_PREHASH_DIGEST_LENGTH)
+             * -> 64 byte (Argon2PrehashDigestLength)
              */
 
-            Blake2bDigest blake = new Blake2bDigest(ARGON2_PREHASH_DIGEST_LENGTH * 8);
+            Blake2bDigest blake = new Blake2bDigest(Argon2PrehashDigestLength * 8);
 
-            int[] values = {
-                parameters.GetLanes(),
-                outputLength,
-                parameters.GetMemory(),
-                parameters.GetIterations(),
-                parameters.GetVersion(),
-                parameters.GetArgonType()
+            uint[] values = {
+                (uint)parameters.Lanes,
+                (uint)outputLength,
+                (uint)parameters.Memory,
+                (uint)parameters.Iterations,
+                (uint)parameters.Version,
+                (uint)parameters.Type
             };
 
-            Helpers.IntArrayToLittleEndian(values, tmpBlockBytes, 0);
+            Pack.UInt32_To_LE(values, tmpBlockBytes, 0);
             blake.BlockUpdate(tmpBlockBytes, 0, values.Length * 4);
 
             AddByteString(tmpBlockBytes, blake, password);
-            AddByteString(tmpBlockBytes, blake, parameters.GetSalt());
-            AddByteString(tmpBlockBytes, blake, parameters.GetSecret());
-            AddByteString(tmpBlockBytes, blake, parameters.GetAdditional());
+            AddByteString(tmpBlockBytes, blake, parameters.Salt);
+            AddByteString(tmpBlockBytes, blake, parameters.Secret);
+            AddByteString(tmpBlockBytes, blake, parameters.Additional);
 
-            byte[] initialHashWithZeros = new byte[ARGON2_PREHASH_SEED_LENGTH];
+            byte[] initialHashWithZeros = new byte[Argon2PrehashSeedLength];
             blake.DoFinal(initialHashWithZeros, 0);
 
             FillFirstBlocks(tmpBlockBytes, initialHashWithZeros);
@@ -539,7 +505,7 @@ private void AddByteString(byte[] tmpBlockBytes, IDigest digest, byte[] octets)
         {
             if (null == octets)
             {
-                digest.BlockUpdate(ZERO_BYTES, 0, 4);
+                digest.BlockUpdate(ZeroBytes, 0, 4);
                 return;
             }
 
@@ -554,20 +520,20 @@ private void AddByteString(byte[] tmpBlockBytes, IDigest digest, byte[] octets)
          */
         private void FillFirstBlocks(byte[] tmpBlockBytes, byte[] initialHashWithZeros)
         {
-            byte[] initialHashWithOnes = new byte[ARGON2_PREHASH_SEED_LENGTH];
-            Array.Copy(initialHashWithZeros, 0, initialHashWithOnes, 0, ARGON2_PREHASH_DIGEST_LENGTH);
-            //        Pack.intToLittleEndian(1, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH);
-            initialHashWithOnes[ARGON2_PREHASH_DIGEST_LENGTH] = 1;
+            byte[] initialHashWithOnes = new byte[Argon2PrehashSeedLength];
+            Array.Copy(initialHashWithZeros, 0, initialHashWithOnes, 0, Argon2PrehashDigestLength);
+            //        Pack.intToLittleEndian(1, initialHashWithOnes, Argon2PrehashDigestLength);
+            initialHashWithOnes[Argon2PrehashDigestLength] = 1;
 
-            for (int i = 0; i < parameters.GetLanes(); i++)
+            for (int i = 0; i < parameters.Lanes; i++)
             {
-                Pack.UInt32_To_LE((uint)i, initialHashWithZeros, ARGON2_PREHASH_DIGEST_LENGTH + 4);
-                Pack.UInt32_To_LE((uint)i, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH + 4);
+                Pack.UInt32_To_LE((uint)i, initialHashWithZeros, Argon2PrehashDigestLength + 4);
+                Pack.UInt32_To_LE((uint)i, initialHashWithOnes, Argon2PrehashDigestLength + 4);
 
-                Hash(initialHashWithZeros, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE);
+                Hash(initialHashWithZeros, tmpBlockBytes, 0, Argon2BlockSize);
                 memory[i * laneLength + 0].FromBytes(tmpBlockBytes);
 
-                Hash(initialHashWithOnes, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE);
+                Hash(initialHashWithOnes, tmpBlockBytes, 0, Argon2BlockSize);
                 memory[i * laneLength + 1].FromBytes(tmpBlockBytes);
             }
         }
@@ -641,70 +607,54 @@ internal void FillBlockWithXor(Block X, Block Y, Block currentBlock)
 
         private sealed class Block
         {
-            private const int SIZE = ARGON2_QWORDS_IN_BLOCK;
+            private const int Size = Argon2QwordsInBlock;
 
             /* 128 * 8 Byte QWords */
-            internal readonly long[] v;
+            internal readonly ulong[] v;
 
             internal Block()
             {
-                v = new long[SIZE];
+                v = new ulong[Size];
             }
 
             internal void FromBytes(byte[] input)
             {
-                if (input.Length < ARGON2_BLOCK_SIZE)
-                {
+                if (input.Length < Argon2BlockSize)
                     throw new ArgumentException("input shorter than blocksize");
-                }
 
-                Helpers.LittleEndianToLongArray(input, 0, v);
+                Pack.LE_To_UInt64(input, 0, v);
             }
 
-           internal void ToBytes(byte[] output)
+            internal void ToBytes(byte[] output)
             {
-                if (output.Length < ARGON2_BLOCK_SIZE)
-                {
+                if (output.Length < Argon2BlockSize)
                     throw new ArgumentException("output shorter than blocksize");
-                }
 
-                Helpers.LongArrayToLittleEndian(v, output, 0);
+                Pack.UInt64_To_LE(v, output, 0);
             }
 
             internal void CopyBlock(Block other)
             {
-                Array.Copy(other.v, 0, v, 0, SIZE);
+                Array.Copy(other.v, 0, v, 0, Size);
             }
 
             internal void Xor(Block b1, Block b2)
             {
-                long[] v0 = v;
-                long[] v1 = b1.v;
-                long[] v2 = b2.v;
-
-                for (int i = 0; i < SIZE; i++)
-                {
-                    v0[i] = v1[i] ^ v2[i];
-                }
+                Nat.Xor64(Size, b1.v, b2.v, v);
             }
 
             internal void XorWith(Block b1)
             {
-                long[] v0 = v;
-                long[] v1 = b1.v;
-
-                for (int i = 0; i < SIZE; i++)
-                {
-                    v0[i] ^= v1[i];
-                }
+                Nat.XorTo64(Size, b1.v, v);
             }
 
             internal void XorWith(Block b1, Block b2)
             {
-                long[] v0 = v;
-                long[] v1 = b1.v;
-                long[] v2 = b2.v;
-                for (int i = 0; i < SIZE; i++)
+                // TODO New Nat.Xor variant for this
+                ulong[] v0 = v;
+                ulong[] v1 = b1.v;
+                ulong[] v2 = b2.v;
+                for (int i = 0; i < Size; i++)
                 {
                     v0[i] ^= v1[i] ^ v2[i];
                 }
@@ -727,36 +677,5 @@ internal Position()
             {
             }
         }
-
-        private static class Helpers
-        {
-            internal static void LittleEndianToLongArray(byte[] bs, int off, long[] ns)
-            {
-                for (int i = 0; i < ns.Length; ++i)
-                {
-                    ns[i] = (long)Pack.LE_To_UInt64(bs, off);
-                    off += 8;
-                }
-            }
-
-            internal static void LongArrayToLittleEndian(long[] ns, byte[] bs, int off)
-            {
-                for (int i = 0; i < ns.Length; ++i)
-                {
-                    Pack.UInt64_To_LE((ulong)ns[i], bs, off);
-                    off += 8;
-                }
-            }
-
-            internal static void IntArrayToLittleEndian(int[] ns, byte[] bs, int off)
-            {
-                for (int i = 0; i < ns.Length; ++i)
-                {
-                    Pack.UInt32_To_LE((uint)ns[i], bs, off);
-                    off += 4;
-                }
-            }
-
-        }
     }
 }
diff --git a/crypto/src/crypto/parameters/Argon2Parameters.cs b/crypto/src/crypto/parameters/Argon2Parameters.cs
index f514d862e1..4da2afca0b 100644
--- a/crypto/src/crypto/parameters/Argon2Parameters.cs
+++ b/crypto/src/crypto/parameters/Argon2Parameters.cs
@@ -1,67 +1,60 @@
-using Org.BouncyCastle.Utilities;
-using System;
+using System;
+
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Parameters
 {
     public sealed class Argon2Parameters
     {
-        public const int ARGON2_d = 0x00;
-        public const int ARGON2_i = 0x01;
-        public const int ARGON2_id = 0x02;
+        public static readonly int Argon2_d = 0x00;
+        public static readonly int Argon2_i = 0x01;
+        public static readonly int Argon2_id = 0x02;
 
-        public const int ARGON2_VERSION_10 = 0x10;
-        public const int ARGON2_VERSION_13 = 0x13;
+        public static readonly int Argon2_Version10 = 0x10;
+        public static readonly int Argon2_Version13 = 0x13;
 
+        private readonly int type;
         private readonly byte[] salt;
         private readonly byte[] secret;
         private readonly byte[] additional;
-
         private readonly int iterations;
         private readonly int memory;
         private readonly int lanes;
-
         private readonly int version;
-        private readonly int type;
         private readonly ICharToByteConverter converter;
 
-        public class Builder
+        public sealed class Builder
         {
-            private const int DEFAULT_ITERATIONS = 3;
-            private const int DEFAULT_MEMORY_COST = 12;
-            private const int DEFAULT_LANES = 1;
-            private const int DEFAULT_TYPE = ARGON2_i;
-            private const int DEFAULT_VERSION = ARGON2_VERSION_13;
+            private static readonly int DefaultIterations = 3;
+            private static readonly int DefaultMemoryCost = 12;
+            private static readonly int DefaultLanes = 1;
+            private static readonly int DefaultType = Argon2_i;
+            private static readonly int DefaultVersion = Argon2_Version13;
+
+            private readonly int type;
 
             private byte[] salt = Array.Empty<byte>();
             private byte[] secret = Array.Empty<byte>();
             private byte[] additional = Array.Empty<byte>();
-
-            private int iterations;
-            private int memory;
-            private int lanes;
-
-            private int version;
-            private readonly int type;
-
-            private ICharToByteConverter converter = PasswordConverter.UTF8;
+            private int iterations = DefaultIterations;
+            private int memory = 1 << DefaultMemoryCost;
+            private int lanes = DefaultLanes;
+            private int version = DefaultVersion;
+            private ICharToByteConverter converter = PasswordConverter.Utf8;
 
             public Builder()
-                : this(DEFAULT_TYPE)
+                : this(DefaultType)
             {
             }
 
             public Builder(int type)
             {
                 this.type = type;
-                lanes = DEFAULT_LANES;
-                memory = 1 << DEFAULT_MEMORY_COST;
-                iterations = DEFAULT_ITERATIONS;
-                version = DEFAULT_VERSION;
             }
 
             public Builder WithParallelism(int parallelism)
             {
-                lanes = parallelism;
+                this.lanes = parallelism;
                 return this;
             }
 
@@ -113,20 +106,8 @@ public Builder WithCharToByteConverter(ICharToByteConverter converter)
                 return this;
             }
 
-            public Builder WithCharToByteConverter(string name, Func<char[], byte[]> converterFunction)
-            {
-                return WithCharToByteConverter(new PasswordConverter(name, converterFunction));
-            }
-
-            public Builder WithCharToByteConverter(Func<char[], byte[]> converterFunction)
-            {
-                return WithCharToByteConverter("Custom", converterFunction);
-            }
-
-            public Argon2Parameters Build()
-            {
-                return new Argon2Parameters(type, salt, secret, additional, iterations, memory, lanes, version, converter);
-            }
+            public Argon2Parameters Build() =>
+                new Argon2Parameters(type, salt, secret, additional, iterations, memory, lanes, version, converter);
 
             public void Clear()
             {
@@ -136,73 +117,42 @@ public void Clear()
             }
         }
 
-        private Argon2Parameters(
-            int type,
-            byte[] salt,
-            byte[] secret,
-            byte[] additional,
-            int iterations,
-            int memory,
-            int lanes,
-            int version,
-            ICharToByteConverter converter)
+        private Argon2Parameters(int type, byte[] salt, byte[] secret, byte[] additional, int iterations, int memory,
+            int lanes, int version, ICharToByteConverter converter)
         {
-
-            this.salt = Arrays.Clone(salt);
-            this.secret = Arrays.Clone(secret);
-            this.additional = Arrays.Clone(additional);
+            this.type = type;
+            this.salt = salt;
+            this.secret = secret;
+            this.additional = additional;
             this.iterations = iterations;
             this.memory = memory;
             this.lanes = lanes;
             this.version = version;
-            this.type = type;
             this.converter = converter;
         }
 
-        public byte[] GetSalt()
-        {
-            return Arrays.Clone(salt);
-        }
+        public ICharToByteConverter CharToByteConverter => converter;
 
-        public byte[] GetSecret()
-        {
-            return Arrays.Clone(secret);
-        }
+        public byte[] GetSalt() => Arrays.Clone(salt);
 
-        public byte[] GetAdditional()
-        {
-            return Arrays.Clone(additional);
-        }
+        public byte[] GetSecret() => Arrays.Clone(secret);
 
-        public int GetIterations()
-        {
-            return iterations;
-        }
+        public byte[] GetAdditional() => Arrays.Clone(additional);
 
-        public int GetMemory()
-        {
-            return memory;
-        }
+        public int Iterations => iterations;
 
-        public int GetLanes()
-        {
-            return lanes;
-        }
+        public int Lanes => lanes;
 
-        public int GetVersion()
-        {
-            return version;
-        }
+        public int Memory => memory;
 
-        public int GetArgonType()
-        {
-            return type;
-        }
+        public int Type => type;
 
-        public ICharToByteConverter GetCharToByteConverter()
-        {
-            return converter;
-        }
+        public int Version => version;
+
+        internal byte[] Additional => additional;
+
+        internal byte[] Salt => salt;
 
+        internal byte[] Secret => secret;
     }
 }
diff --git a/crypto/test/src/crypto/test/Argon2Test.cs b/crypto/test/src/crypto/test/Argon2Test.cs
index 23a3561009..eb3a946936 100644
--- a/crypto/test/src/crypto/test/Argon2Test.cs
+++ b/crypto/test/src/crypto/test/Argon2Test.cs
@@ -1,84 +1,46 @@
-using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using NUnit.Framework;
 
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
-using Org.BouncyCastle.Utilities.Test;
-using System;
-using System.Collections.Generic;
-using System.Text;
 
 namespace Org.BouncyCastle.Crypto.Tests
 {
     [TestFixture]
     public class Argon2Test
-        : SimpleTest
     {
-        private const int DEFAULT_OUTPUTLEN = 32;
-
-        public override string Name => "ArgonTest";
-        public override void PerformTest()
-        {
-            TestExceptions();
-            TestPermutations();
-            TestVectorsFromSpecs();
-            HashTestsVersion10();
-            HashTestsVersion13();
-        }
+        private const int DefaultOutputLen = 32;
 
         #region "Exception tests"
         [Test]
         public void TestExceptions()
         {
             // lanes less than MIN_PARALLELISM
-            Assert.Throws<InvalidOperationException>(() => {
-                Argon2BytesGenerator gen = new Argon2BytesGenerator();
-                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
-                    .WithParallelism(0);
-
-                gen.Init(builder.Build());
-            });
+            CheckInvalidConfig(b => b.WithParallelism(0));
 
             // lanes greater than MAX_PARALLELISM
-            Assert.Throws<InvalidOperationException>(() => {
-                Argon2BytesGenerator gen = new Argon2BytesGenerator();
-                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
-                    .WithParallelism(16777299);
-
-                gen.Init(builder.Build());
-            });
+            CheckInvalidConfig(b => b.WithParallelism(16777299));
 
             // iterations less than MIN_ITERATIONS
-            Assert.Throws<InvalidOperationException>(() => {
-                Argon2BytesGenerator gen = new Argon2BytesGenerator();
-                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
-                    .WithIterations(0);
-
-                gen.Init(builder.Build());
-            });
+            CheckInvalidConfig(b => b.WithIterations(0));
 
             // memory less than 2 * lanes
-            Assert.Throws<InvalidOperationException>(() => {
-                Argon2BytesGenerator gen = new Argon2BytesGenerator();
-                Argon2Parameters.Builder builder = new Argon2Parameters.Builder()
-                    .WithMemoryAsKB(10)
-                    .WithParallelism(6);
-
-                gen.Init(builder.Build());
-            });
+            CheckInvalidConfig(b => b.WithMemoryAsKB(10).WithParallelism(6));
 
             // output length less than MIN_OUTLEN
-            Assert.Throws<InvalidOperationException>(() => {
+            Assert.Throws<InvalidOperationException>(() =>
+            {
                 Argon2BytesGenerator gen = new Argon2BytesGenerator();
-                Argon2Parameters.Builder builder = new Argon2Parameters.Builder();
-
-                gen.Init(builder.Build());
-
+                Argon2Parameters parameters = new Argon2Parameters.Builder().Build();
+                gen.Init(parameters);
                 byte[] result = new byte[3];
-                gen.GenerateBytes("password", result);
+                gen.GenerateBytes("password".ToCharArray(), result);
             });
-
         }
         #endregion
 
@@ -88,33 +50,33 @@ public void TestPermutations()
         {
             byte[] rootPassword = Encoding.ASCII.GetBytes("aac");
             byte[] buf;
-            
+
             byte[][] salts = new byte[][] {
                 new byte[16],
                 new byte[16],
-                new byte[16] 
+                new byte[16]
             };
 
-            for (int t = 0; t< 16; t++)
+            for (int t = 0; t < 16; t++)
             {
-                salts[1][t] = (byte) t;
-                salts[2][t] = (byte) (16 - t);
+                salts[1][t] = (byte)t;
+                salts[2][t] = (byte)(16 - t);
             }
-            
+
             //
             // Permutation, starting with a shorter array, same length then one longer.
             //
-            for (int j = rootPassword.Length - 1; j<rootPassword.Length + 2; j++)
+            for (int j = rootPassword.Length - 1; j < rootPassword.Length + 2; j++)
             {
                 buf = new byte[j];
-                
-                for (int a = 0; a<rootPassword.Length; a++)
+
+                for (int a = 0; a < rootPassword.Length; a++)
                 {
-                    for (int b = 0; b<buf.Length; b++)
+                    for (int b = 0; b < buf.Length; b++)
                     {
                         buf[b] = rootPassword[(a + b) % rootPassword.Length];
                     }
-                    
+
                     List<byte[]> permutations = new List<byte[]>();
                     Permute(permutations, buf, 0, buf.Length - 1);
 
@@ -124,20 +86,32 @@ public void TestPermutations()
                         for (int k = 0; k != salts.Length; k++)
                         {
                             byte[] salt = salts[k];
-                            byte[] expected = Generate(Argon2Parameters.ARGON2_VERSION_10, 1, 8, 2, rootPassword, salt, 32);
-                            byte[] testValue = Generate(Argon2Parameters.ARGON2_VERSION_10, 1, 8, 2, candidate, salt, 32);
+                            byte[] expected = Generate(Argon2Parameters.Argon2_Version10, 1, 8, 2, rootPassword, salt, 32);
+                            byte[] testValue = Generate(Argon2Parameters.Argon2_Version10, 1, 8, 2, candidate, salt, 32);
 
                             //
                             // If the passwords are the same for the same salt we should have the same string.
                             //
                             bool sameAsRoot = Arrays.AreEqual(rootPassword, candidate);
-                            IsTrue("expected same result", sameAsRoot == Arrays.AreEqual(expected, testValue));
+                            Assert.AreEqual(sameAsRoot, Arrays.AreEqual(expected, testValue), "expected same result");
                         }
                     }
                 }
             }
         }
 
+        private static void CheckInvalidConfig(Action<Argon2Parameters.Builder> config)
+        {
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                var generator = new Argon2BytesGenerator();
+                var builder = new Argon2Parameters.Builder();
+                config(builder);
+                var parameters = builder.Build();
+                generator.Init(parameters);
+            });
+        }
+
         private static void Swap(byte[] buf, int i, int j)
         {
             byte b = buf[i];
@@ -167,26 +141,25 @@ private static void Permute(List<byte[]> permutation, byte[] a, int l, int r)
                 }
             }
         }
-        
-        private static byte[] Generate(int version, int iterations, int memory, int parallelism,
-                                byte[] password, byte[] salt, int outputLength)
+
+        private static byte[] Generate(int version, int iterations, int memory, int parallelism, byte[] password,
+            byte[] salt, int outputLength)
         {
-            Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i)
+            Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.Argon2_i)
                 .WithVersion(version)
                 .WithIterations(iterations)
                 .WithMemoryPowOfTwo(memory)
                 .WithParallelism(parallelism)
-                .WithSalt(salt);
+                .WithSalt(salt)
+                .Build();
 
             //
             // Set the password.
             //
             Argon2BytesGenerator gen = new Argon2BytesGenerator();
-
-            gen.Init(builder.Build());
+            gen.Init(parameters);
 
             byte[] result = new byte[outputLength];
-
             gen.GenerateBytes(password, result, 0, result.Length);
             return result;
         }
@@ -199,112 +172,110 @@ public void HashTestsVersion10()
         {
             /* Multiple test cases for various input values */
 
-            int version = Argon2Parameters.ARGON2_VERSION_10;
+            int version = Argon2Parameters.Argon2_Version10;
 
             HashTest(version, 2, 16, 1, "password", "somesalt",
                 "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 20, 1, "password", "somesalt",
                 "9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 18, 1, "password", "somesalt",
                 "3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 8, 1, "password", "somesalt",
                 "fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
             HashTest(version, 2, 8, 2, "password", "somesalt",
                 "b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 1, 16, 1, "password", "somesalt",
                 "81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 4, 16, 1, "password", "somesalt",
                 "f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 16, 1, "differentpassword", "somesalt",
                 "e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 16, 1, "password", "diffsalt",
                 "79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 16, 1, "password", "diffsalt",
                 "1a097a5d1c80e579583f6e19c7e4763ccb7c522ca85b7d58143738e12ca39f8e6e42734c950ff2463675b97c37ba" +
                     "39feba4a9cd9cc5b4c798f2aaf70eb4bd044c8d148decb569870dbd923430b82a083f284beae777812cce18cdac68ee8ccef" +
                     "c6ec9789f30a6b5a034591f51af830f4",
                 112);
-
         }
 
         [Test]
         public void HashTestsVersion13()
         {
-            int version = Argon2Parameters.ARGON2_VERSION_13;
+            int version = Argon2Parameters.Argon2_Version13;
 
             HashTest(version, 2, 16, 1, "password", "somesalt",
                 "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 20, 1, "password", "somesalt",
                 "d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 18, 1, "password", "somesalt",
                 "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 8, 1, "password", "somesalt",
                 "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 8, 2, "password", "somesalt",
                 "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 1, 16, 1, "password", "somesalt",
                 "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 4, 16, 1, "password", "somesalt",
                 "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 16, 1, "differentpassword", "somesalt",
                 "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
-                DEFAULT_OUTPUTLEN);
+                DefaultOutputLen);
 
             HashTest(version, 2, 16, 1, "password", "diffsalt",
                 "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
-                DEFAULT_OUTPUTLEN);
-
+                DefaultOutputLen);
         }
 
-        private void HashTest(int version, int iterations, int memory, int parallelism,
-                      string password, string salt, String passwordRef, int outputLength)
+        private void HashTest(int version, int iterations, int memory, int parallelism, string password, string salt,
+            string passwordRef, int outputLength)
         {
-            Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i)
+            Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.Argon2_i)
                 .WithVersion(version)
                 .WithIterations(iterations)
                 .WithMemoryPowOfTwo(memory)
                 .WithParallelism(parallelism)
-                .WithSalt(Encoding.ASCII.GetBytes(salt));
+                .WithSalt(Encoding.ASCII.GetBytes(salt))
+                .Build();
 
             Argon2BytesGenerator gen = new Argon2BytesGenerator();
-
-            gen.Init(builder.Build());
+            gen.Init(parameters);
 
             byte[] result = new byte[outputLength];
-            gen.GenerateBytes(password, result, 0, result.Length);
+            gen.GenerateBytes(password.ToCharArray(), result, 0, result.Length);
 
-            IsTrue(passwordRef + " Failed", AreEqual(result, Hex.Decode(passwordRef)));
+            Assert.True(Arrays.AreEqual(result, Hex.Decode(passwordRef)), passwordRef + " Failed");
         }
         #endregion
 
@@ -317,24 +288,24 @@ private void SpecsTest(int version, int type, string passwordRef)
             byte[] password = Hex.Decode("0101010101010101010101010101010101010101010101010101010101010101");
 
             byte[] expected = Hex.Decode(passwordRef);
-            byte[] result = new byte[32];
 
-            Argon2Parameters.Builder builder = new Argon2Parameters.Builder(type)
+            Argon2Parameters parameters = new Argon2Parameters.Builder(type)
                 .WithVersion(version)
                 .WithIterations(3)
                 .WithMemoryAsKB(32)
                 .WithParallelism(4)
                 .WithAdditional(ad)
                 .WithSecret(secret)
-                .WithSalt(salt);
+                .WithSalt(salt)
+                .Build();
 
             Argon2BytesGenerator gen = new Argon2BytesGenerator();
+            gen.Init(parameters);
 
-            gen.Init(builder.Build());
-
+            byte[] result = new byte[32];
             gen.GenerateBytes(password, result, 0, result.Length);
 
-            IsTrue(passwordRef + " Failed", AreEqual(result, expected));
+            Assert.True(Arrays.AreEqual(expected, result), passwordRef + " Failed");
         }
 
         [Test]
@@ -342,34 +313,34 @@ public void TestVectorsFromSpecs()
         {
             /* Version 0x13 (19) from RFC 9106 https://datatracker.ietf.org/doc/html/rfc9106#name-test-vectors */
             SpecsTest(
-                Argon2Parameters.ARGON2_VERSION_13,
-                Argon2Parameters.ARGON2_d,
+                Argon2Parameters.Argon2_Version13,
+                Argon2Parameters.Argon2_d,
                 "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb");
 
             SpecsTest(
-                Argon2Parameters.ARGON2_VERSION_13,
-                Argon2Parameters.ARGON2_i,
+                Argon2Parameters.Argon2_Version13,
+                Argon2Parameters.Argon2_i,
                 "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8");
 
             SpecsTest(
-                Argon2Parameters.ARGON2_VERSION_13,
-                Argon2Parameters.ARGON2_id,
+                Argon2Parameters.Argon2_Version13,
+                Argon2Parameters.Argon2_id,
                 "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659");
 
             /* Version 0x10 (16) from reference C implementation https://github.com/P-H-C/phc-winner-argon2/tree/master/kats */
             SpecsTest(
-                Argon2Parameters.ARGON2_VERSION_10,
-                Argon2Parameters.ARGON2_d,
+                Argon2Parameters.Argon2_Version10,
+                Argon2Parameters.Argon2_d,
                 "96a9d4e5a1734092c85e29f410a45914a5dd1f5cbf08b2670da68a0285abf32b");
 
             SpecsTest(
-                Argon2Parameters.ARGON2_VERSION_10,
-                Argon2Parameters.ARGON2_i,
+                Argon2Parameters.Argon2_Version10,
+                Argon2Parameters.Argon2_i,
                 "87aeedd6517ab830cd9765cd8231abb2e647a5dee08f7c05e02fcb763335d0fd");
 
             SpecsTest(
-                Argon2Parameters.ARGON2_VERSION_10,
-                Argon2Parameters.ARGON2_id,
+                Argon2Parameters.Argon2_Version10,
+                Argon2Parameters.Argon2_id,
                 "b64615f07789b66b645b67ee9ed3b377ae350b6bfcbb0fc95141ea8f322613c0");
         }
         #endregion

From 55361bdd7420747929196922de30a64539bf9c6a Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 3 Oct 2024 19:45:02 +0200
Subject: [PATCH 31/37] Followup changes to Argon2 in OpenPGP

---
 crypto/src/openpgp/PgpUtilities.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 477e9e906c..559b700077 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -343,8 +343,8 @@ internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag al
             if (s2k != null && s2k.Type == S2k.Argon2)
             {
                 Argon2Parameters.Builder builder =
-                    new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
-                        .WithVersion(Argon2Parameters.ARGON2_VERSION_13)
+                    new Argon2Parameters.Builder(Argon2Parameters.Argon2_id)
+                        .WithVersion(Argon2Parameters.Argon2_Version13)
                         .WithIterations(s2k.Passes)
                         .WithMemoryPowOfTwo(s2k.MemorySizeExponent)
                         .WithParallelism(s2k.Parallelism)

From f9182de615ca1aadebc349727978fb99d2b9455e Mon Sep 17 00:00:00 2001
From: Peter Dettman <peter.dettman@bouncycastle.org>
Date: Thu, 3 Oct 2024 18:44:18 +0700
Subject: [PATCH 32/37] Add XorBothTo methods to Nat(512)

---
 crypto/src/math/raw/Nat.cs    |  82 ++++++++
 crypto/src/math/raw/Nat512.cs | 344 ++++++++++++++++++++++++++++++----
 2 files changed, 387 insertions(+), 39 deletions(-)

diff --git a/crypto/src/math/raw/Nat.cs b/crypto/src/math/raw/Nat.cs
index b524750d85..48bf3b2e14 100644
--- a/crypto/src/math/raw/Nat.cs
+++ b/crypto/src/math/raw/Nat.cs
@@ -2930,6 +2930,88 @@ public static void Xor64(int len, ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y,
         }
 #endif
 
+        public static void XorBothTo(int len, uint[] x, uint[] y, uint[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorBothTo(len, x.AsSpan(0, len), y.AsSpan(0, len), z.AsSpan(0, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[i] ^= x[i] ^ y[i];
+            }
+#endif
+        }
+
+        public static void XorBothTo(int len, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorBothTo(len, x.AsSpan(xOff, len), y.AsSpan(yOff, len), z.AsSpan(zOff, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[zOff + i] ^= x[xOff + i] ^ y[yOff + i];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorBothTo(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            int i = 0, limit16 = len - 16;
+            while (i <= limit16)
+            {
+                Nat512.XorBothTo(x[i..], y[i..], z[i..]);
+                i += 16;
+            }
+            while (i < len)
+            {
+                z[i] ^= x[i] ^ y[i];
+                ++i;
+            }
+        }
+#endif
+
+        public static void XorBothTo64(int len, ulong[] x, ulong[] y, ulong[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorBothTo64(len, x.AsSpan(0, len), y.AsSpan(0, len), z.AsSpan(0, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[i] ^= x[i] ^ y[i];
+            }
+#endif
+        }
+
+        public static void XorBothTo64(int len, ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorBothTo64(len, x.AsSpan(xOff, len), y.AsSpan(yOff, len), z.AsSpan(zOff, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[zOff + i] ^= x[xOff + i] ^ y[yOff + i];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorBothTo64(int len, ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulong> z)
+        {
+            int i = 0, limit8 = len - 8;
+            while (i <= limit8)
+            {
+                Nat512.XorBothTo64(x[i..], y[i..], z[i..]);
+                i += 8;
+            }
+            while (i < len)
+            {
+                z[i] ^= x[i] ^ y[i];
+                ++i;
+            }
+        }
+#endif
+
         public static void XorTo(int len, uint[] x, uint[] z)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
diff --git a/crypto/src/math/raw/Nat512.cs b/crypto/src/math/raw/Nat512.cs
index 71b53214cf..8e98c37df6 100644
--- a/crypto/src/math/raw/Nat512.cs
+++ b/crypto/src/math/raw/Nat512.cs
@@ -48,6 +48,21 @@ public static void Square(uint[] x, uint[] zz)
             Nat.AddWordAt(32, c24, zz, 24); 
         }
 
+        public static void Xor(uint[] x, uint[] y, uint[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor(x.AsSpan(), y.AsSpan(), z.AsSpan());
+#else
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] = x[i + 0] ^ y[i + 0];
+                z[i + 1] = x[i + 1] ^ y[i + 1];
+                z[i + 2] = x[i + 2] ^ y[i + 2];
+                z[i + 3] = x[i + 3] ^ y[i + 3];
+            }
+#endif
+        }
+
         public static void Xor(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
@@ -128,36 +143,52 @@ public static void Xor(ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
         }
 #endif
 
-        public static void XorTo(uint[] x, int xOff, uint[] z, int zOff)
+        public static void Xor64(ulong[] x, ulong[] y, ulong[] z)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            XorTo(x.AsSpan(xOff), z.AsSpan(zOff));
+            Xor64(x.AsSpan(), y.AsSpan(), z.AsSpan());
 #else
-            for (int i = 0; i < 16; i += 4)
+            for (int i = 0; i < 8; i += 4)
             {
-                z[zOff + i + 0] ^= x[xOff + i + 0];
-                z[zOff + i + 1] ^= x[xOff + i + 1];
-                z[zOff + i + 2] ^= x[xOff + i + 2];
-                z[zOff + i + 3] ^= x[xOff + i + 3];
+                z[i + 0] = x[i + 0] ^ y[i + 0];
+                z[i + 1] = x[i + 1] ^ y[i + 1];
+                z[i + 2] = x[i + 2] ^ y[i + 2];
+                z[i + 3] = x[i + 3] ^ y[i + 3];
             }
 #endif
         }
 
+        public static void Xor64(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
+        {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        public static void XorTo(ReadOnlySpan<uint> x, Span<uint> z)
+            Xor64(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[zOff + i + 0] = x[xOff + i + 0] ^ y[yOff + i + 0];
+                z[zOff + i + 1] = x[xOff + i + 1] ^ y[yOff + i + 1];
+                z[zOff + i + 2] = x[xOff + i + 2] ^ y[yOff + i + 2];
+                z[zOff + i + 3] = x[xOff + i + 3] ^ y[yOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Xor64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulong> z)
         {
 #if NETCOREAPP3_0_OR_GREATER
             if (Org.BouncyCastle.Runtime.Intrinsics.X86.Avx2.IsEnabled &&
                 Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPacked)
             {
-                var X = MemoryMarshal.AsBytes(x[..16]);
-                var Z = MemoryMarshal.AsBytes(z[..16]);
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Y = MemoryMarshal.AsBytes(y[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
 
                 var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
                 var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
 
-                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Z[0x00..0x20]);
-                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Z[0x20..0x40]);
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Y[0x00..0x20]);
+                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Y[0x20..0x40]);
 
                 var Z0 = Avx2.Xor(X0, Y0);
                 var Z1 = Avx2.Xor(X1, Y1);
@@ -170,18 +201,19 @@ public static void XorTo(ReadOnlySpan<uint> x, Span<uint> z)
             if (Org.BouncyCastle.Runtime.Intrinsics.X86.Sse2.IsEnabled &&
                 Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPacked)
             {
-                var X = MemoryMarshal.AsBytes(x[..16]);
-                var Z = MemoryMarshal.AsBytes(z[..16]);
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Y = MemoryMarshal.AsBytes(y[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
 
                 var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
                 var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
                 var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
                 var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
 
-                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Z[0x00..0x10]);
-                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Z[0x10..0x20]);
-                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Z[0x20..0x30]);
-                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Z[0x30..0x40]);
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Y[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Y[0x10..0x20]);
+                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Y[0x20..0x30]);
+                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Y[0x30..0x40]);
 
                 var Z0 = Sse2.Xor(X0, Y0);
                 var Z1 = Sse2.Xor(X1, Y1);
@@ -196,33 +228,151 @@ public static void XorTo(ReadOnlySpan<uint> x, Span<uint> z)
             }
 #endif
 
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[i + 0] = x[i + 0] ^ y[i + 0];
+                z[i + 1] = x[i + 1] ^ y[i + 1];
+                z[i + 2] = x[i + 2] ^ y[i + 2];
+                z[i + 3] = x[i + 3] ^ y[i + 3];
+            }
+        }
+#endif
+
+        public static void XorBothTo(uint[] x, uint[] y, uint[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorBothTo(x.AsSpan(), y.AsSpan(), z.AsSpan());
+#else
             for (int i = 0; i < 16; i += 4)
             {
-                z[i + 0] ^= x[i + 0];
-                z[i + 1] ^= x[i + 1];
-                z[i + 2] ^= x[i + 2];
-                z[i + 3] ^= x[i + 3];
+                z[i + 0] ^= x[i + 0] ^ y[i + 0];
+                z[i + 1] ^= x[i + 1] ^ y[i + 1];
+                z[i + 2] ^= x[i + 2] ^ y[i + 2];
+                z[i + 3] ^= x[i + 3] ^ y[i + 3];
             }
+#endif
         }
+
+        public static void XorBothTo(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorBothTo(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[zOff + i + 0] ^= x[xOff + i + 0] ^ y[yOff + i + 0];
+                z[zOff + i + 1] ^= x[xOff + i + 1] ^ y[yOff + i + 1];
+                z[zOff + i + 2] ^= x[xOff + i + 2] ^ y[yOff + i + 2];
+                z[zOff + i + 3] ^= x[xOff + i + 3] ^ y[yOff + i + 3];
+            }
 #endif
+        }
 
-        public static void Xor64(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorBothTo(ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Org.BouncyCastle.Runtime.Intrinsics.X86.Avx2.IsEnabled &&
+                Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPacked)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Y = MemoryMarshal.AsBytes(y[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Y[0x00..0x20]);
+                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Y[0x20..0x40]);
+
+                var Z0 = MemoryMarshal.Read<Vector256<byte>>(Z[0x00..0x20]);
+                var Z1 = MemoryMarshal.Read<Vector256<byte>>(Z[0x20..0x40]);
+
+                Z0 = Avx2.Xor(Z0, Avx2.Xor(X0, Y0));
+                Z1 = Avx2.Xor(Z1, Avx2.Xor(X1, Y1));
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
+                return;
+            }
+
+            if (Org.BouncyCastle.Runtime.Intrinsics.X86.Sse2.IsEnabled &&
+                Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPacked)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Y = MemoryMarshal.AsBytes(y[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+                var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
+                var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Y[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Y[0x10..0x20]);
+                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Y[0x20..0x30]);
+                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Y[0x30..0x40]);
+
+                var Z0 = MemoryMarshal.Read<Vector128<byte>>(Z[0x00..0x10]);
+                var Z1 = MemoryMarshal.Read<Vector128<byte>>(Z[0x10..0x20]);
+                var Z2 = MemoryMarshal.Read<Vector128<byte>>(Z[0x20..0x30]);
+                var Z3 = MemoryMarshal.Read<Vector128<byte>>(Z[0x30..0x40]);
+
+                Z0 = Sse2.Xor(Z0, Sse2.Xor(X0, Y0));
+                Z1 = Sse2.Xor(Z1, Sse2.Xor(X1, Y1));
+                Z2 = Sse2.Xor(Z2, Sse2.Xor(X2, Y2));
+                Z3 = Sse2.Xor(Z3, Sse2.Xor(X3, Y3));
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                MemoryMarshal.Write(Z[0x20..0x30], ref Z2);
+                MemoryMarshal.Write(Z[0x30..0x40], ref Z3);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] ^= x[i + 0] ^ y[i + 0];
+                z[i + 1] ^= x[i + 1] ^ y[i + 1];
+                z[i + 2] ^= x[i + 2] ^ y[i + 2];
+                z[i + 3] ^= x[i + 3] ^ y[i + 3];
+            }
+        }
+#endif
+
+        public static void XorBothTo64(ulong[] x, ulong[] y, ulong[] z)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            Xor64(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+            XorBothTo64(x.AsSpan(), y.AsSpan(), z.AsSpan());
 #else
             for (int i = 0; i < 8; i += 4)
             {
-                z[zOff + i + 0] = x[xOff + i + 0] ^ y[yOff + i + 0];
-                z[zOff + i + 1] = x[xOff + i + 1] ^ y[yOff + i + 1];
-                z[zOff + i + 2] = x[xOff + i + 2] ^ y[yOff + i + 2];
-                z[zOff + i + 3] = x[xOff + i + 3] ^ y[yOff + i + 3];
+                z[i + 0] ^= x[i + 0] ^ y[i + 0];
+                z[i + 1] ^= x[i + 1] ^ y[i + 1];
+                z[i + 2] ^= x[i + 2] ^ y[i + 2];
+                z[i + 3] ^= x[i + 3] ^ y[i + 3];
             }
 #endif
         }
 
+        public static void XorBothTo64(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
+        {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        public static void Xor64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulong> z)
+            XorBothTo64(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[zOff + i + 0] ^= x[xOff + i + 0] ^ y[yOff + i + 0];
+                z[zOff + i + 1] ^= x[xOff + i + 1] ^ y[yOff + i + 1];
+                z[zOff + i + 2] ^= x[xOff + i + 2] ^ y[yOff + i + 2];
+                z[zOff + i + 3] ^= x[xOff + i + 3] ^ y[yOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorBothTo64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulong> z)
         {
 #if NETCOREAPP3_0_OR_GREATER
             if (Org.BouncyCastle.Runtime.Intrinsics.X86.Avx2.IsEnabled &&
@@ -238,8 +388,11 @@ public static void Xor64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulon
                 var Y0 = MemoryMarshal.Read<Vector256<byte>>(Y[0x00..0x20]);
                 var Y1 = MemoryMarshal.Read<Vector256<byte>>(Y[0x20..0x40]);
 
-                var Z0 = Avx2.Xor(X0, Y0);
-                var Z1 = Avx2.Xor(X1, Y1);
+                var Z0 = MemoryMarshal.Read<Vector256<byte>>(Z[0x00..0x20]);
+                var Z1 = MemoryMarshal.Read<Vector256<byte>>(Z[0x20..0x40]);
+
+                Z0 = Avx2.Xor(Z0, Avx2.Xor(X0, Y0));
+                Z1 = Avx2.Xor(Z1, Avx2.Xor(X1, Y1));
 
                 MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
                 MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
@@ -263,10 +416,15 @@ public static void Xor64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulon
                 var Y2 = MemoryMarshal.Read<Vector128<byte>>(Y[0x20..0x30]);
                 var Y3 = MemoryMarshal.Read<Vector128<byte>>(Y[0x30..0x40]);
 
-                var Z0 = Sse2.Xor(X0, Y0);
-                var Z1 = Sse2.Xor(X1, Y1);
-                var Z2 = Sse2.Xor(X2, Y2);
-                var Z3 = Sse2.Xor(X3, Y3);
+                var Z0 = MemoryMarshal.Read<Vector128<byte>>(Z[0x00..0x10]);
+                var Z1 = MemoryMarshal.Read<Vector128<byte>>(Z[0x10..0x20]);
+                var Z2 = MemoryMarshal.Read<Vector128<byte>>(Z[0x20..0x30]);
+                var Z3 = MemoryMarshal.Read<Vector128<byte>>(Z[0x30..0x40]);
+
+                Z0 = Sse2.Xor(Z0, Sse2.Xor(X0, Y0));
+                Z1 = Sse2.Xor(Z1, Sse2.Xor(X1, Y1));
+                Z2 = Sse2.Xor(Z2, Sse2.Xor(X2, Y2));
+                Z3 = Sse2.Xor(Z3, Sse2.Xor(X3, Y3));
 
                 MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
                 MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
@@ -278,14 +436,122 @@ public static void Xor64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulon
 
             for (int i = 0; i < 8; i += 4)
             {
-                z[i + 0] = x[i + 0] ^ y[i + 0];
-                z[i + 1] = x[i + 1] ^ y[i + 1];
-                z[i + 2] = x[i + 2] ^ y[i + 2];
-                z[i + 3] = x[i + 3] ^ y[i + 3];
+                z[i + 0] ^= x[i + 0] ^ y[i + 0];
+                z[i + 1] ^= x[i + 1] ^ y[i + 1];
+                z[i + 2] ^= x[i + 2] ^ y[i + 2];
+                z[i + 3] ^= x[i + 3] ^ y[i + 3];
             }
         }
 #endif
 
+        public static void XorTo(uint[] x, uint[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo(x.AsSpan(), z.AsSpan());
+#else
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] ^= x[i + 0];
+                z[i + 1] ^= x[i + 1];
+                z[i + 2] ^= x[i + 2];
+                z[i + 3] ^= x[i + 3];
+            }
+#endif
+        }
+
+        public static void XorTo(uint[] x, int xOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo(x.AsSpan(xOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[zOff + i + 0] ^= x[xOff + i + 0];
+                z[zOff + i + 1] ^= x[xOff + i + 1];
+                z[zOff + i + 2] ^= x[xOff + i + 2];
+                z[zOff + i + 3] ^= x[xOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorTo(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Org.BouncyCastle.Runtime.Intrinsics.X86.Avx2.IsEnabled &&
+                Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPacked)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
+
+                var Z0 = MemoryMarshal.Read<Vector256<byte>>(Z[0x00..0x20]);
+                var Z1 = MemoryMarshal.Read<Vector256<byte>>(Z[0x20..0x40]);
+
+                Z0 = Avx2.Xor(Z0, X0);
+                Z1 = Avx2.Xor(Z1, X1);
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
+                return;
+            }
+
+            if (Org.BouncyCastle.Runtime.Intrinsics.X86.Sse2.IsEnabled &&
+                Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPacked)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+                var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
+                var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
+
+                var Z0 = MemoryMarshal.Read<Vector128<byte>>(Z[0x00..0x10]);
+                var Z1 = MemoryMarshal.Read<Vector128<byte>>(Z[0x10..0x20]);
+                var Z2 = MemoryMarshal.Read<Vector128<byte>>(Z[0x20..0x30]);
+                var Z3 = MemoryMarshal.Read<Vector128<byte>>(Z[0x30..0x40]);
+
+                Z0 = Sse2.Xor(Z0, X0);
+                Z1 = Sse2.Xor(Z1, X1);
+                Z2 = Sse2.Xor(Z2, X2);
+                Z3 = Sse2.Xor(Z3, X3);
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                MemoryMarshal.Write(Z[0x20..0x30], ref Z2);
+                MemoryMarshal.Write(Z[0x30..0x40], ref Z3);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] ^= x[i + 0];
+                z[i + 1] ^= x[i + 1];
+                z[i + 2] ^= x[i + 2];
+                z[i + 3] ^= x[i + 3];
+            }
+        }
+#endif
+
+        public static void XorTo64(ulong[] x, ulong[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo64(x.AsSpan(), z.AsSpan());
+#else
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[i + 0] ^= x[i + 0];
+                z[i + 1] ^= x[i + 1];
+                z[i + 2] ^= x[i + 2];
+                z[i + 3] ^= x[i + 3];
+            }
+#endif
+        }
+
         public static void XorTo64(ulong[] x, int xOff, ulong[] z, int zOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER

From d396a19d59b6abe3c84f5bcad340c17925848e73 Mon Sep 17 00:00:00 2001
From: Peter Dettman <peter.dettman@bouncycastle.org>
Date: Thu, 3 Oct 2024 19:14:08 +0700
Subject: [PATCH 33/37] Refactoring in Argon2

---
 .../crypto/generators/Argon2BytesGenerator.cs | 96 +++++++++----------
 .../src/crypto/parameters/Argon2Parameters.cs | 30 +++---
 crypto/test/src/crypto/test/Argon2Test.cs     | 45 ++++-----
 3 files changed, 80 insertions(+), 91 deletions(-)

diff --git a/crypto/src/crypto/generators/Argon2BytesGenerator.cs b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
index 3800ffb129..b16d0c4170 100644
--- a/crypto/src/crypto/generators/Argon2BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
@@ -22,7 +22,7 @@ public sealed class Argon2BytesGenerator
 
         /* Minimum and maximum number of lanes (degree of parallelism) */
         private const int MinParallelism = 1;
-        private const int MaxParallelism = 16777216;
+        private const int MaxParallelism = (1 << 24) - 1;
 
         /* Minimum and maximum digest size in bytes */
         private const int MinOutlen = 4;
@@ -50,18 +50,43 @@ public Argon2BytesGenerator()
          */
         public void Init(Argon2Parameters parameters)
         {
-            this.parameters = parameters;
+            if (parameters.Version != Argon2Parameters.Version10 &&
+                parameters.Version != Argon2Parameters.Version13)
+            {
+                throw new NotSupportedException("unknown Argon2 version");
+            }
+            if (parameters.Type != Argon2Parameters.Argon2d &&
+                parameters.Type != Argon2Parameters.Argon2i &&
+                parameters.Type != Argon2Parameters.Argon2id)
+            {
+                throw new NotSupportedException("unknown Argon2 type");
+            }
 
-            if (parameters.Lanes < MinParallelism)
-                throw new InvalidOperationException($"lanes must be greater than " + MinParallelism);
-            if (parameters.Lanes > MaxParallelism)
-                throw new InvalidOperationException("lanes must be less than " + MaxParallelism);
-            if (parameters.Memory < 2 * parameters.Lanes)
-                throw new InvalidOperationException("memory is less than: " + (2 * parameters.Lanes) + " expected " + (2 * parameters.Lanes));
+            if (parameters.Parallelism < MinParallelism)
+                throw new InvalidOperationException("parallelism must be at least " + MinParallelism);
+            if (parameters.Parallelism > MaxParallelism)
+                throw new InvalidOperationException("parallelism must be at most " + MaxParallelism);
             if (parameters.Iterations < MinIterations)
-                throw new InvalidOperationException("iterations is less than: " + MinIterations);
+                throw new InvalidOperationException("iterations must be at least " + MinIterations);
+
+            this.parameters = parameters;
+
+            // 2. Align memory size
+            // Minimum memoryBlocks = 8L blocks, where L is the number of lanes
+            int memoryBlocks = System.Math.Max(parameters.Memory, 2 * Argon2SyncPoints * parameters.Parallelism);
+
+            this.segmentLength = memoryBlocks / (Argon2SyncPoints * parameters.Parallelism);
+            this.laneLength = segmentLength * Argon2SyncPoints;
+
+            // Ensure that all segments have equal length
+            memoryBlocks = parameters.Parallelism * laneLength;
 
-            DoInit(parameters);
+            this.memory = new Block[memoryBlocks];
+
+            for (int i = 0; i < memory.Length; i++)
+            {
+                memory[i] = new Block();
+            }
         }
 
         public int GenerateBytes(char[] password, byte[] output) =>
@@ -102,31 +127,6 @@ private void Reset()
             }
         }
 
-        private void DoInit(Argon2Parameters parameters)
-        {
-            /* 2. Align memory size */
-            /* Minimum memoryBlocks = 8L blocks, where L is the number of lanes */
-            int memoryBlocks = System.Math.Max(parameters.Memory, 2 * Argon2SyncPoints * parameters.Lanes);
-
-            this.segmentLength = memoryBlocks / (parameters.Lanes * Argon2SyncPoints);
-            this.laneLength = segmentLength * Argon2SyncPoints;
-
-            /* Ensure that all segments have equal length */
-            memoryBlocks = segmentLength * (parameters.Lanes * Argon2SyncPoints);
-
-            InitMemory(memoryBlocks);
-        }
-
-        private void InitMemory(int memoryBlocks)
-        {
-            this.memory = new Block[memoryBlocks];
-
-            for (int i = 0; i < memory.Length; i++)
-            {
-                memory[i] = new Block();
-            }
-        }
-
         private void FillMemoryBlocks()
         {
             FillBlock filler = new FillBlock();
@@ -139,7 +139,7 @@ private void FillMemoryBlocks()
                 {
                     position.slice = slice;
 
-                    for (int lane = 0; lane < parameters.Lanes; ++lane)
+                    for (int lane = 0; lane < parameters.Parallelism; ++lane)
                     {
                         position.lane = lane;
 
@@ -202,8 +202,8 @@ private void FillSegment(FillBlock filler, Position position)
 
         private bool IsDataIndependentAddressing(Position position)
         {
-            return (parameters.Type == Argon2Parameters.Argon2_i) ||
-                (parameters.Type == Argon2Parameters.Argon2_id
+            return (parameters.Type == Argon2Parameters.Argon2i) ||
+                (parameters.Type == Argon2Parameters.Argon2id
                     && (position.pass == 0)
                     && (position.slice < Argon2SyncPoints / 2)
                 );
@@ -227,7 +227,7 @@ private void InitAddressBlocks(FillBlock filler, Position position, Block inputB
 
         private bool IsWithXor(Position position)
         {
-            return !(position.pass == 0 || parameters.Version == Argon2Parameters.Argon2_Version10);
+            return !(position.pass == 0 || parameters.Version == Argon2Parameters.Version10);
         }
 
         private int GetPrevOffset(int currentOffset)
@@ -290,7 +290,7 @@ private ulong GetPseudoRandom(
 
         private int GetRefLane(Position position, ulong pseudoRandom)
         {
-            int refLane = (int)((long)(pseudoRandom >> 32) % parameters.Lanes);
+            int refLane = (int)((long)(pseudoRandom >> 32) % parameters.Parallelism);
 
             if ((position.pass == 0) && (position.slice == 0))
             {
@@ -347,7 +347,7 @@ private void Digest(byte[] tmpBlockBytes, byte[] output, int outOff, int outLen)
             Block finalBlock = memory[laneLength - 1];
 
             /* XOR the last blocks */
-            for (int i = 1; i < parameters.Lanes; i++)
+            for (int i = 1; i < parameters.Parallelism; i++)
             {
                 int lastBlockInLane = i * laneLength + (laneLength - 1);
                 finalBlock.XorWith(memory[lastBlockInLane]);
@@ -479,7 +479,7 @@ private void Initialize(byte[] tmpBlockBytes, byte[] password, int outputLength)
             Blake2bDigest blake = new Blake2bDigest(Argon2PrehashDigestLength * 8);
 
             uint[] values = {
-                (uint)parameters.Lanes,
+                (uint)parameters.Parallelism,
                 (uint)outputLength,
                 (uint)parameters.Memory,
                 (uint)parameters.Iterations,
@@ -522,10 +522,9 @@ private void FillFirstBlocks(byte[] tmpBlockBytes, byte[] initialHashWithZeros)
         {
             byte[] initialHashWithOnes = new byte[Argon2PrehashSeedLength];
             Array.Copy(initialHashWithZeros, 0, initialHashWithOnes, 0, Argon2PrehashDigestLength);
-            //        Pack.intToLittleEndian(1, initialHashWithOnes, Argon2PrehashDigestLength);
             initialHashWithOnes[Argon2PrehashDigestLength] = 1;
 
-            for (int i = 0; i < parameters.Lanes; i++)
+            for (int i = 0; i < parameters.Parallelism; i++)
             {
                 Pack.UInt32_To_LE((uint)i, initialHashWithZeros, Argon2PrehashDigestLength + 4);
                 Pack.UInt32_To_LE((uint)i, initialHashWithOnes, Argon2PrehashDigestLength + 4);
@@ -650,14 +649,7 @@ internal void XorWith(Block b1)
 
             internal void XorWith(Block b1, Block b2)
             {
-                // TODO New Nat.Xor variant for this
-                ulong[] v0 = v;
-                ulong[] v1 = b1.v;
-                ulong[] v2 = b2.v;
-                for (int i = 0; i < Size; i++)
-                {
-                    v0[i] ^= v1[i] ^ v2[i];
-                }
+                Nat.XorBothTo64(Size, b1.v, b2.v, v);
             }
 
             internal Block Clear()
diff --git a/crypto/src/crypto/parameters/Argon2Parameters.cs b/crypto/src/crypto/parameters/Argon2Parameters.cs
index 4da2afca0b..0073b92146 100644
--- a/crypto/src/crypto/parameters/Argon2Parameters.cs
+++ b/crypto/src/crypto/parameters/Argon2Parameters.cs
@@ -6,12 +6,12 @@ namespace Org.BouncyCastle.Crypto.Parameters
 {
     public sealed class Argon2Parameters
     {
-        public static readonly int Argon2_d = 0x00;
-        public static readonly int Argon2_i = 0x01;
-        public static readonly int Argon2_id = 0x02;
+        public static readonly int Argon2d = 0x00;
+        public static readonly int Argon2i = 0x01;
+        public static readonly int Argon2id = 0x02;
 
-        public static readonly int Argon2_Version10 = 0x10;
-        public static readonly int Argon2_Version13 = 0x13;
+        public static readonly int Version10 = 0x10;
+        public static readonly int Version13 = 0x13;
 
         private readonly int type;
         private readonly byte[] salt;
@@ -19,7 +19,7 @@ public sealed class Argon2Parameters
         private readonly byte[] additional;
         private readonly int iterations;
         private readonly int memory;
-        private readonly int lanes;
+        private readonly int parallelism;
         private readonly int version;
         private readonly ICharToByteConverter converter;
 
@@ -27,9 +27,9 @@ public sealed class Builder
         {
             private static readonly int DefaultIterations = 3;
             private static readonly int DefaultMemoryCost = 12;
-            private static readonly int DefaultLanes = 1;
-            private static readonly int DefaultType = Argon2_i;
-            private static readonly int DefaultVersion = Argon2_Version13;
+            private static readonly int DefaultParallelism = 1;
+            private static readonly int DefaultType = Argon2i;
+            private static readonly int DefaultVersion = Version13;
 
             private readonly int type;
 
@@ -38,7 +38,7 @@ public sealed class Builder
             private byte[] additional = Array.Empty<byte>();
             private int iterations = DefaultIterations;
             private int memory = 1 << DefaultMemoryCost;
-            private int lanes = DefaultLanes;
+            private int parallelism = DefaultParallelism;
             private int version = DefaultVersion;
             private ICharToByteConverter converter = PasswordConverter.Utf8;
 
@@ -54,7 +54,7 @@ public Builder(int type)
 
             public Builder WithParallelism(int parallelism)
             {
-                this.lanes = parallelism;
+                this.parallelism = parallelism;
                 return this;
             }
 
@@ -107,7 +107,7 @@ public Builder WithCharToByteConverter(ICharToByteConverter converter)
             }
 
             public Argon2Parameters Build() =>
-                new Argon2Parameters(type, salt, secret, additional, iterations, memory, lanes, version, converter);
+                new Argon2Parameters(type, salt, secret, additional, iterations, memory, parallelism, version, converter);
 
             public void Clear()
             {
@@ -118,7 +118,7 @@ public void Clear()
         }
 
         private Argon2Parameters(int type, byte[] salt, byte[] secret, byte[] additional, int iterations, int memory,
-            int lanes, int version, ICharToByteConverter converter)
+            int parallelism, int version, ICharToByteConverter converter)
         {
             this.type = type;
             this.salt = salt;
@@ -126,7 +126,7 @@ private Argon2Parameters(int type, byte[] salt, byte[] secret, byte[] additional
             this.additional = additional;
             this.iterations = iterations;
             this.memory = memory;
-            this.lanes = lanes;
+            this.parallelism = parallelism;
             this.version = version;
             this.converter = converter;
         }
@@ -141,7 +141,7 @@ private Argon2Parameters(int type, byte[] salt, byte[] secret, byte[] additional
 
         public int Iterations => iterations;
 
-        public int Lanes => lanes;
+        public int Parallelism => parallelism;
 
         public int Memory => memory;
 
diff --git a/crypto/test/src/crypto/test/Argon2Test.cs b/crypto/test/src/crypto/test/Argon2Test.cs
index eb3a946936..296d2a79cf 100644
--- a/crypto/test/src/crypto/test/Argon2Test.cs
+++ b/crypto/test/src/crypto/test/Argon2Test.cs
@@ -20,18 +20,15 @@ public class Argon2Test
         [Test]
         public void TestExceptions()
         {
-            // lanes less than MIN_PARALLELISM
+            // Parallelism less than MIN_PARALLELISM
             CheckInvalidConfig(b => b.WithParallelism(0));
 
-            // lanes greater than MAX_PARALLELISM
-            CheckInvalidConfig(b => b.WithParallelism(16777299));
+            // Parallelism greater than MAX_PARALLELISM
+            CheckInvalidConfig(b => b.WithParallelism(1 << 24));
 
             // iterations less than MIN_ITERATIONS
             CheckInvalidConfig(b => b.WithIterations(0));
 
-            // memory less than 2 * lanes
-            CheckInvalidConfig(b => b.WithMemoryAsKB(10).WithParallelism(6));
-
             // output length less than MIN_OUTLEN
             Assert.Throws<InvalidOperationException>(() =>
             {
@@ -86,8 +83,8 @@ public void TestPermutations()
                         for (int k = 0; k != salts.Length; k++)
                         {
                             byte[] salt = salts[k];
-                            byte[] expected = Generate(Argon2Parameters.Argon2_Version10, 1, 8, 2, rootPassword, salt, 32);
-                            byte[] testValue = Generate(Argon2Parameters.Argon2_Version10, 1, 8, 2, candidate, salt, 32);
+                            byte[] expected = Generate(Argon2Parameters.Version10, 1, 8, 2, rootPassword, salt, 32);
+                            byte[] testValue = Generate(Argon2Parameters.Version10, 1, 8, 2, candidate, salt, 32);
 
                             //
                             // If the passwords are the same for the same salt we should have the same string.
@@ -145,7 +142,7 @@ private static void Permute(List<byte[]> permutation, byte[] a, int l, int r)
         private static byte[] Generate(int version, int iterations, int memory, int parallelism, byte[] password,
             byte[] salt, int outputLength)
         {
-            Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.Argon2_i)
+            Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.Argon2i)
                 .WithVersion(version)
                 .WithIterations(iterations)
                 .WithMemoryPowOfTwo(memory)
@@ -172,7 +169,7 @@ public void HashTestsVersion10()
         {
             /* Multiple test cases for various input values */
 
-            int version = Argon2Parameters.Argon2_Version10;
+            int version = Argon2Parameters.Version10;
 
             HashTest(version, 2, 16, 1, "password", "somesalt",
                 "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694",
@@ -219,7 +216,7 @@ public void HashTestsVersion10()
         [Test]
         public void HashTestsVersion13()
         {
-            int version = Argon2Parameters.Argon2_Version13;
+            int version = Argon2Parameters.Version13;
 
             HashTest(version, 2, 16, 1, "password", "somesalt",
                 "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
@@ -261,7 +258,7 @@ public void HashTestsVersion13()
         private void HashTest(int version, int iterations, int memory, int parallelism, string password, string salt,
             string passwordRef, int outputLength)
         {
-            Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.Argon2_i)
+            Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.Argon2i)
                 .WithVersion(version)
                 .WithIterations(iterations)
                 .WithMemoryPowOfTwo(memory)
@@ -313,34 +310,34 @@ public void TestVectorsFromSpecs()
         {
             /* Version 0x13 (19) from RFC 9106 https://datatracker.ietf.org/doc/html/rfc9106#name-test-vectors */
             SpecsTest(
-                Argon2Parameters.Argon2_Version13,
-                Argon2Parameters.Argon2_d,
+                Argon2Parameters.Version13,
+                Argon2Parameters.Argon2d,
                 "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb");
 
             SpecsTest(
-                Argon2Parameters.Argon2_Version13,
-                Argon2Parameters.Argon2_i,
+                Argon2Parameters.Version13,
+                Argon2Parameters.Argon2i,
                 "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8");
 
             SpecsTest(
-                Argon2Parameters.Argon2_Version13,
-                Argon2Parameters.Argon2_id,
+                Argon2Parameters.Version13,
+                Argon2Parameters.Argon2id,
                 "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659");
 
             /* Version 0x10 (16) from reference C implementation https://github.com/P-H-C/phc-winner-argon2/tree/master/kats */
             SpecsTest(
-                Argon2Parameters.Argon2_Version10,
-                Argon2Parameters.Argon2_d,
+                Argon2Parameters.Version10,
+                Argon2Parameters.Argon2d,
                 "96a9d4e5a1734092c85e29f410a45914a5dd1f5cbf08b2670da68a0285abf32b");
 
             SpecsTest(
-                Argon2Parameters.Argon2_Version10,
-                Argon2Parameters.Argon2_i,
+                Argon2Parameters.Version10,
+                Argon2Parameters.Argon2i,
                 "87aeedd6517ab830cd9765cd8231abb2e647a5dee08f7c05e02fcb763335d0fd");
 
             SpecsTest(
-                Argon2Parameters.Argon2_Version10,
-                Argon2Parameters.Argon2_id,
+                Argon2Parameters.Version10,
+                Argon2Parameters.Argon2id,
                 "b64615f07789b66b645b67ee9ed3b377ae350b6bfcbb0fc95141ea8f322613c0");
         }
         #endregion

From 6940300ed5f6355578842b920ec134cc8cac6d0d Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Sat, 5 Oct 2024 19:10:17 +0200
Subject: [PATCH 34/37] Adapt PgpUtilities.DoMakeKeyFromPassPhrase to Argon2
 Refactoring

---
 crypto/src/openpgp/PgpUtilities.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 559b700077..d1f5b7173f 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -343,8 +343,8 @@ internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag al
             if (s2k != null && s2k.Type == S2k.Argon2)
             {
                 Argon2Parameters.Builder builder =
-                    new Argon2Parameters.Builder(Argon2Parameters.Argon2_id)
-                        .WithVersion(Argon2Parameters.Argon2_Version13)
+                    new Argon2Parameters.Builder(Argon2Parameters.Argon2id)
+                        .WithVersion(Argon2Parameters.Version13)
                         .WithIterations(s2k.Passes)
                         .WithMemoryPowOfTwo(s2k.MemorySizeExponent)
                         .WithParallelism(s2k.Parallelism)

From 1e57feb56f76b0e5f5afb167af9b26547c6ddf7a Mon Sep 17 00:00:00 2001
From: Peter Dettman <peter.dettman@bouncycastle.org>
Date: Wed, 10 Apr 2024 17:02:37 +0700
Subject: [PATCH 35/37] Add various fingerprint-related methods in OpenPgp

---
 crypto/src/openpgp/PgpPublicKey.cs           |  6 +-
 crypto/src/openpgp/PgpPublicKeyRing.cs       | 19 ++++--
 crypto/src/openpgp/PgpPublicKeyRingBundle.cs | 72 ++++++++++++++------
 crypto/src/openpgp/PgpSecretKeyRing.cs       | 48 ++++++++++++-
 crypto/src/openpgp/PgpSecretKeyRingBundle.cs | 34 ++++-----
 crypto/test/src/openpgp/test/PGPRSATest.cs   | 14 +++-
 crypto/test/src/openpgp/test/PgpEdDsaTest.cs |  6 +-
 7 files changed, 151 insertions(+), 48 deletions(-)

diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 61be4b6a4f..bd93264afc 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Drawing;
 using System.IO;
 
 using Org.BouncyCastle.Asn1.Cryptlib;
@@ -572,6 +571,11 @@ public byte[] GetFingerprint()
             return Arrays.Clone(fingerprint);
         }
 
+        public bool HasFingerprint(byte[] fingerprint)
+        {
+            return Arrays.AreEqual(this.fingerprint, fingerprint);
+        }
+
         /// <summary>
         /// Check if this key has an algorithm type that makes it suitable to use for encryption.
         /// </summary>
diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs
index 46eecd726e..f50dd915c1 100644
--- a/crypto/src/openpgp/PgpPublicKeyRing.cs
+++ b/crypto/src/openpgp/PgpPublicKeyRing.cs
@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.IO;
 
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
@@ -80,6 +79,18 @@ public virtual PgpPublicKey GetPublicKey(long keyId)
             return null;
         }
 
+        /// <summary>Return the public key with the passed in fingerprint if it is present.</summary>
+        public virtual PgpPublicKey GetPublicKey(byte[] fingerprint)
+        {
+            foreach (PgpPublicKey k in keys)
+            {
+                if (k.HasFingerprint(fingerprint))
+                    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 virtual IEnumerable<PgpPublicKey> GetPublicKeys()
@@ -239,17 +250,17 @@ public static PgpPublicKeyRing Join(PgpPublicKeyRing first, PgpPublicKeyRing sec
         public static PgpPublicKeyRing Join(PgpPublicKeyRing first, PgpPublicKeyRing second, bool joinTrustPackets,
             bool allowSubkeySigsOnNonSubkey)
         {
-            if (!Arrays.AreEqual(first.GetPublicKey().GetFingerprint(), second.GetPublicKey().GetFingerprint()))
+            if (!second.GetPublicKey().HasFingerprint(first.GetPublicKey().GetFingerprint()))
                 throw new ArgumentException("Cannot merge certificates with differing primary keys.");
 
             var secondKeys = new HashSet<long>();
-            foreach (var key in second.GetPublicKeys())
+            foreach (var key in second.keys)
             {
                 secondKeys.Add(key.KeyId);
             }
 
             var merged = new List<PgpPublicKey>();
-            foreach (var key in first.GetPublicKeys())
+            foreach (var key in first.keys)
             {
                 var copy = second.GetPublicKey(key.KeyId);
                 if (copy != null)
diff --git a/crypto/src/openpgp/PgpPublicKeyRingBundle.cs b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs
index 473d0ae5bb..1940c979e4 100644
--- a/crypto/src/openpgp/PgpPublicKeyRingBundle.cs
+++ b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs
@@ -8,10 +8,10 @@
 
 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>
+    /// <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<long, PgpPublicKeyRing> m_pubRings;
@@ -66,7 +66,7 @@ public int Count
 		/// <summary>Allow enumeration of the public key rings making up this collection.</summary>
         public IEnumerable<PgpPublicKeyRing> GetKeyRings()
         {
-			return CollectionUtilities.Proxy(m_pubRings.Values);
+			return CollectionUtilities.Proxy(KeyRings);
         }
 
 		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
@@ -96,7 +96,7 @@ public IEnumerable<PgpPublicKeyRing> GetKeyRings(string userID, bool matchPartia
 			var compareInfo = CultureInfo.InvariantCulture.CompareInfo;
 			var compareOptions = ignoreCase ? CompareOptions.OrdinalIgnoreCase : CompareOptions.Ordinal;
 
-			foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+			foreach (PgpPublicKeyRing pubRing in KeyRings)
 			{
 				foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds())
 				{
@@ -118,7 +118,7 @@ public IEnumerable<PgpPublicKeyRing> GetKeyRings(string userID, bool matchPartia
 		/// <param name="keyId">The ID of the public key to return.</param>
         public PgpPublicKey GetPublicKey(long keyId)
         {
-            foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+            foreach (PgpPublicKeyRing pubRing in KeyRings)
             {
                 PgpPublicKey pub = pubRing.GetPublicKey(keyId);
 				if (pub != null)
@@ -135,7 +135,7 @@ public PgpPublicKeyRing GetPublicKeyRing(long keyId)
 			if (m_pubRings.TryGetValue(keyId, out var keyRing))
 				return keyRing;
 
-			foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+			foreach (PgpPublicKeyRing pubRing in KeyRings)
             {
                 if (pubRing.GetPublicKey(keyId) != null)
                     return pubRing;
@@ -144,11 +144,39 @@ public PgpPublicKeyRing GetPublicKeyRing(long keyId)
 			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)
+        /// <summary>Return the PGP public key associated with the given key fingerprint.</summary>
+        /// <param name="fingerprint">the public key fingerprint to match against.</param>
+        public PgpPublicKey GetPublicKey(byte[] fingerprint)
+        {
+            foreach (PgpPublicKeyRing pubRing in KeyRings)
+            {
+                PgpPublicKey pub = pubRing.GetPublicKey(fingerprint);
+                if (pub != null)
+                    return pub;
+            }
+
+            return null;
+        }
+
+        /// <summary>Return the public key ring which contains the key associated with the given key fingerprint.
+        /// </summary>
+        /// <param name="fingerprint">the public key fingerprint to match against.</param>
+        public PgpPublicKeyRing GetPublicKeyRing(byte[] fingerprint)
+        {
+            foreach (PgpPublicKeyRing pubRing in KeyRings)
+            {
+                if (pubRing.GetPublicKey(fingerprint) != 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;
 		}
@@ -170,14 +198,16 @@ public void Encode(Stream outStr)
             }
         }
 
-		/// <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>
+        private ICollection<PgpPublicKeyRing> KeyRings => m_pubRings.Values;
+
+        /// <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)
         {
diff --git a/crypto/src/openpgp/PgpSecretKeyRing.cs b/crypto/src/openpgp/PgpSecretKeyRing.cs
index a070aa132e..ff644545f2 100644
--- a/crypto/src/openpgp/PgpSecretKeyRing.cs
+++ b/crypto/src/openpgp/PgpSecretKeyRing.cs
@@ -3,7 +3,6 @@
 using System.IO;
 
 using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
@@ -115,6 +114,38 @@ public PgpPublicKey GetPublicKey()
             return keys[0].PublicKey;
         }
 
+        /// <summary>Return the public key referred to by the passed in keyID if it is present.</summary>
+        public PgpPublicKey GetPublicKey(long keyID)
+        {
+            PgpSecretKey key = GetSecretKey(keyID);
+            if (key != null)
+                return key.PublicKey;
+
+            foreach (PgpPublicKey k in extraPubKeys)
+            {
+                if (keyID == k.KeyId)
+                    return k;
+            }
+
+            return null;
+        }
+
+        /// <summary>Return the public key with the passed in fingerprint if it is present.</summary>
+        public PgpPublicKey GetPublicKey(byte[] fingerprint)
+        {
+            PgpSecretKey key = GetSecretKey(fingerprint);
+            if (key != null)
+                return key.PublicKey;
+
+            foreach (PgpPublicKey k in extraPubKeys)
+            {
+                if (k.HasFingerprint(fingerprint))
+                    return k;
+            }
+
+            return null;
+        }
+
         /**
          * Return any keys carrying a signature issued by the key represented by keyID.
          *
@@ -165,6 +196,7 @@ public IEnumerable<PgpSecretKey> GetSecretKeys()
             return CollectionUtilities.Proxy(keys);
         }
 
+        /// <summary>Return the secret key referred to by the passed in keyID if it is present.</summary>
         public PgpSecretKey GetSecretKey(long keyId)
         {
             foreach (PgpSecretKey k in keys)
@@ -176,6 +208,18 @@ public PgpSecretKey GetSecretKey(long keyId)
             return null;
         }
 
+        /// <summary>Return the secret key associated with the passed in fingerprint if it is present.</summary>
+        public PgpSecretKey GetSecretKey(byte[] fingerprint)
+        {
+            foreach (PgpSecretKey k in keys)
+            {
+                if (k.PublicKey.HasFingerprint(fingerprint))
+                    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
@@ -247,7 +291,7 @@ public static PgpSecretKeyRing CopyWithNewPassword(
         {
             var newKeys = new List<PgpSecretKey>(ring.keys.Count);
 
-            foreach (PgpSecretKey secretKey in ring.GetSecretKeys())
+            foreach (PgpSecretKey secretKey in ring.keys)
             {
                 if (secretKey.IsPrivateKeyEmpty)
                 {
diff --git a/crypto/src/openpgp/PgpSecretKeyRingBundle.cs b/crypto/src/openpgp/PgpSecretKeyRingBundle.cs
index 695c882b70..fe9a2461a7 100644
--- a/crypto/src/openpgp/PgpSecretKeyRingBundle.cs
+++ b/crypto/src/openpgp/PgpSecretKeyRingBundle.cs
@@ -8,10 +8,10 @@
 
 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>
+    /// <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<long, PgpSecretKeyRing> m_secretRings;
@@ -66,7 +66,7 @@ public int Count
 		/// <summary>Allow enumeration of the secret key rings making up this collection.</summary>
 		public IEnumerable<PgpSecretKeyRing> GetKeyRings()
         {
-            return CollectionUtilities.Proxy(m_secretRings.Values);
+            return CollectionUtilities.Proxy(KeyRings);
         }
 
 		/// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
@@ -96,7 +96,7 @@ public IEnumerable<PgpSecretKeyRing> GetKeyRings(string userID, bool matchPartia
 			var compareInfo = CultureInfo.InvariantCulture.CompareInfo;
 			var compareOptions = ignoreCase ? CompareOptions.OrdinalIgnoreCase : CompareOptions.Ordinal;
 
-			foreach (PgpSecretKeyRing secRing in GetKeyRings())
+			foreach (PgpSecretKeyRing secRing in KeyRings)
 			{
 				foreach (string nextUserID in secRing.GetSecretKey().UserIds)
 				{
@@ -118,7 +118,7 @@ public IEnumerable<PgpSecretKeyRing> GetKeyRings(string userID, bool matchPartia
 		/// <param name="keyId">The ID of the secret key to return.</param>
 		public PgpSecretKey GetSecretKey(long keyId)
         {
-            foreach (PgpSecretKeyRing secRing in GetKeyRings())
+            foreach (PgpSecretKeyRing secRing in KeyRings)
             {
                 PgpSecretKey sec = secRing.GetSecretKey(keyId);
 				if (sec != null)
@@ -135,7 +135,7 @@ public PgpSecretKeyRing GetSecretKeyRing(long keyId)
 			if (m_secretRings.TryGetValue(keyId, out var keyRing))
 				return keyRing;
 
-			foreach (PgpSecretKeyRing secretRing in GetKeyRings())
+			foreach (PgpSecretKeyRing secretRing in KeyRings)
             {
                 if (secretRing.GetSecretKey(keyId) != null)
                     return secretRing;
@@ -170,14 +170,16 @@ public void Encode(Stream outStr)
             }
         }
 
-		/// <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>
+        private ICollection<PgpSecretKeyRing> KeyRings => m_secretRings.Values;
+
+        /// <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)
         {
diff --git a/crypto/test/src/openpgp/test/PGPRSATest.cs b/crypto/test/src/openpgp/test/PGPRSATest.cs
index 6de95fbeb8..56d761c166 100644
--- a/crypto/test/src/openpgp/test/PGPRSATest.cs
+++ b/crypto/test/src/openpgp/test/PGPRSATest.cs
@@ -325,7 +325,12 @@ private void FingerPrintTest()
 
             PgpPublicKey pubKey = pgpPub.GetPublicKey();
 
-            if (!Arrays.AreEqual(pubKey.GetFingerprint(), Hex.Decode("4FFB9F0884266C715D1CEAC804A3BBFA")))
+            byte[] expectedVersion3 = Hex.Decode("4FFB9F0884266C715D1CEAC804A3BBFA");
+            if (!Arrays.AreEqual(pubKey.GetFingerprint(), expectedVersion3))
+            {
+                Fail("version 3 fingerprint test failed");
+            }
+            if (!pubKey.HasFingerprint(expectedVersion3))
             {
                 Fail("version 3 fingerprint test failed");
             }
@@ -337,10 +342,15 @@ private void FingerPrintTest()
 
             pubKey = pgpPub.GetPublicKey();
 
-            if (!Arrays.AreEqual(pubKey.GetFingerprint(), Hex.Decode("3062363c1046a01a751946bb35586146fdf3f373")))
+            byte[] expectedVersion4 = Hex.Decode("3062363c1046a01a751946bb35586146fdf3f373");
+            if (!Arrays.AreEqual(pubKey.GetFingerprint(), expectedVersion4))
             {
                Fail("version 4 fingerprint test failed");
             }
+            if (!pubKey.HasFingerprint(expectedVersion4))
+            {
+                Fail("version 4 fingerprint test failed");
+            }
         }
 
         private void MixedTest(
diff --git a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
index f67d19a7fe..c3cdd53f02 100644
--- a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
+++ b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
@@ -202,8 +202,10 @@ public override void PerformTest()
 
             PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(aIn);
 
-            IsTrue(AreEqual(Hex.Decode("EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E"),
-                pubKeyRing.GetPublicKey().GetFingerprint()));
+            IsTrue(AreEqual(pubKeyRing.GetPublicKey().GetFingerprint(),
+                Hex.Decode("EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E")));
+            IsTrue(pubKeyRing.GetPublicKey().HasFingerprint(
+                Hex.Decode("EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E")));
 
             aIn = new ArmoredInputStream(new MemoryStream(Strings.ToByteArray(edDSASecretKey), false));
 

From ea6a5b32e615debcfa5ca81421dbffc7bdc03d46 Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 5 Dec 2024 18:00:53 +0100
Subject: [PATCH 36/37] followup changes

---
 crypto/src/bcpg/ArmoredOutputStream.cs               | 6 +-----
 crypto/src/bcpg/PublicKeyEncSessionPacket.cs         | 1 +
 crypto/src/crypto/generators/Argon2BytesGenerator.cs | 7 +------
 crypto/src/openpgp/PgpPublicKey.cs                   | 5 -----
 crypto/test/src/openpgp/test/PGPArmoredTest.cs       | 4 ++--
 5 files changed, 5 insertions(+), 18 deletions(-)

diff --git a/crypto/src/bcpg/ArmoredOutputStream.cs b/crypto/src/bcpg/ArmoredOutputStream.cs
index 6c57181baa..a63200f1dc 100644
--- a/crypto/src/bcpg/ArmoredOutputStream.cs
+++ b/crypto/src/bcpg/ArmoredOutputStream.cs
@@ -17,7 +17,6 @@ public class ArmoredOutputStream
         : BaseOutputStream
     {
         public static readonly string HeaderVersion = "Version";
-        private readonly bool showVersion;
 
         private static readonly byte[] encodingTable =
         {
@@ -368,10 +367,7 @@ public override void WriteByte(byte value)
 
                 DoWrite(headerStart + type + headerTail + NewLine);
 
-                // https://www.rfc-editor.org/rfc/rfc9580#name-version-armor-header
-                // To minimize metadata, implementations SHOULD NOT emit this key and its corresponding value except
-                // for debugging purposes with explicit user consent.
-                if (showVersion && m_headers.TryGetValue(HeaderVersion, out var versionHeaders))
+                if (m_headers.TryGetValue(HeaderVersion, out var versionHeaders))
                 {
                     WriteHeaderEntry(HeaderVersion, versionHeaders[0]);
                 }
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index 9c35a9cd97..c3edf00bfe 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -3,6 +3,7 @@
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 using Org.BouncyCastle.Crypto.Utilities;
+using System;
 
 namespace Org.BouncyCastle.Bcpg
 {
diff --git a/crypto/src/crypto/generators/Argon2BytesGenerator.cs b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
index 82d16a7034..8e182f85e0 100644
--- a/crypto/src/crypto/generators/Argon2BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
@@ -150,12 +150,8 @@ private void Reset()
 
         private void FillMemoryBlocks()
         {
-            FillBlock filler = new FillBlock();
-            Position position = new Position();
             for (int pass = 0; pass < parameters.Iterations; ++pass)
             {
-                position.pass = pass;
-
                 for (int slice = 0; slice < Argon2SyncPoints; ++slice)
                 {
                     if (m_taskFactory == null || parameters.Parallelism <= 1)
@@ -455,8 +451,6 @@ private static void Hash(ReadOnlySpan<byte> input, Span<byte> output)
                 digest.BlockUpdate(outLenBytes, 0, outLenBytes.Length);
                 digest.BlockUpdate(input, 0, input.Length);
                 digest.DoFinal(outBuffer, 0);
-
-                int halfLen = blake2bLength / 2, outPos = outOff;
                 Array.Copy(outBuffer, 0, output, outPos, halfLen);
 #endif
                 outPos += halfLen;
@@ -776,3 +770,4 @@ internal Position(int pass, int slice, int lane)
         }
     }
 }
+
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index f674403a77..bd93264afc 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -576,11 +576,6 @@ public bool HasFingerprint(byte[] fingerprint)
             return Arrays.AreEqual(this.fingerprint, fingerprint);
         }
 
-        public bool HasFingerprint(byte[] fingerprint)
-        {
-            return Arrays.AreEqual(this.fingerprint, fingerprint);
-        }
-
         /// <summary>
         /// Check if this key has an algorithm type that makes it suitable to use for encryption.
         /// </summary>
diff --git a/crypto/test/src/openpgp/test/PGPArmoredTest.cs b/crypto/test/src/openpgp/test/PGPArmoredTest.cs
index 195c1ca248..2cacf1366d 100644
--- a/crypto/test/src/openpgp/test/PGPArmoredTest.cs
+++ b/crypto/test/src/openpgp/test/PGPArmoredTest.cs
@@ -128,7 +128,7 @@ private void VersionIsOptionalTest()
 		{
 			using (MemoryStream bOut = new MemoryStream())
 			{
-				using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut))
+				using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut, addVersionHeader: false))
 				{
 					aOut.Write(sample, 0, sample.Length);
 				}
@@ -146,7 +146,7 @@ private void VersionIsOptionalTest()
 
             using (MemoryStream bOut = new MemoryStream())
             {
-                using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut, showVersion: true))
+                using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut, addVersionHeader: true))
                 {
                     aOut.Write(sample, 0, sample.Length);
                 }

From 8ad4e843536dc1ef84745ef290fd2cc52252b79c Mon Sep 17 00:00:00 2001
From: Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>
Date: Thu, 5 Dec 2024 18:34:36 +0100
Subject: [PATCH 37/37] BitStrength for RFC 9580 pubkey algorithms

---
 crypto/src/openpgp/PgpPublicKey.cs                   | 8 ++++++++
 crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs | 4 +++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index bd93264afc..4d6df9c25a 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -217,6 +217,14 @@ private void Init()
                         this.keyStrength = -1; // unknown
                     }
                 }
+                else if (key is Ed25519PublicBcpgKey || key is X25519PublicBcpgKey)
+                {
+                    this.keyStrength = 256;
+                }
+                else if (key is Ed448PublicBcpgKey || key is X448PublicBcpgKey)
+                {
+                    this.keyStrength = 448;
+                }
             }
         }
 
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
index 7677a2c27d..27606cbe17 100644
--- a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -396,7 +396,7 @@ public void Version4Ed25519LegacyPubkeySampleTest()
 
             IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
             IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20140819142827");
-
+            IsEquals(pubKey.BitStrength, 256);
             byte[] expectedFingerprint = Hex.Decode("C959BDBAFA32A2F89A153B678CFDE12197965A9A");
             IsEquals((ulong)pubKey.KeyId, 0x8CFDE12197965A9A);
             IsTrue("wrong fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
@@ -622,6 +622,7 @@ public void Version6Ed25519KeyPairCreationTest()
 
             IsEquals(keypair.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
             IsEquals(keypair.PublicKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+            IsEquals(keypair.PublicKey.BitStrength, 256);
             byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
             IsEquals((ulong)keypair.KeyId, 0xCB186C4F0609A697);
             IsTrue("wrong master key fingerprint", AreEqual(keypair.PublicKey.GetFingerprint(), expectedFingerprint));
@@ -744,6 +745,7 @@ public void Version6Ed448KeyPairCreationTest()
             PgpKeyPair keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed448, kp, now);
             IsEquals(keypair.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed448);
             IsEquals(keypair.PublicKey.CreationTime.ToString("yyyyMMddHHmmss"), now.ToString("yyyyMMddHHmmss"));
+            IsEquals(keypair.PublicKey.BitStrength, 448);
             long keyId = keypair.PublicKey.KeyId;
             byte[] fpr = keypair.PublicKey.GetFingerprint();
             IsEquals(fpr.Length, 32);