コード例 #1
0
        /// <summary>
        /// Send Y to the remote client, with a random padding that is 0 to 512 bytes long
        /// (Either "1 A->B: Diffie Hellman Ya, PadA" or "2 B->A: Diffie Hellman Yb, PadB")
        /// </summary>
        protected async Task SendY()
        {
            byte[] toSend = new byte[96 + RandomNumber(512)];
            random.GetBytes(toSend);
            Buffer.BlockCopy(Y, 0, toSend, 0, 96);

            await NetworkIO.SendAsync(socket, toSend, 0, toSend.Length, null, null, null).ConfigureAwait(false);
        }
コード例 #2
0
        protected override async ReusableTask doneReceiveY()
        {
            CreateCryptors(KeyABytes, KeyBBytes);

            // 3 A->B: HASH('req1', S)
            byte[] req1 = Hash(Req1Bytes, S);

            // ... HASH('req2', SKEY)
            byte[] req2 = Hash(Req2Bytes, SKEY.Hash);

            // ... HASH('req3', S)
            byte[] req3 = Hash(Req3Bytes, S);

            // HASH('req2', SKEY) xor HASH('req3', S)
            for (int i = 0; i < req2.Length; i++)
            {
                req2[i] ^= req3[i];
            }

            byte[] padC = GeneratePad();

            // 3 A->B: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), ...
            int bufferLength = req1.Length + req2.Length + VerificationConstant.Length + CryptoProvide.Length
                               + 2 + padC.Length + 2 + InitialPayload.Length;

            byte[] buffer = ClientEngine.BufferPool.Rent(bufferLength);

            int offset = 0;

            offset += Message.Write(buffer, offset, req1);
            offset += Message.Write(buffer, offset, req2);
            offset += Message.Write(buffer, offset, VerificationConstant);
            DoEncrypt(buffer, offset - VerificationConstant.Length, VerificationConstant.Length);

            offset += Message.Write(buffer, offset, DoEncrypt(CryptoProvide));
            offset += Message.Write(buffer, offset, (short)padC.Length);
            DoEncrypt(buffer, offset - 2, 2);

            offset += Message.Write(buffer, offset, DoEncrypt(padC));

            // ... PadC, len(IA)), ENCRYPT(IA)
            offset += Message.Write(buffer, offset, (short)InitialPayload.Length);
            DoEncrypt(buffer, offset - 2, 2);

            offset += Message.Write(buffer, offset, DoEncrypt(InitialPayload));

            // Send the entire message in one go
            try {
                await NetworkIO.SendAsync(socket, buffer, 0, bufferLength).ConfigureAwait(false);
            } finally {
                ClientEngine.BufferPool.Return(buffer);
            }

            DoDecrypt(VerificationConstant, 0, VerificationConstant.Length);
            await Synchronize(VerificationConstant, 616).ConfigureAwait(false);   // 4 B->A: ENCRYPT(VC)
        }
コード例 #3
0
        /// <summary>
        /// Send Y to the remote client, with a random padding that is 0 to 512 bytes long
        /// (Either "1 A->B: Diffie Hellman Ya, PadA" or "2 B->A: Diffie Hellman Yb, PadB")
        /// </summary>
        async ReusableTask SendYAsync()
        {
            int length = 96 + RandomNumber(512);

            using (NetworkIO.BufferPool.Rent(length, out ByteBuffer toSend)) {
                Buffer.BlockCopy(Y, 0, toSend.Data, 0, 96);
                random.GetBytes(toSend.Data, 96, length - 96);
                await NetworkIO.SendAsync(socket, toSend, 0, length, null, null, null).ConfigureAwait(false);
            }
        }
コード例 #4
0
        /// <summary>
        /// Send Y to the remote client, with a random padding that is 0 to 512 bytes long
        /// (Either "1 A->B: Diffie Hellman Ya, PadA" or "2 B->A: Diffie Hellman Yb, PadB")
        /// </summary>
        async ReusableTask SendYAsync()
        {
            int length = 96 + RandomNumber(512);

            using (NetworkIO.BufferPool.Rent(length, out SocketMemory toSend)) {
                Y.AsSpan(0, 96).CopyTo(toSend.AsSpan());
                random.GetBytes(toSend.Span.Slice(96));
                await NetworkIO.SendAsync(socket, toSend, null, null, null).ConfigureAwait(false);
            }
        }
