public void GenerateKeyStream(byte[] key, byte[] iv, byte[] output)
        {
            if (output.Length % BLOCK_SIZE != 0)
            {
                throw new Exception("Output buffer size must be aligned to BLOCK_SIZE");
            }

            var keyHandle    = default(GCHandle);
            var ivHandle     = default(GCHandle);
            var inputHandle  = default(GCHandle);
            var outputHandle = default(GCHandle);

            try
            {
                var inputBlock = new byte[BLOCK_SIZE];

                keyHandle    = GCHandle.Alloc(key, GCHandleType.Pinned);
                ivHandle     = GCHandle.Alloc(iv, GCHandleType.Pinned);
                inputHandle  = GCHandle.Alloc(inputBlock, GCHandleType.Pinned);
                outputHandle = GCHandle.Alloc(output, GCHandleType.Pinned);

                if (OpenSsl.EVP_EncryptInit_ex(_ctx, OpenSsl.EVP_aes_128_ctr(), IntPtr.Zero,
                                               keyHandle.AddrOfPinnedObject(), ivHandle.AddrOfPinnedObject()) != 1)
                {
                    throw new Exception($"Cannot initialize AES: {OpenSsl.GetLastError()}");
                }

                int outputOffset = 0;
                while (outputOffset < output.Length)
                {
                    int outputLength = 0;
                    if (OpenSsl.EVP_EncryptUpdate(_ctx,
                                                  IntPtr.Add(outputHandle.AddrOfPinnedObject(), outputOffset), ref outputLength,
                                                  inputHandle.AddrOfPinnedObject(), BLOCK_SIZE) != 1)
                    {
                        throw new Exception($"Cannot encode AES: {OpenSsl.GetLastError()}");
                    }
                    outputOffset += outputLength;
                }
            }
            finally
            {
                if (keyHandle.IsAllocated)
                {
                    keyHandle.Free();
                }
                if (ivHandle.IsAllocated)
                {
                    ivHandle.Free();
                }
                if (inputHandle.IsAllocated)
                {
                    inputHandle.Free();
                }
                if (outputHandle.IsAllocated)
                {
                    outputHandle.Free();
                }
            }
        }
        public void GenerateSessionKey(
            byte[] masterKey,
            byte[] masterSalt,
            byte[] sessionKey,
            byte[] sessionSalt,
            byte[] sessionAuth
            )
        {
            var keyHandle         = default(GCHandle);
            var ivHandle          = default(GCHandle);
            var inputHandle       = default(GCHandle);
            var sessionKeyHandle  = default(GCHandle);
            var sessionSaltHandle = default(GCHandle);
            var sessionAuthHandle = default(GCHandle);

            try
            {
                var inputBlock = new byte[BLOCK_SIZE];
                var sessionIv  = new byte[BLOCK_SIZE];

                keyHandle         = GCHandle.Alloc(masterKey, GCHandleType.Pinned);
                ivHandle          = GCHandle.Alloc(sessionIv, GCHandleType.Pinned);
                inputHandle       = GCHandle.Alloc(inputBlock, GCHandleType.Pinned);
                sessionKeyHandle  = GCHandle.Alloc(sessionKey, GCHandleType.Pinned);
                sessionSaltHandle = GCHandle.Alloc(sessionSalt, GCHandleType.Pinned);
                sessionAuthHandle = GCHandle.Alloc(sessionAuth, GCHandleType.Pinned);

                Array.Copy(masterSalt, sessionIv, masterSalt.Length);

                if (OpenSsl.EVP_EncryptInit_ex(_ctx, OpenSsl.EVP_aes_128_ctr(), IntPtr.Zero,
                                               keyHandle.AddrOfPinnedObject(), ivHandle.AddrOfPinnedObject()) != 1)
                {
                    throw new Exception($"Cannot initialize AES: {OpenSsl.GetLastError()}");
                }

                int outputLength = 0;
                if (OpenSsl.EVP_EncryptUpdate(_ctx,
                                              sessionKeyHandle.AddrOfPinnedObject(), ref outputLength,
                                              inputHandle.AddrOfPinnedObject(), BLOCK_SIZE) != 1)
                {
                    throw new Exception($"Cannot encode AES: {OpenSsl.GetLastError()}");
                }

                sessionIv[7] ^= 0x02;
                if (OpenSsl.EVP_EncryptInit_ex(_ctx, OpenSsl.EVP_aes_128_ctr(), IntPtr.Zero,
                                               keyHandle.AddrOfPinnedObject(), ivHandle.AddrOfPinnedObject()) != 1)
                {
                    throw new Exception($"Cannot initialize AES: {OpenSsl.GetLastError()}");
                }

                outputLength = 0;
                if (OpenSsl.EVP_EncryptUpdate(_ctx,
                                              sessionSaltHandle.AddrOfPinnedObject(), ref outputLength,
                                              inputHandle.AddrOfPinnedObject(), BLOCK_SIZE) != 1)
                {
                    throw new Exception($"Cannot encode AES: {OpenSsl.GetLastError()}");
                }

                sessionIv[7] ^= 0x03;
                if (OpenSsl.EVP_EncryptInit_ex(_ctx, OpenSsl.EVP_aes_128_ctr(), IntPtr.Zero,
                                               keyHandle.AddrOfPinnedObject(), ivHandle.AddrOfPinnedObject()) != 1)
                {
                    throw new Exception($"Cannot initialize AES: {OpenSsl.GetLastError()}");
                }

                int outputOffset = 0;
                while (outputOffset < sessionAuth.Length)
                {
                    outputLength = 0;
                    if (OpenSsl.EVP_EncryptUpdate(_ctx,
                                                  IntPtr.Add(sessionAuthHandle.AddrOfPinnedObject(), outputOffset), ref outputLength,
                                                  inputHandle.AddrOfPinnedObject(), BLOCK_SIZE) != 1)
                    {
                        throw new Exception($"Cannot encode AES: {OpenSsl.GetLastError()}");
                    }
                    outputOffset += outputLength;
                }
            }
            finally
            {
                if (keyHandle.IsAllocated)
                {
                    keyHandle.Free();
                }
                if (ivHandle.IsAllocated)
                {
                    ivHandle.Free();
                }
                if (inputHandle.IsAllocated)
                {
                    inputHandle.Free();
                }
                if (sessionKeyHandle.IsAllocated)
                {
                    sessionKeyHandle.Free();
                }
                if (sessionSaltHandle.IsAllocated)
                {
                    sessionSaltHandle.Free();
                }
                if (sessionAuthHandle.IsAllocated)
                {
                    sessionAuthHandle.Free();
                }
            }
        }