protected internal Header GenerateHeader(
            ISymmetricEncryptionProviderOptions options,
            ReadOnlySpan <byte> symmetricKey = default,
            ReadOnlySpan <byte> privateKey   = default,
            ReadOnlySpan <byte> metadata     = default,
            IEncryptionProvider symmetricKeyEncryptionProvider = null)
        {
            Check.ArgNotNull(options, nameof(options));
            privateKey = !privateKey.IsEmpty ? privateKey : options.Key.Memory.Span;

            var memoryPool = MemoryPool <byte> .Shared;
            ReadOnlySpan <byte> signingKey = default;

            if (options.SigningKey != null)
            {
                signingKey = options.SigningKey.Memory.Span;
            }

            // header values
            // 1. version
            // 2. metadataSize
            // 3. iterations
            // 4. symmetricSaltSize
            // 5. signingSaltSize
            // 6. ivSize
            // 7. symmetricKeySize
            // 8. hashSize

            // header values
            // 1. metadata (optional)
            // 2. symmetricSalt (optional)
            // 3. signingSalt (optional)
            // 4. iv
            // 5. symmetricKey (optional)
            // 6. hash
            var header = new HeaderV1
            {
                MetaDataSize = metadata.Length,
            };

            bool privateKeyEmpty   = privateKey == null || privateKey.IsEmpty;
            bool symmetricKeyEmpty = symmetricKey == null || symmetricKey.IsEmpty;

            if (privateKeyEmpty && symmetricKeyEmpty)
            {
                throw new ArgumentNullException(nameof(privateKey), "privateKey or symmetricKey must have a value");
            }

            if (!options.SkipSigning && privateKeyEmpty && signingKey.IsEmpty)
            {
                throw new ArgumentNullException(nameof(privateKey),
                                                "privateKey must have a value or options.SigningKey must have a value or options.SkipSigning must be true");
            }

            if (!privateKeyEmpty)
            {
                header.SymmetricSaltSize = (short)(options.SaltSize / 8);

                if (!options.SkipSigning && (signingKey == null || signingKey.IsEmpty))
                {
                    header.SigningSaltSize = (short)(options.SaltSize / 8);
                    this.signingAlgorithm  = this.signingAlgorithm ?? CreateSigningAlgorithm(options);
                }
            }

            if (!symmetricKeyEmpty)
            {
                header.SymmetricKeySize = (short)(options.KeySize / 8);
            }

            this.algorithm = this.algorithm ?? CreateSymmetricAlgorithm(options);
            this.algorithm.GenerateIV();
            var iv = this.algorithm.IV;

            header.IvSize = (short)iv.Length;
            {
                var buffer = MemoryPool <byte> .Shared.Rent(iv.Length);

                iv.CopyTo(buffer.Memory.Span);
                header.IV = buffer;
            }

            header.HashSize   = (short)(this.signingAlgorithm.HashSize / 8);
            header.Iterations = options.Iterations;
            using (var ms = new MemoryStream(new byte[header.HeaderSize]))
                using (var bw = new BinaryWriter(ms, Utf8Options.NoBom, false))
                {
                    if (!symmetricKey.IsEmpty && symmetricKeyEncryptionProvider != null)
                    {
                        symmetricKey            = symmetricKeyEncryptionProvider.Encrypt(symmetricKey);
                        header.SymmetricKeySize = (short)symmetricKey.Length;
                    }

                    header.SymmetricAlgorithmType = options.SymmetricAlgorithm;
                    header.KeyedHashAlgorithmType = options.KeyedHashedAlgorithm;

                    bw.Write(header.Version);
                    bw.Write((short)header.SymmetricAlgorithmType);
                    bw.Write((short)header.KeyedHashAlgorithmType);
                    bw.Write(header.MetaDataSize);
                    bw.Write(header.Iterations);
                    bw.Write(header.SymmetricSaltSize);
                    bw.Write(header.SigningSaltSize);
                    bw.Write(header.IvSize);
                    bw.Write(header.SymmetricKeySize);
                    bw.Write(header.HashSize);

                    if (privateKey != null)
                    {
                        ReadOnlySpan <byte> symmetricSalt = GenerateSalt(header.SymmetricSaltSize);
                        if (symmetricSalt.Length != header.SymmetricSaltSize)
                        {
                            throw new Exception("bad length");
                        }

                        using (var generator = new NerdyRfc2898DeriveBytes(privateKey, symmetricSalt, options.Iterations, HashAlgorithmName.SHA256))
                        {
                            ReadOnlySpan <byte> bytes = generator.GetBytes(options.KeySize / 8);
                            header.SymmetricKey = memoryPool
                                                  .Rent(bytes.Length)
                                                  .CopyFrom(bytes);
#if NET461 || NET451
                            var next = ArrayPool <byte> .Shared.Rent(symmetricSalt.Length);

                            symmetricSalt.CopyTo(next);
                            bw.Write(next, 0, next.Length);
                            next.Clear();
                            ArrayPool <byte> .Shared.Return(next);
#else
                            bw.Write(symmetricSalt);
#endif
                        }

                        if (!options.SkipSigning || !signingKey.IsEmpty)
                        {
                            var signingSalt = GenerateSalt(header.SigningSaltSize);

                            using (var generator = new NerdyRfc2898DeriveBytes(
                                       privateKey, signingSalt, options.Iterations, HashAlgorithmName.SHA256))
                            {
                                signingKey        = generator.GetBytes(options.KeySize / 8);
                                header.SigningKey = memoryPool
                                                    .Rent(signingKey.Length)
                                                    .CopyFrom(signingKey);

                                bw.Write(signingSalt);
                            }

                            signingSalt.Clear();
                        }
                    }

                    bw.Write(iv);
                    if (!symmetricKeyEmpty)
                    {
#if NET461 || NET451
                        var next = ArrayPool <byte> .Shared.Rent(symmetricKey.Length);

                        symmetricKey.CopyTo(next);
                        bw.Write(next, 0, next.Length);
                        next.Clear();
                        ArrayPool <byte> .Shared.Return(next);
#else
                        bw.Write(symmetricKey);
#endif
                    }

                    bw.Flush();
                    ms.Flush();
                    header.Position = ms.Position;

                    ReadOnlySpan <byte> data = ms.ToArray();
                    header.Bytes = memoryPool
                                   .Rent(header.HeaderSize)
                                   .CopyFrom(data);
                }

            return(header);
        }
 public static ReadOnlySpan <byte> Pbkdf2(ReadOnlySpan <byte> password, ReadOnlySpan <byte> salt, int iterations = 64000, int outputBytes = 32)
 {
     using var pbkdf2 = new NerdyRfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256);
     return(pbkdf2.GetBytes(outputBytes));
 }
        protected internal Header ReadHeader(
            Stream reader,
            ISymmetricEncryptionProviderOptions options,
            ReadOnlySpan <byte> privateKey = default,
            IEncryptionProvider symmetricKeyEncryptionProvider = null)
        {
            Check.ArgNotNull(reader, nameof(reader));
            Check.ArgNotNull(options, nameof(options));

            ReadOnlySpan <byte> signingKey = default;

            if (options.SigningKey != null)
            {
                signingKey = options.SigningKey.Memory.Span;
            }

            var memoryPool = MemoryPool <byte> .Shared;

            using (var ms = new MemoryStream())
                using (var bw = new BinaryWriter(ms, Utf8Options.NoBom, true))
                    using (var br = new BinaryReader(reader, Utf8Options.NoBom, true))
                    {
                        var version = br.ReadInt16();
                        bw.Write(version);
                        Header header = null;
                        switch (version)
                        {
                        case 1:
                        default:
                            header = new HeaderV1();
                            break;
                        }

                        // header shorts/ints
                        // 1. version
                        // 2. algo
                        // 3. signing,
                        // 4. metadataSize
                        // 5  iterations
                        // 6. symmetricSaltSize
                        // 7. signingSaltSize
                        // 8. ivSize
                        // 9. symmetricKeySize
                        // 10. hashSize

                        // header values
                        // 1. metadata
                        // 2. symmetricSalt
                        // 3. signingSalt
                        // 4. iv
                        // 5. symmetricKey
                        // 6. hash
                        header.SymmetricAlgorithmType = (SymmetricAlgorithmType)br.ReadInt16();
                        header.KeyedHashAlgorithmType = (KeyedHashAlgorithmType)br.ReadInt16();
                        header.MetaDataSize           = br.ReadInt32();
                        header.Iterations             = br.ReadInt32();
                        header.SymmetricSaltSize      = br.ReadInt16();
                        header.SigningSaltSize        = br.ReadInt16();
                        header.IvSize           = br.ReadInt16();
                        header.SymmetricKeySize = br.ReadInt16();
                        header.HashSize         = br.ReadInt16();

                        bw.Write((short)header.SymmetricAlgorithmType);
                        bw.Write((short)header.KeyedHashAlgorithmType);
                        bw.Write(header.MetaDataSize);
                        bw.Write(header.Iterations);
                        bw.Write(header.SymmetricSaltSize);
                        bw.Write(header.SigningSaltSize);
                        bw.Write(header.IvSize);
                        bw.Write(header.SymmetricKeySize);
                        bw.Write(header.HashSize);

                        if (options.SymmetricAlgorithm != header.SymmetricAlgorithmType)
                        {
                            options.SymmetricAlgorithm = header.SymmetricAlgorithmType;
                            this.algorithm             = null;
                        }

                        if (options.KeyedHashedAlgorithm != header.KeyedHashAlgorithmType)
                        {
                            options.KeyedHashedAlgorithm = header.KeyedHashAlgorithmType;
                            this.signingAlgorithm        = null;
                        }

                        byte[] metadata                 = null;
                        byte[] symmetricSalt            = null;
                        ReadOnlySpan <byte> signingSalt = default;
                        byte[] iv = null;
                        ReadOnlySpan <byte> symmetricKey = default;
                        byte[] hash = null;

                        if (header.MetaDataSize > 0)
                        {
                            metadata = br.ReadBytes(header.MetaDataSize);
                            bw.Write(metadata);
                        }

                        if (header.SymmetricSaltSize > 0)
                        {
                            symmetricSalt = br.ReadBytes(header.SymmetricSaltSize);
                            bw.Write(symmetricSalt);
                        }

                        if (header.SigningSaltSize > 0)
                        {
                            signingSalt = br.ReadBytes(header.SigningSaltSize);
#if NET461 || NET451
                            var next = ArrayPool <byte> .Shared.Rent(signingSalt.Length);

                            signingSalt.CopyTo(next);
                            bw.Write(next, 0, next.Length);
                            next.Clear();
                            ArrayPool <byte> .Shared.Return(next);
#else
                            bw.Write(signingSalt);
#endif
                        }

                        if (header.IvSize > 0)
                        {
                            iv = br.ReadBytes(header.IvSize);
                            bw.Write(iv);
                        }

                        if (header.SymmetricKeySize > 0)
                        {
                            symmetricKey = br.ReadBytes(header.SymmetricKeySize);
#if NET461 || NET451
                            var next = ArrayPool <byte> .Shared.Rent(symmetricKey.Length);

                            symmetricKey.CopyTo(next);
                            bw.Write(next, 0, next.Length);
                            next.Clear();
                            ArrayPool <byte> .Shared.Return(next);
#else
                            bw.Write(symmetricKey);
#endif
                        }

                        if (header.HashSize > 0)
                        {
                            hash = br.ReadBytes(header.HashSize);
                            bw.Write(hash);
                        }

                        bw.Flush();
                        ms.Flush();
                        {
                            ReadOnlySpan <byte> bytes = ms.ToArray();
                            header.Bytes = memoryPool
                                           .Rent(bytes.Length)
                                           .CopyFrom(bytes);
                        }

                        header.Position = reader.Position;

                        if (symmetricKeyEncryptionProvider != null)
                        {
                            symmetricKey = symmetricKeyEncryptionProvider.Decrypt(symmetricKey);
                        }

                        if (symmetricKey == null && privateKey.IsEmpty)
                        {
                            throw new ArgumentNullException(nameof(privateKey),
                                                            "privateKey or symmetricKey must have a value");
                        }

                        if (!options.SkipSigning && privateKey == null && signingKey.IsEmpty)
                        {
                            throw new ArgumentNullException(nameof(privateKey),
                                                            "privateKey must have a value or options.SigningKey must have a value or options.SkipSigning must be true");
                        }

                        if (symmetricKey == null)
                        {
                            if (symmetricSalt == null)
                            {
                                throw new InvalidOperationException("symmetricSalt for the privateKey could not be retrieved");
                            }

                            using (var generator = new NerdyRfc2898DeriveBytes(privateKey, symmetricSalt, header.Iterations, HashAlgorithmName.SHA256))
                            {
                                ReadOnlySpan <byte> bytes = generator.GetBytes(options.KeySize / 8);
                                header.SymmetricKey = memoryPool
                                                      .Rent(bytes.Length)
                                                      .CopyFrom(bytes);
                            }
                        }

                        if (!options.SkipSigning && (signingKey == null || signingKey.IsEmpty))
                        {
                            if (signingSalt == null)
                            {
                                throw new InvalidOperationException("symmetricSalt for the privateKey could not be retrieved");
                            }

                            var key = !symmetricKey.IsEmpty ? symmetricKey : privateKey;
                            using (var generator = new NerdyRfc2898DeriveBytes(key, signingSalt, header.Iterations, HashAlgorithmName.SHA256))
                            {
                                generator.IterationCount = header.Iterations;
                                ReadOnlySpan <byte> bytes = generator.GetBytes(options.KeySize / 8);
                                header.SigningKey = memoryPool
                                                    .Rent(bytes.Length)
                                                    .CopyFrom(bytes);
                            }
                        }

                        if (header.SymmetricKeySize > 0)
                        {
                            header.SymmetricKey = memoryPool
                                                  .Rent(header.SymmetricKeySize)
                                                  .CopyFrom(symmetricKey);
                        }

                        header.IV = memoryPool
                                    .Rent(iv.Length)
                                    .CopyFrom(iv);

                        header.Hash = memoryPool
                                      .Rent(header.HashSize)
                                      .CopyFrom(hash);

                        return(header);
                    }
        }