コード例 #5
0
ファイル: EncryptedSocket.cs プロジェクト: zjklee/monotorrent
        /// <summary>
        /// Send Y to the remote client, with a random padding that is 0 to 512 bytes long
        /// (Either "1 A->B: Diffie Hellman Ya, PadA" or "2 B->A: Diffie Hellman Yb, PadB")
        /// </summary>
        protected async ReusableTask SendY()
        {
            int length = 96 + RandomNumber(512);

            byte[] toSend = ClientEngine.BufferPool.Rent(length);
            Buffer.BlockCopy(Y, 0, toSend, 0, 96);
            random.GetBytes(toSend, 96, length - 96);
            try {
                await NetworkIO.SendAsync(socket, toSend, 0, length, null, null, null).ConfigureAwait(false);
            } finally {
                ClientEngine.BufferPool.Return(toSend);
            }
        }
コード例 #6
0
        /// <summary>
        /// Send Y to the remote client, with a random padding that is 0 to 512 bytes long
        /// (Either "1 A->B: Diffie Hellman Ya, PadA" or "2 B->A: Diffie Hellman Yb, PadB")
        /// </summary>
        protected async ReusableTask SendY()
        {
            var length = 96 + RandomNumber(512);
            var toSend = ClientEngine.BufferManager.GetBuffer(length);

            Buffer.BlockCopy(Y, 0, toSend, 0, 96);
            random.GetBytes(toSend, 96, length - 96);
            try  {
                await NetworkIO.SendAsync(socket, toSend, 0, length, null, null, null).ConfigureAwait(false);
            } finally {
                ClientEngine.BufferManager.FreeBuffer(toSend);
            }
        }
コード例 #7
0
        private async Task StepThree()
        {
            CreateCryptors("keyA", "keyB");

            // 3 A->B: HASH('req1', S)
            byte[] req1 = Hash(Encoding.ASCII.GetBytes("req1"), S);

            // ... HASH('req2', SKEY)
            byte[] req2 = Hash(Encoding.ASCII.GetBytes("req2"), SKEY.Hash);

            // ... HASH('req3', S)
            byte[] req3 = Hash(Encoding.ASCII.GetBytes("req3"), S);

            // HASH('req2', SKEY) xor HASH('req3', S)
            for (int i = 0; i < req2.Length; i++)
            {
                req2[i] ^= req3[i];
            }

            byte[] padC = GeneratePad();

            // 3 A->B: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), ...
            byte[] buffer = new byte[req1.Length + req2.Length + VerificationConstant.Length + CryptoProvide.Length
                                     + 2 + padC.Length + 2 + InitialPayload.Length];

            int offset = 0;

            offset += Message.Write(buffer, offset, req1);
            offset += Message.Write(buffer, offset, req2);
            offset += Message.Write(buffer, offset, DoEncrypt(VerificationConstant));
            offset += Message.Write(buffer, offset, DoEncrypt(CryptoProvide));
            offset += Message.Write(buffer, offset, DoEncrypt(Len(padC)));
            offset += Message.Write(buffer, offset, DoEncrypt(padC));

            // ... PadC, len(IA)), ENCRYPT(IA)
            offset += Message.Write(buffer, offset, DoEncrypt(Len(InitialPayload)));
            offset += Message.Write(buffer, offset, DoEncrypt(InitialPayload));

            // Send the entire message in one go
            await NetworkIO.SendAsync(socket, buffer, 0, buffer.Length, null, null, null).ConfigureAwait(false);

            InitialPayload = BufferManager.EmptyBuffer;

            await Synchronize(DoDecrypt(VerificationConstant), 616); // 4 B->A: ENCRYPT(VC)
        }
