/// <summary>
        /// Encrypt a file
        /// </summary>
        /// <param name="inputStream">File to read data from</param>
        /// <param name="publicKey"></param>
        /// <returns>EncryptedPacket with all info for receiver to decrypt</returns>
        public static EncryptedPacket EncryptFile(Stream inputStream, RSAParameters publicKey)
        {
            MemoryStream outputStream = new MemoryStream();

            byte[] aesKey = Random.GetNumbers(32);
            byte[] iv     = Random.GetNumbers(16);

            EncryptedPacket encryptedPacket = new EncryptedPacket
            {
                DataType            = DataType.File,
                EncryptedSessionKey = AsymmetricEncryption.Encrypt(aesKey, publicKey),
                Iv = iv
            };

            try
            {
                // create streamers
                using (HashStreamer hashStreamer = new HashStreamer(aesKey))
                    using (SymmetricStreamer symmetricStreamer = new SymmetricStreamer(aesKey, iv))
                    {
                        // create streams
                        var hmacStream      = hashStreamer.HmacShaStream(outputStream, aesKey, CryptoStreamMode.Write);
                        var encryptedStream = symmetricStreamer.EncryptStream(hmacStream, CryptoStreamMode.Write);

                        // read all data
                        inputStream.CopyTo(encryptedStream);

                        // get hash
                        encryptedPacket.Hmac = hashStreamer.Hash;

                        // create signature
                        encryptedPacket.Signature = AsymmetricEncryption.Sign(encryptedPacket.Hmac);

                        // close file streams
                        inputStream.Close();
                    }
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while encrypting stream", e);
            }

            // read encrypted data from memory stream
            encryptedPacket.EncryptedData = outputStream.ToArray();

            return(encryptedPacket);
        }
        /// <summary>
        /// Decrypt an encrypted packet of data
        /// </summary>
        /// <param name="encryptedPacket">Packet containing data</param>
        /// <param name="publicKey">Public RSA key of sender</param>
        /// <param name="skipSignature"></param>
        /// <exception cref="CryptoException"></exception>
        /// <returns>Decrypted data of packet</returns>
        public static byte[] Decrypt(EncryptedPacket encryptedPacket, RSAParameters publicKey, bool skipSignature = false)
        {
            // decrypt AES session key with private RSA key
            byte[] sessionKey;
            try
            {
                sessionKey = AsymmetricEncryption.Decrypt(encryptedPacket.EncryptedSessionKey);
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while decryption AES session key", e);
            }

            // rehash data with session key
            byte[] hashedData;
            try
            {
                hashedData = Hashing.HmacSha(encryptedPacket.EncryptedData, sessionKey);
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while hashing data", e);
            }

            // check hash
            bool checkedHash = Hashing.CompareHashes(hashedData, encryptedPacket.Hmac);

            if (!checkedHash)
            {
                throw new CryptoException("Hash validation failed, data may have been modified!");
            }

            // check signature if required
            if (!skipSignature)
            {
                bool checkedSignature;
                try
                {
                    checkedSignature = AsymmetricEncryption.CheckSignature(encryptedPacket.Signature, publicKey, encryptedPacket.Hmac);
                }
                catch (CryptographicException e)
                {
                    throw new CryptoException("Error while checking signature", e);
                }

                if (!checkedSignature)
                {
                    throw new CryptoException("Signature check failed, packet may have come from a different sender.");
                }
            }

            // decrypt data with AES key and IV
            try
            {
                return(SymmetricEncryption.Decrypt(encryptedPacket.EncryptedData, sessionKey, encryptedPacket.Iv));
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while decrypting data", e);
            }
        }
        /// <summary>
        /// Decrypt a file and write it to another file
        /// </summary>
        /// <param name="inputStream">File to read encrypted packet from</param>
        /// <param name="outputStream">File to write decrypted data to</param>
        /// <param name="publicKey">Public key of sender</param>
        public static async Task <bool> DecryptFile(Stream inputStream, Stream outputStream, RSAParameters publicKey)
        {
            // roll back stream to start
            inputStream.Position = 0;

            // read data type
            inputStream.ReadByte();

            // read and decrypt aes key
            byte[] encryptedAesKeyLengthBuffer = new byte[2];
            await inputStream.ReadAsync(encryptedAesKeyLengthBuffer, 0, 2);

            await inputStream.FlushAsync();

            ushort encryptedAesKeyLength = BitConverter.ToUInt16(encryptedAesKeyLengthBuffer, 0);

            byte[] encryptedAesKey = new byte[encryptedAesKeyLength];
            await inputStream.ReadAsync(encryptedAesKey, 0, encryptedAesKeyLength);

            await inputStream.FlushAsync();

            byte[] aesKey = AsymmetricEncryption.Decrypt(encryptedAesKey);

            // read aes iv
            byte[] iv = new byte[16];
            await inputStream.ReadAsync(iv, 0, 16);

            await inputStream.FlushAsync();

            // read hash
            byte[] hmac = new byte[64];
            await inputStream.ReadAsync(hmac, 0, 64);

            await inputStream.FlushAsync();

            // read signature
            int signatureLength = AsymmetricEncryption.PublicKey.Modulus.Length;

            byte[] signature = new byte[signatureLength];
            await inputStream.ReadAsync(signature, 0, signatureLength);

            await inputStream.FlushAsync();

            // check signature
            if (!AsymmetricEncryption.CheckSignature(signature, publicKey, hmac))
            {
                throw new CryptoException("Signature check failed, file could have been sent by somebody else!");
            }

            try
            {
                // create streamers
                using (HashStreamer hashStreamer = new HashStreamer(aesKey))
                    using (SymmetricStreamer symmetricStreamer = new SymmetricStreamer(aesKey, iv))
                    {
                        long currentPos = inputStream.Position;

                        // create streams
                        var hmacStream    = hashStreamer.HmacShaStream(new MemoryStream(), aesKey, CryptoStreamMode.Write);
                        var decryptStream = symmetricStreamer.DecryptStream(outputStream, CryptoStreamMode.Write);

                        // create hash
                        await inputStream.CopyToAsync(hmacStream);

                        inputStream.Position = currentPos;
                        await inputStream.FlushAsync();

                        // skip decrypting if the hash isn't correct
                        if (!Hashing.CompareHashes(hashStreamer.Hash, hmac))
                        {
                            return(false);
                        }

                        // decrypt the actual data
                        await inputStream.CopyToAsync(decryptStream);

                        await inputStream.FlushAsync();

                        // Something something, absolute bullshit
                        inputStream.Position = inputStream.Seek(-16, SeekOrigin.End);
                        await inputStream.CopyToAsync(decryptStream);

                        // flush it all
                        await outputStream.FlushAsync();

                        return(true);
                    }
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while decrypting stream", e);
            }
        }
        /// <summary>
        /// Encrypt a file and store all data in another file
        /// </summary>
        /// <param name="inputStream">Stream to read data from</param>
        /// <param name="outputStream">Stream to write encrypted packet to</param>
        /// <param name="publicKey"></param>
        /// <returns>Length of output stream</returns>
        public static async Task <long> EncryptFile(Stream inputStream, Stream outputStream, RSAParameters publicKey)
        {
            byte[] aesKey = Random.GetNumbers(32);
            byte[] iv     = Random.GetNumbers(16);

            // write header
            byte[] encryptedSessionKey = AsymmetricEncryption.Encrypt(aesKey, publicKey);

            List <byte> firstData = new List <byte>();

            firstData.Add((byte)DataType.File);
            firstData.AddRange(BitConverter.GetBytes((ushort)encryptedSessionKey.Length));
            firstData.AddRange(encryptedSessionKey);
            firstData.AddRange(iv);

            await outputStream.WriteAsync(firstData.ToArray(), 0, firstData.Count); // write datatype, encrypted aes key and aes iv

            await outputStream.FlushAsync();

            await outputStream.WriteAsync(new byte[64], 0, 64); // reserve space for hmac

            await outputStream.FlushAsync();

            int signatureLength = publicKey.Modulus.Length;
            await outputStream.WriteAsync(new byte[signatureLength], 0, signatureLength); // reserve space for signature

            await outputStream.FlushAsync();

            try
            {
                using (HashStreamer hashStreamer = new HashStreamer(aesKey))
                    using (SymmetricStreamer symmetricStreamer = new SymmetricStreamer(aesKey, iv))
                    {
                        var hmacStream      = hashStreamer.HmacShaStream(outputStream, aesKey, CryptoStreamMode.Write);
                        var encryptedStream = symmetricStreamer.EncryptStream(hmacStream, CryptoStreamMode.Write);
                        outputStream.SetLength(firstData.Count + 64 + signatureLength);
                        outputStream.Position = firstData.Count + 64 + signatureLength;

                        await inputStream.CopyToAsync(encryptedStream);

                        // write hash in front of file
                        outputStream.Position = 0;
                        await outputStream.WriteAsync(firstData.ToArray(), 0, firstData.Count);

                        byte[] hash = hashStreamer.Hash;
                        await outputStream.WriteAsync(hash, 0, 64);

                        // write signature in front of file
                        await outputStream.WriteAsync(AsymmetricEncryption.Sign(hash), 0, signatureLength);

                        // flush it all the way
                        await outputStream.FlushAsync();

                        return(outputStream.Length);
                    }
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while encrypting file", e);
            }
        }
        /// <summary>
        /// Encrypt data
        /// </summary>
        /// <param name="type">Type of data</param>
        /// <param name="data">Data to be encrypted</param>
        /// <param name="publicKey">Public key of receiver</param>
        /// <exception cref="CryptoException"></exception>
        /// <returns>EncryptedPacket with all info for receiver to decrypt</returns>
        public static EncryptedPacket Encrypt(DataType type, byte[] data, RSAParameters publicKey)
        {
            // create AES session key
            byte[] sessionKey = Random.GetNumbers(32);

            // create AES IV
            byte[] iv = Random.GetNumbers(16);

            // encrypt AES session key with RSA
            byte[] encryptedSessionKey;
            try
            {
                encryptedSessionKey = AsymmetricEncryption.Encrypt(sessionKey, publicKey);
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while encrypting AES session key", e);
            }

            // encrypt data with AES
            byte[] encryptedData;
            try
            {
                encryptedData = SymmetricEncryption.Encrypt(data, sessionKey, iv);
            }
            catch (NullReferenceException)
            {
                throw new CryptoException("Data was null");
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while encrypting data", e);
            }


            // generate hash of encrypted data with session key
            byte[] hash;
            try
            {
                hash = Hashing.HmacSha(encryptedData, sessionKey);
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while hashing data", e);
            }


            // generate signature using hash
            byte[] signature;
            try
            {
                signature = AsymmetricEncryption.Sign(hash);
            }
            catch (CryptographicException e)
            {
                throw new CryptoException("Error while creating signature", e);
            }

            // put all info into new EncryptedPacket
            var encryptedPacket = new EncryptedPacket
            {
                DataType            = type,
                EncryptedSessionKey = encryptedSessionKey,
                Iv            = iv,
                Hmac          = hash,
                Signature     = signature,
                EncryptedData = encryptedData
            };

            return(encryptedPacket);
        }