How to use stream cipher to encrypt data in TcpStream

Hello,
I'm wrting a tcp proxy server, data must be encrypted between the client and server. I want to write

  1. a transparent stream writer which wraps the raw TCP writer, it will encrypt data before sending
  2. a transparent stream reader which wraps the raw TCP reader, it will decrypt data after reading
    see the code snippet for the server, is this the right approach?
    The client can not get right data after transferring data for some time, is it due to the cipher data is split during network transfer?

fn handle_client_conn() {
    let (mut payload_reader, mut payload_writer) = payload_socket;
    let (mut client_reader, mut client_writer) = client_socket;
    let mut dec_reader = DecReader::new(client_reader);
    let mut enc_writer = DecReader::new(client_writer);
    async::spawn{
        copy_stream(payload_reader, enc_writer); //transparent write payload data
    }
    copy_stream(dec_reader, payload_writer); //transparent read client request
}


pub struct DecReader {
    inner: OwnedReadHalf,
    crypt_cipher: ChaCha20,
}


impl AsyncRead for DecReader {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<io::Result<()>> {
    
        match Pin::new(&mut self.inner).poll_read(cx, buf) {
            Poll::Ready(x) => {
                &self.crypt_cipher.apply_keystream(&mut buf); // decrypt data
                Poll::Ready(Ok(()))
            }
            Poll::Pending => {
                Poll::Pending
            }
        }
        
        
    }
}


pub struct EncWriter {
    inner: OwnedWriteHalf,
    crypt_cipher: ChaCha20,
}

impl AsyncWrite for EncWriter {
    fn poll_write(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<io::Result<usize>> {
    
        if buf.len() > 0 {
            let mut wbuf = Vec::with_capacity(len);
            wbuf.copy_from_slice(buf);
            &self.crypt_cipher.apply_keystream(&mut wbuf); //encrypt data
            return Pin::new(&mut self.inner).poll_write(cx, &wbuf);
        }
        
        Pin::new(&mut self.inner).poll_write(cx, buf)
    }
}

(Playground)

I have never really used this cipher implementation before so I am not 100% sure how this works. The general approach seems ok, since this is a stream cipher.

A first step to try and debug this error, would be to print what data you send/receive and compare the two sides.

Also give us more details. What does it mean "can not get the right data"? Does it decrypt to garbage? does it not even decrypt?

The main way this can go wrong is that poll_write might not write everything you have passed to it, in which case you consumed more of your cipher than you actually wrote.

Thanks, so it is due to packet loss then the ciphers state not the same, I'm considering split stream data to blocks and do verifying, but I dont know how to handle that packet loss condition, is there any project I can reference for?

This is not packet loss! No data is lost. It's just that there's no guarantee you can write the entire contents of wbuf right now, and if you can't then poll_write returns either Pending or Ready(Ok(n)) with n < wbuf.len().

You will have to introduce some sort of buffering in your writer to remember the encrypted bytes that could not be written right now.

1 Like

The lost data is not due to packet lost. TCP guarantees that no packet will be lost.

In a sense your code is the source of data loss because it doesn't correctly write the data to the write half. As @alice said, you can't be sure how many bytes were actually written to the write half. Or to any type that is AsyncWrite for that matter.

That is the reason why poll_write returns the number of bytes written. So if you want to write 10 bytes, and the first call to poll_write returns a Ready(Ok(3)), only the first 3 bytes of your buffer were written. You need to remember the other 7 bytes, so that when you are polled again, you can try to write them in the write half. And you need to keep doing that until the total number of bytes written is 10.

1 Like

Unless it's a learning exercise or you are 100% confident in your cryptography knowledge, you should NOT use unauthenticated encryption and especially raw streaming algorithms like ChaCha and CTR modes. It can lead to serious vulnerabilities. See the AEAD crates for possible solutions.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.