コード例 #8
0
        static async ReusableTask <EncryptorResult> DoCheckOutgoingConnectionAsync(IConnection2 connection, EncryptionTypes encryption, EngineSettings settings, InfoHash infoHash, HandshakeMessage handshake)
        {
            EncryptionTypes allowedEncryption = settings.AllowedEncryption & encryption;
            bool            supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header);
            bool            supportsRC4Full   = allowedEncryption.HasFlag(EncryptionTypes.RC4Full);
            bool            supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText);

            if ((settings.PreferEncryption || !supportsPlainText) && (supportsRC4Header || supportsRC4Full))
            {
                // First switch to the threadpool as creating encrypted sockets runs expensive computations in the ctor
                await MainLoop.SwitchToThreadpool();

                var encSocket = new PeerAEncryption(infoHash, allowedEncryption, handshake?.Encode());
                await encSocket.HandshakeAsync(connection).ConfigureAwait(false);

                if (encSocket.Decryptor is RC4Header && !supportsRC4Header)
                {
                    throw new EncryptionException("Decryptor was RC4Header but that is not allowed");
                }
                if (encSocket.Decryptor is RC4 && !supportsRC4Full)
                {
                    throw new EncryptionException("Decryptor was RC4Full but that is not allowed");
                }

                return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, null));
            }
            else if (supportsPlainText)
            {
                if (handshake != null)
                {
                    int    length = handshake.ByteLength;
                    byte[] buffer = ClientEngine.BufferPool.Rent(length);
                    handshake.Encode(buffer, 0);
                    try {
                        await NetworkIO.SendAsync(connection, buffer, 0, length, null, null, null).ConfigureAwait(false);
                    } finally {
                        ClientEngine.BufferPool.Return(buffer);
                    }
                }
                return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, null));
            }

            connection.Dispose();
            throw new EncryptionException("Invalid handshake received and no decryption works");
        }
コード例 #9
0
        protected override async ReusableTask DoneReceiveY()
        {
            CreateCryptors(KeyABytes, KeyBBytes);

            // 3 A->B: HASH('req1', S)
            byte[] req1 = Hash(Req1Bytes, S);

            // ... HASH('req2', SKEY)
            byte[] req2 = Hash(Req2Bytes, SKEY.Span.ToArray());

            // ... HASH('req3', S)
            byte[] req3 = Hash(Req3Bytes, S);

            // HASH('req2', SKEY) xor HASH('req3', S)
            for (int i = 0; i < req2.Length; i++)
            {
                req2[i] ^= req3[i];
            }

            using var releaser = MemoryPool.Default.Rent(RandomNumber(512), out Memory <byte> padC);

            // 3 A->B: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), ...
            int bufferLength = req1.Length + req2.Length + VerificationConstant.Length + CryptoProvide.Length
                               + 2 + padC.Length + 2 + InitialPayload.Length;

            using (NetworkIO.BufferPool.Rent(bufferLength, out SocketMemory buffer)) {
                var position = buffer.Memory;
                Message.Write(ref position, req1);
                Message.Write(ref position, req2);

                var before = position;
                Message.Write(ref position, VerificationConstant);
                Message.Write(ref position, CryptoProvide);
                Message.Write(ref position, (short)padC.Length);
                Message.Write(ref position, padC.Span);
                Message.Write(ref position, (short)InitialPayload.Length);
                Message.Write(ref position, InitialPayload);
                DoEncrypt(before.Span);

                await NetworkIO.SendAsync(socket, buffer).ConfigureAwait(false);
            }
            DoDecrypt(VerificationConstant);
            await SynchronizeAsync(VerificationConstant, 616).ConfigureAwait(false);   // 4 B->A: ENCRYPT(VC)
        }
コード例 #10
0
        private async Task StepFour()
        {
            byte[] padD = GeneratePad();
            SelectCrypto(b, false);
            // 4 B->A: ENCRYPT(VC, crypto_select, len(padD), padD)
            byte[] buffer = new byte[VerificationConstant.Length + CryptoSelect.Length + 2 + padD.Length];

            int offset = 0;

            offset += Message.Write(buffer, offset, VerificationConstant);
            offset += Message.Write(buffer, offset, CryptoSelect);
            offset += Message.Write(buffer, offset, Len(padD));
            offset += Message.Write(buffer, offset, padD);

            DoEncrypt(buffer, 0, buffer.Length);
            await NetworkIO.SendAsync(socket, buffer, 0, buffer.Length, null, null, null).ConfigureAwait(false);

            SelectCrypto(b, true);
        }
コード例 #11
0
        static async ReusableTask <EncryptorResult> DoCheckOutgoingConnectionAsync(IConnection2 connection, EncryptionTypes encryption, EngineSettings settings, InfoHash infoHash, HandshakeMessage handshake)
        {
            var allowedEncryption = settings.AllowedEncryption & encryption;
            var supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header);
            var supportsRC4Full   = allowedEncryption.HasFlag(EncryptionTypes.RC4Full);
            var supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText);

            if ((settings.PreferEncryption || !supportsPlainText) && (supportsRC4Header || supportsRC4Full))
            {
                var encSocket = new PeerAEncryption(infoHash, allowedEncryption, handshake?.Encode());

                await encSocket.HandshakeAsync(connection);

                if (encSocket.Decryptor is RC4Header && !supportsRC4Header)
                {
                    throw new EncryptionException("Decryptor was RC4Header but that is not allowed");
                }
                if (encSocket.Decryptor is RC4 && !supportsRC4Full)
                {
                    throw new EncryptionException("Decryptor was RC4Full but that is not allowed");
                }

                return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, null));
            }
            else if (supportsPlainText)
            {
                if (handshake != null)
                {
                    var length = handshake.ByteLength;
                    var buffer = ClientEngine.BufferPool.Rent(length);
                    handshake.Encode(buffer, 0);
                    try  {
                        await NetworkIO.SendAsync(connection, buffer, 0, length, null, null, null);
                    } finally  {
                        ClientEngine.BufferPool.Return(buffer);
                    }
                }
                return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, null));
            }

            connection.Dispose();
            throw new EncryptionException("Invalid handshake received and no decryption works");
        }
コード例 #12
0
        static async ReusableTask <EncryptorResult> DoCheckOutgoingConnectionAsync(IPeerConnection connection, IList <EncryptionType> preferredEncryption, InfoHash infoHash, HandshakeMessage?handshake, Factories factories)
        {
            bool supportsRC4Header = preferredEncryption.Contains(EncryptionType.RC4Header);
            bool supportsRC4Full   = preferredEncryption.Contains(EncryptionType.RC4Full);
            bool supportsPlainText = preferredEncryption.Contains(EncryptionType.PlainText);

            // First switch to the threadpool as creating encrypted sockets runs expensive computations in the ctor
            await MainLoop.SwitchToThreadpool();

            Memory <byte> handshakeBuffer = default;

            using var releaser = handshake == null ? default : NetworkIO.BufferPool.Rent(handshake.ByteLength, out handshakeBuffer);
                                 handshake?.Encode(handshakeBuffer.Span);

                                 if (preferredEncryption[0] != EncryptionType.PlainText)
                                 {
                                     using var encSocket = new PeerAEncryption(factories, infoHash, preferredEncryption, handshakeBuffer);
                                     await encSocket.HandshakeAsync(connection).ConfigureAwait(false);

                                     if (encSocket.Decryptor is RC4Header && !supportsRC4Header)
                                     {
                                         throw new EncryptionException("Decryptor was RC4Header but that is not allowed");
                                     }
                                     if (encSocket.Decryptor is RC4 && !supportsRC4Full)
                                     {
                                         throw new EncryptionException("Decryptor was RC4Full but that is not allowed");
                                     }

                                     return(new EncryptorResult(encSocket.Decryptor !, encSocket.Encryptor !, null));
                                 }
                                 else if (supportsPlainText)
                                 {
                                     if (handshakeBuffer.Length > 0)
                                     {
                                         await NetworkIO.SendAsync(connection, handshakeBuffer, null, null, null).ConfigureAwait(false);
                                     }
                                     return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, null));
                                 }

                                 connection.Dispose();
                                 throw new EncryptionException("Invalid handshake received and no decryption works");
        }
コード例 #13
0
        static async ReusableTask <EncryptorResult> DoCheckOutgoingConnectionAsync(IConnection connection, IList <EncryptionType> preferredEncryption, InfoHash infoHash, HandshakeMessage handshake)
        {
            bool supportsRC4Header = preferredEncryption.Contains(EncryptionType.RC4Header);
            bool supportsRC4Full   = preferredEncryption.Contains(EncryptionType.RC4Full);
            bool supportsPlainText = preferredEncryption.Contains(EncryptionType.PlainText);

            // First switch to the threadpool as creating encrypted sockets runs expensive computations in the ctor
            await MainLoop.SwitchToThreadpool();

            if (preferredEncryption[0] != EncryptionType.PlainText)
            {
                var encSocket = new PeerAEncryption(infoHash, preferredEncryption, handshake?.Encode());
                await encSocket.HandshakeAsync(connection).ConfigureAwait(false);

                if (encSocket.Decryptor is RC4Header && !supportsRC4Header)
                {
                    throw new EncryptionException("Decryptor was RC4Header but that is not allowed");
                }
                if (encSocket.Decryptor is RC4 && !supportsRC4Full)
                {
                    throw new EncryptionException("Decryptor was RC4Full but that is not allowed");
                }

                return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, null));
            }
            else if (supportsPlainText)
            {
                if (handshake != null)
                {
                    int length = handshake.ByteLength;
                    using (NetworkIO.BufferPool.Rent(length, out ByteBuffer buffer)) {
                        handshake.Encode(buffer.Data, 0);
                        await NetworkIO.SendAsync(connection, buffer, 0, length, null, null, null).ConfigureAwait(false);
                    }
                }
                return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, null));
            }

            connection.Dispose();
            throw new EncryptionException("Invalid handshake received and no decryption works");
        }
コード例 #14
0
        async ReusableTask GotVerification(byte[] verifyBytes)
        {
            byte[] torrentHash = new byte[20];

            byte[] myCP = new byte[4];

            Array.Copy(verifyBytes, 0, torrentHash, 0, torrentHash.Length);  // HASH('req2', SKEY) xor HASH('req3', S)

            if (!MatchSKEY(torrentHash))
            {
                throw new EncryptionException("No valid SKey found");
            }

            CreateCryptors(KeyBBytes, KeyABytes);

            DoDecrypt(verifyBytes, 20, 14);  // ENCRYPT(VC, ...

            if (!Toolbox.ByteMatch(verifyBytes, 20, VerificationConstant, 0, VerificationConstant.Length))
            {
                throw new EncryptionException("Verification constant was invalid");
            }

            Array.Copy(verifyBytes, 28, myCP, 0, myCP.Length);  // ...crypto_provide ...

            // We need to select the crypto *after* we send our response, otherwise the wrong
            // encryption will be used on the response
            int lenInitialPayload;
            int lenPadC = Message.ReadShort(verifyBytes, 32) + 2;

            using (NetworkIO.BufferPool.Rent(lenPadC, out ByteBuffer padC)) {
                await ReceiveMessageAsync(padC, lenPadC).ConfigureAwait(false);   // padC

                DoDecrypt(padC.Data, 0, lenPadC);
                lenInitialPayload = Message.ReadShort(padC.Data, lenPadC - 2);
            }

            InitialData = new byte[lenInitialPayload]; // ... ENCRYPT(IA)
            using (NetworkIO.BufferPool.Rent(InitialData.Length, out ByteBuffer receiveBuffer)) {
                await ReceiveMessageAsync(receiveBuffer, InitialData.Length).ConfigureAwait(false);

                Buffer.BlockCopy(receiveBuffer.Data, 0, InitialData, 0, lenInitialPayload);
                DoDecrypt(InitialData, 0, InitialData.Length);  // ... ENCRYPT(IA)
            }

            // Step Four
            byte[] padD = GeneratePad();
            SelectCrypto(myCP, false);

            // 4 B->A: ENCRYPT(VC, crypto_select, len(padD), padD)
            int finalBufferLength = VerificationConstant.Length + CryptoSelect.Length + 2 + padD.Length;

            using (NetworkIO.BufferPool.Rent(finalBufferLength, out ByteBuffer buffer)) {
                int offset = 0;
                offset += Message.Write(buffer.Data, offset, VerificationConstant);
                offset += Message.Write(buffer.Data, offset, CryptoSelect);
                offset += Message.Write(buffer.Data, offset, Len(padD));
                offset += Message.Write(buffer.Data, offset, padD);

                DoEncrypt(buffer.Data, 0, finalBufferLength);
                await NetworkIO.SendAsync(socket, buffer, 0, finalBufferLength).ConfigureAwait(false);
            }

            SelectCrypto(myCP, true);
        }
コード例 #15
0
        async ReusableTask GotVerification(Memory <byte> verifyBytes)
        {
            var infoHash             = verifyBytes.Slice(0, 20);
            var verificationConstant = verifyBytes.Slice(20, 8);
            var myCP     = verifyBytes.Slice(28, 4);
            var padCSpan = verifyBytes.Slice(32, 2);

            if (!MatchSKEY(infoHash.Span))
            {
                throw new EncryptionException("No valid SKey found");
            }

            // Create the encryptor/decryptors
            CreateCryptors(KeyBBytes, KeyABytes);

            // Decrypt everything after the infohash.
            DoDecrypt(verifyBytes.Slice(infoHash.Length).Span);

            if (!verificationConstant.Span.SequenceEqual(VerificationConstant))
            {
                throw new EncryptionException("Verification constant was invalid");
            }

            // We need to select the crypto *after* we send our response, otherwise the wrong
            // encryption will be used on the response
            int lenInitialPayload;
            int lenPadC = Message.ReadShort(padCSpan.Span) + 2;

            using (NetworkIO.BufferPool.Rent(lenPadC, out SocketMemory padC)) {
                await ReceiveMessageAsync(padC).ConfigureAwait(false);   // padC

                DoDecrypt(padC.AsSpan());
                lenInitialPayload = Message.ReadShort(padC.AsSpan(lenPadC - 2, 2));
            }

            InitialData = new byte[lenInitialPayload]; // ... ENCRYPT(IA)
            using (NetworkIO.BufferPool.Rent(InitialData.Length, out SocketMemory receiveBuffer)) {
                await ReceiveMessageAsync(receiveBuffer).ConfigureAwait(false);

                receiveBuffer.Memory.CopyTo(InitialData);
                DoDecrypt(InitialData);  // ... ENCRYPT(IA)
            }

            // Step Four
            using var releaser = MemoryPool.Default.Rent(RandomNumber(512), out Memory <byte> padD);
            SelectCrypto(myCP.Span, false);

            // 4 B->A: ENCRYPT(VC, crypto_select, len(padD), padD)
            int finalBufferLength = VerificationConstant.Length + CryptoSelect.Length + 2 + padD.Length;

            using (NetworkIO.BufferPool.Rent(finalBufferLength, out SocketMemory buffer)) {
                var position = buffer.Memory;
                Message.Write(ref position, VerificationConstant);
                Message.Write(ref position, CryptoSelect);
                Message.Write(ref position, (short)padD.Length);
                Message.Write(ref position, padD.Span);
                DoEncrypt(buffer.Span);

                await NetworkIO.SendAsync(socket, buffer).ConfigureAwait(false);
            }

            SelectCrypto(myCP.Span, true);
        }
コード例 #16
0
        private async Task gotVerification(byte[] verifyBytes)
        {
            byte[] torrentHash = new byte[20];

            byte[] myVC    = new byte[8];
            byte[] myCP    = new byte[4];
            byte[] lenPadC = new byte[2];

            Array.Copy(verifyBytes, 0, torrentHash, 0, torrentHash.Length); // HASH('req2', SKEY) xor HASH('req3', S)

            if (!MatchSKEY(torrentHash))
            {
                throw new EncryptionException("No valid SKey found");
            }

            CreateCryptors("keyB", "keyA");

            DoDecrypt(verifyBytes, 20, 14); // ENCRYPT(VC, ...

            Array.Copy(verifyBytes, 20, myVC, 0, myVC.Length);
            if (!Toolbox.ByteMatch(myVC, VerificationConstant))
            {
                throw new EncryptionException("Verification constant was invalid");
            }

            Array.Copy(verifyBytes, 28, myCP, 0, myCP.Length); // ...crypto_provide ...

            // We need to select the crypto *after* we send our response, otherwise the wrong
            // encryption will be used on the response
            Array.Copy(verifyBytes, 32, lenPadC, 0, lenPadC.Length); // ... len(padC) ...
            var padC = new byte[DeLen(lenPadC) + 2];

            await ReceiveMessage(padC, padC.Length); // padC

            DoDecrypt(padC, 0, padC.Length);

            byte[] lenInitialPayload = new byte[2]; // ... len(IA))
            Array.Copy(padC, padC.Length - 2, lenInitialPayload, 0, 2);

            InitialData = new byte[DeLen(lenInitialPayload)]; // ... ENCRYPT(IA)
            await ReceiveMessage(InitialData, InitialData.Length);

            DoDecrypt(InitialData, 0, InitialData.Length); // ... ENCRYPT(IA)

            // Step Four
            byte[] padD = GeneratePad();
            SelectCrypto(myCP, false);

            // 4 B->A: ENCRYPT(VC, crypto_select, len(padD), padD)
            byte[] buffer = new byte[VerificationConstant.Length + CryptoSelect.Length + 2 + padD.Length];

            int offset = 0;

            offset += Message.Write(buffer, offset, VerificationConstant);
            offset += Message.Write(buffer, offset, CryptoSelect);
            offset += Message.Write(buffer, offset, Len(padD));
            offset += Message.Write(buffer, offset, padD);

            DoEncrypt(buffer, 0, buffer.Length);
            await NetworkIO.SendAsync(socket, buffer, 0, buffer.Length, null, null, null).ConfigureAwait(false);

            SelectCrypto(myCP, true);
        }